| // Copyright 2017 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <object/fifo_dispatcher.h> |
| |
| #include <string.h> |
| |
| #include <lib/user_copy/user_ptr.h> |
| #include <zircon/rights.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <object/handle.h> |
| |
| using fbl::AutoLock; |
| |
| // static |
| zx_status_t FifoDispatcher::Create(uint32_t count, uint32_t elemsize, uint32_t options, |
| fbl::RefPtr<Dispatcher>* dispatcher0, |
| fbl::RefPtr<Dispatcher>* dispatcher1, |
| zx_rights_t* rights) { |
| // count and elemsize must be nonzero |
| // count must be a power of two |
| // total size must be <= kMaxSizeBytes |
| if (!count || !elemsize || (count & (count - 1)) || |
| (count > kMaxSizeBytes) || (elemsize > kMaxSizeBytes) || |
| ((count * elemsize) > kMaxSizeBytes)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| fbl::AllocChecker ac; |
| auto data0 = fbl::unique_ptr<uint8_t[]>(new (&ac) uint8_t[count * elemsize]); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| auto fifo0 = fbl::AdoptRef(new (&ac) FifoDispatcher(options, count, elemsize, fbl::move(data0))); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| auto data1 = fbl::unique_ptr<uint8_t[]>(new (&ac) uint8_t[count * elemsize]); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| auto fifo1 = fbl::AdoptRef(new (&ac) FifoDispatcher(options, count, elemsize, fbl::move(data1))); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| fifo0->Init(fifo1); |
| fifo1->Init(fifo0); |
| |
| *rights = ZX_DEFAULT_FIFO_RIGHTS; |
| *dispatcher0 = fbl::move(fifo0); |
| *dispatcher1 = fbl::move(fifo1); |
| return ZX_OK; |
| } |
| |
| FifoDispatcher::FifoDispatcher(uint32_t /*options*/, uint32_t count, uint32_t elem_size, |
| fbl::unique_ptr<uint8_t[]> data) |
| : elem_count_(count), elem_size_(elem_size), mask_(count - 1), |
| peer_koid_(0u), state_tracker_(ZX_FIFO_WRITABLE), |
| head_(0u), tail_(0u), data_(fbl::move(data)) { |
| } |
| |
| FifoDispatcher::~FifoDispatcher() { |
| } |
| |
| // Thread safety analysis disabled as this happens during creation only, |
| // when no other thread could be accessing the object. |
| void FifoDispatcher::Init(fbl::RefPtr<FifoDispatcher> other) TA_NO_THREAD_SAFETY_ANALYSIS { |
| other_ = fbl::move(other); |
| peer_koid_ = other_->get_koid(); |
| } |
| |
| zx_status_t FifoDispatcher::user_signal(uint32_t clear_mask, uint32_t set_mask, bool peer) { |
| canary_.Assert(); |
| |
| if ((set_mask & ~ZX_USER_SIGNAL_ALL) || (clear_mask & ~ZX_USER_SIGNAL_ALL)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| if (!peer) { |
| state_tracker_.UpdateState(clear_mask, set_mask); |
| return ZX_OK; |
| } |
| |
| fbl::RefPtr<FifoDispatcher> other; |
| { |
| AutoLock lock(&lock_); |
| if (!other_) |
| return ZX_ERR_PEER_CLOSED; |
| other = other_; |
| } |
| |
| return other->UserSignalSelf(clear_mask, set_mask); |
| } |
| |
| zx_status_t FifoDispatcher::UserSignalSelf(uint32_t clear_mask, uint32_t set_mask) { |
| canary_.Assert(); |
| state_tracker_.UpdateState(clear_mask, set_mask); |
| return ZX_OK; |
| } |
| |
| void FifoDispatcher::on_zero_handles() { |
| canary_.Assert(); |
| |
| fbl::RefPtr<FifoDispatcher> fifo; |
| { |
| AutoLock lock(&lock_); |
| fifo = fbl::move(other_); |
| } |
| if (fifo) |
| fifo->OnPeerZeroHandles(); |
| } |
| |
| void FifoDispatcher::OnPeerZeroHandles() { |
| canary_.Assert(); |
| |
| AutoLock lock(&lock_); |
| other_.reset(); |
| state_tracker_.UpdateState(ZX_FIFO_WRITABLE, ZX_FIFO_PEER_CLOSED); |
| } |
| |
| zx_status_t FifoDispatcher::Write(const uint8_t* src, size_t len, uint32_t* actual) { |
| auto copy_from_fn = [](const uint8_t* src, uint8_t* data, size_t len) -> zx_status_t { |
| memcpy(data, src, len); |
| return ZX_OK; |
| }; |
| return Write(src, len, actual, copy_from_fn); |
| } |
| |
| zx_status_t FifoDispatcher::Read(uint8_t* dst, size_t len, uint32_t* actual) { |
| auto copy_to_fn = [](uint8_t* dst, const uint8_t* data, size_t len) -> zx_status_t { |
| memcpy(dst, data, len); |
| return ZX_OK; |
| }; |
| return Read(dst, len, actual, copy_to_fn); |
| } |
| |
| zx_status_t FifoDispatcher::WriteFromUser(const uint8_t* src, size_t len, uint32_t* actual) { |
| auto copy_from_fn = [](const uint8_t* src, uint8_t* data, size_t len) -> zx_status_t { |
| return make_user_ptr(src).copy_array_from_user(data, len); |
| }; |
| return Write(src, len, actual, copy_from_fn); |
| } |
| |
| zx_status_t FifoDispatcher::ReadToUser(uint8_t* dst, size_t len, uint32_t* actual) { |
| auto copy_to_fn = [](uint8_t* dst, const uint8_t* data, size_t len) -> zx_status_t { |
| return make_user_ptr(dst).copy_array_to_user(data, len); |
| }; |
| return Read(dst, len, actual, copy_to_fn); |
| } |
| |
| zx_status_t FifoDispatcher::Write(const uint8_t* ptr, size_t len, uint32_t* actual, |
| fifo_copy_from_fn_t copy_from_fn) { |
| canary_.Assert(); |
| |
| fbl::RefPtr<FifoDispatcher> other; |
| { |
| AutoLock lock(&lock_); |
| if (!other_) |
| return ZX_ERR_PEER_CLOSED; |
| other = other_; |
| } |
| |
| return other->WriteSelf(ptr, len, actual, copy_from_fn); |
| } |
| |
| zx_status_t FifoDispatcher::WriteSelf(const uint8_t* ptr, size_t bytelen, uint32_t* actual, |
| fifo_copy_from_fn_t copy_from_fn) { |
| canary_.Assert(); |
| |
| size_t count = bytelen / elem_size_; |
| if (count == 0) |
| return ZX_ERR_OUT_OF_RANGE; |
| |
| AutoLock lock(&lock_); |
| |
| uint32_t old_head = head_; |
| |
| // total number of available empty slots in the fifo |
| size_t avail = elem_count_ - (head_ - tail_); |
| |
| if (avail == 0) |
| return ZX_ERR_SHOULD_WAIT; |
| |
| bool was_empty = (avail == elem_count_); |
| |
| if (count > avail) |
| count = avail; |
| |
| while (count > 0) { |
| uint32_t offset = (head_ & mask_); |
| |
| // number of slots from target to end, inclusive |
| uint32_t n = elem_count_ - offset; |
| |
| // number of slots we can actually copy |
| size_t to_copy = (count > n) ? n : count; |
| |
| zx_status_t status = copy_from_fn(ptr, &data_[offset * elem_size_], to_copy * elem_size_); |
| if (status != ZX_OK) { |
| // roll back, in case this is the second copy |
| head_ = old_head; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // adjust head and count |
| // due to size limitations on fifo, to_copy will always fit in a u32 |
| head_ += static_cast<uint32_t>(to_copy); |
| count -= to_copy; |
| ptr += to_copy * elem_size_; |
| } |
| |
| // if was empty, we've become readable |
| if (was_empty) |
| state_tracker_.UpdateState(0u, ZX_FIFO_READABLE); |
| |
| // if now full, we're no longer writable |
| if (elem_count_ == (head_ - tail_)) |
| other_->state_tracker_.UpdateState(ZX_FIFO_WRITABLE, 0u); |
| |
| *actual = (head_ - old_head); |
| return ZX_OK; |
| } |
| |
| zx_status_t FifoDispatcher::Read(uint8_t* ptr, size_t bytelen, uint32_t* actual, |
| fifo_copy_to_fn_t copy_to_fn) { |
| canary_.Assert(); |
| |
| size_t count = bytelen / elem_size_; |
| if (count == 0) |
| return ZX_ERR_OUT_OF_RANGE; |
| |
| AutoLock lock(&lock_); |
| |
| uint32_t old_tail = tail_; |
| |
| // total number of available entries to read from the fifo |
| size_t avail = (head_ - tail_); |
| |
| if (avail == 0) |
| return ZX_ERR_SHOULD_WAIT; |
| |
| bool was_full = (avail == elem_count_); |
| |
| if (count > avail) |
| count = avail; |
| |
| while (count > 0) { |
| uint32_t offset = (tail_ & mask_); |
| |
| // number of slots from target to end, inclusive |
| uint32_t n = elem_count_ - offset; |
| |
| // number of slots we can actually copy |
| size_t to_copy = (count > n) ? n : count; |
| |
| zx_status_t status = copy_to_fn(ptr, &data_[offset * elem_size_], to_copy * elem_size_); |
| if (status != ZX_OK) { |
| // roll back, in case this is the second copy |
| tail_ = old_tail; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // adjust tail and count |
| // due to size limitations on fifo, to_copy will always fit in a u32 |
| tail_ += static_cast<uint32_t>(to_copy); |
| count -= to_copy; |
| ptr += to_copy * elem_size_; |
| |
| } |
| |
| // if we were full, we have become writable |
| if (was_full && other_) |
| other_->state_tracker_.UpdateState(0u, ZX_FIFO_WRITABLE); |
| |
| // if we've become empty, we're no longer readable |
| if ((head_ - tail_) == 0) |
| state_tracker_.UpdateState(ZX_FIFO_READABLE, 0u); |
| |
| *actual = (tail_ - old_tail); |
| return ZX_OK; |
| } |