1// Copyright 2017 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include <object/fifo_dispatcher.h>
8
9#include <string.h>
10
11#include <zircon/rights.h>
12#include <fbl/alloc_checker.h>
13#include <fbl/auto_lock.h>
14#include <object/handle.h>
15
16// static
17zx_status_t FifoDispatcher::Create(size_t count, size_t elemsize, uint32_t options,
18                                   fbl::RefPtr<Dispatcher>* dispatcher0,
19                                   fbl::RefPtr<Dispatcher>* dispatcher1,
20                                   zx_rights_t* rights) {
21    // count and elemsize must be nonzero
22    // count must be a power of two
23    // total size must be <= kMaxSizeBytes
24    if (!count || !elemsize || (count & (count - 1)) ||
25        (count > kMaxSizeBytes) || (elemsize > kMaxSizeBytes) ||
26        ((count * elemsize) > kMaxSizeBytes)) {
27        return ZX_ERR_OUT_OF_RANGE;
28    }
29
30    fbl::AllocChecker ac;
31    auto holder0 = fbl::AdoptRef(new (&ac) PeerHolder<FifoDispatcher>());
32    if (!ac.check())
33        return ZX_ERR_NO_MEMORY;
34    auto holder1 = holder0;
35
36    auto data0 = fbl::unique_ptr<uint8_t[]>(new (&ac) uint8_t[count * elemsize]);
37    if (!ac.check())
38        return ZX_ERR_NO_MEMORY;
39
40    auto fifo0 = fbl::AdoptRef(new (&ac) FifoDispatcher(fbl::move(holder0), options, static_cast<uint32_t>(count),
41                                                        static_cast<uint32_t>(elemsize), fbl::move(data0)));
42    if (!ac.check())
43        return ZX_ERR_NO_MEMORY;
44
45    auto data1 = fbl::unique_ptr<uint8_t[]>(new (&ac) uint8_t[count * elemsize]);
46    if (!ac.check())
47        return ZX_ERR_NO_MEMORY;
48
49    auto fifo1 = fbl::AdoptRef(new (&ac) FifoDispatcher(fbl::move(holder1), options, static_cast<uint32_t>(count),
50                                                        static_cast<uint32_t>(elemsize), fbl::move(data1)));
51    if (!ac.check())
52        return ZX_ERR_NO_MEMORY;
53
54    fifo0->Init(fifo1);
55    fifo1->Init(fifo0);
56
57    *rights = ZX_DEFAULT_FIFO_RIGHTS;
58    *dispatcher0 = fbl::move(fifo0);
59    *dispatcher1 = fbl::move(fifo1);
60    return ZX_OK;
61}
62
63FifoDispatcher::FifoDispatcher(fbl::RefPtr<PeerHolder<FifoDispatcher>> holder,
64                               uint32_t /*options*/, uint32_t count, uint32_t elem_size,
65                               fbl::unique_ptr<uint8_t[]> data)
66    : PeeredDispatcher(fbl::move(holder), ZX_FIFO_WRITABLE),
67      elem_count_(count), elem_size_(elem_size), mask_(count - 1),
68      head_(0u), tail_(0u), data_(fbl::move(data)) {
69}
70
71FifoDispatcher::~FifoDispatcher() {
72}
73
74// Thread safety analysis disabled as this happens during creation only,
75// when no other thread could be accessing the object.
76void FifoDispatcher::Init(fbl::RefPtr<FifoDispatcher> other) TA_NO_THREAD_SAFETY_ANALYSIS {
77    peer_ = fbl::move(other);
78    peer_koid_ = peer_->get_koid();
79}
80
81zx_status_t FifoDispatcher::UserSignalSelfLocked(uint32_t clear_mask, uint32_t set_mask) {
82    canary_.Assert();
83    UpdateStateLocked(clear_mask, set_mask);
84    return ZX_OK;
85}
86
87void FifoDispatcher::on_zero_handles_locked() {
88    canary_.Assert();
89}
90
91void FifoDispatcher::OnPeerZeroHandlesLocked() {
92    canary_.Assert();
93
94    UpdateStateLocked(ZX_FIFO_WRITABLE, ZX_FIFO_PEER_CLOSED);
95}
96
97zx_status_t FifoDispatcher::WriteFromUser(size_t elem_size, user_in_ptr<const uint8_t> ptr,
98                                          size_t count, size_t* actual)
99    TA_NO_THREAD_SAFETY_ANALYSIS {
100    canary_.Assert();
101
102    Guard<fbl::Mutex> guard{get_lock()};
103    if (!peer_)
104        return ZX_ERR_PEER_CLOSED;
105    return peer_->WriteSelfLocked(elem_size, ptr, count, actual);
106}
107
108zx_status_t FifoDispatcher::WriteSelfLocked(size_t elem_size, user_in_ptr<const uint8_t> ptr,
109                                            size_t count, size_t* actual)
110    TA_NO_THREAD_SAFETY_ANALYSIS {
111    canary_.Assert();
112
113    if (elem_size != elem_size_)
114        return ZX_ERR_OUT_OF_RANGE;
115    if (count == 0)
116        return ZX_ERR_OUT_OF_RANGE;
117
118    uint32_t old_head = head_;
119
120    // total number of available empty slots in the fifo
121    size_t avail = elem_count_ - (head_ - tail_);
122
123    if (avail == 0)
124        return ZX_ERR_SHOULD_WAIT;
125
126    bool was_empty = (avail == elem_count_);
127
128    if (count > avail)
129        count = avail;
130
131    while (count > 0) {
132        uint32_t offset = (head_ & mask_);
133
134        // number of slots from target to end, inclusive
135        uint32_t n = elem_count_ - offset;
136
137        // number of slots we can actually copy
138        size_t to_copy = (count > n) ? n : count;
139
140        zx_status_t status = ptr.copy_array_from_user(&data_[offset * elem_size_],
141                                                      to_copy * elem_size_);
142        if (status != ZX_OK) {
143            // roll back, in case this is the second copy
144            head_ = old_head;
145            return ZX_ERR_INVALID_ARGS;
146        }
147
148        // adjust head and count
149        // due to size limitations on fifo, to_copy will always fit in a u32
150        head_ += static_cast<uint32_t>(to_copy);
151        count -= to_copy;
152        ptr = ptr.byte_offset(to_copy * elem_size_);
153    }
154
155    // if was empty, we've become readable
156    if (was_empty)
157        UpdateStateLocked(0u, ZX_FIFO_READABLE);
158
159    // if now full, we're no longer writable
160    if (elem_count_ == (head_ - tail_))
161        peer_->UpdateStateLocked(ZX_FIFO_WRITABLE, 0u);
162
163    *actual = (head_ - old_head);
164    return ZX_OK;
165}
166
167zx_status_t FifoDispatcher::ReadToUser(size_t elem_size, user_out_ptr<uint8_t> ptr, size_t count,
168                                       size_t* actual)
169    TA_NO_THREAD_SAFETY_ANALYSIS {
170    canary_.Assert();
171
172    if (elem_size != elem_size_)
173        return ZX_ERR_OUT_OF_RANGE;
174    if (count == 0)
175        return ZX_ERR_OUT_OF_RANGE;
176
177    Guard<fbl::Mutex> guard{get_lock()};
178
179    uint32_t old_tail = tail_;
180
181    // total number of available entries to read from the fifo
182    size_t avail = (head_ - tail_);
183
184    if (avail == 0)
185        return peer_ ? ZX_ERR_SHOULD_WAIT : ZX_ERR_PEER_CLOSED;
186
187    bool was_full = (avail == elem_count_);
188
189    if (count > avail)
190        count = avail;
191
192    while (count > 0) {
193        uint32_t offset = (tail_ & mask_);
194
195        // number of slots from target to end, inclusive
196        uint32_t n = elem_count_ - offset;
197
198        // number of slots we can actually copy
199        size_t to_copy = (count > n) ? n : count;
200
201        zx_status_t status = ptr.copy_array_to_user(&data_[offset * elem_size_],
202                                                    to_copy * elem_size_);
203        if (status != ZX_OK) {
204            // roll back, in case this is the second copy
205            tail_ = old_tail;
206            return ZX_ERR_INVALID_ARGS;
207        }
208
209        // adjust tail and count
210        // due to size limitations on fifo, to_copy will always fit in a u32
211        tail_ += static_cast<uint32_t>(to_copy);
212        count -= to_copy;
213        ptr = ptr.byte_offset(to_copy * elem_size_);
214    }
215
216    // if we were full, we have become writable
217    if (was_full && peer_)
218        peer_->UpdateStateLocked(0u, ZX_FIFO_WRITABLE);
219
220    // if we've become empty, we're no longer readable
221    if ((head_ - tail_) == 0)
222        UpdateStateLocked(ZX_FIFO_READABLE, 0u);
223
224    *actual = (tail_ - old_tail);
225    return ZX_OK;
226}
227