| // 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 <lib/counters.h> |
| #include <string.h> |
| #include <zircon/rights.h> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <object/handle.h> |
| |
| KCOUNTER(dispatcher_fifo_create_count, "dispatcher.fifo.create") |
| KCOUNTER(dispatcher_fifo_destroy_count, "dispatcher.fifo.destroy") |
| |
| // static |
| zx_status_t FifoDispatcher::Create(size_t count, size_t elemsize, uint32_t options, |
| KernelHandle<FifoDispatcher>* handle0, |
| KernelHandle<FifoDispatcher>* handle1, zx_rights_t* rights) { |
| // count and elemsize must be nonzero |
| // total size must be <= kMaxSizeBytes |
| if (!count || !elemsize || (count > kMaxSizeBytes) || (elemsize > kMaxSizeBytes) || |
| ((count * elemsize) > kMaxSizeBytes)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| fbl::AllocChecker ac; |
| auto holder0 = fbl::AdoptRef(new (&ac) PeerHolder<FifoDispatcher>()); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| auto holder1 = holder0; |
| |
| auto data0 = ktl::unique_ptr<uint8_t[]>(new (&ac) uint8_t[count * elemsize]); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| KernelHandle fifo0(fbl::AdoptRef( |
| new (&ac) FifoDispatcher(ktl::move(holder0), options, static_cast<uint32_t>(count), |
| static_cast<uint32_t>(elemsize), ktl::move(data0)))); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| auto data1 = ktl::unique_ptr<uint8_t[]>(new (&ac) uint8_t[count * elemsize]); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| KernelHandle fifo1(fbl::AdoptRef( |
| new (&ac) FifoDispatcher(ktl::move(holder1), options, static_cast<uint32_t>(count), |
| static_cast<uint32_t>(elemsize), ktl::move(data1)))); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| |
| fifo0.dispatcher()->Init(fifo1.dispatcher()); |
| fifo1.dispatcher()->Init(fifo0.dispatcher()); |
| |
| *rights = default_rights(); |
| *handle0 = ktl::move(fifo0); |
| *handle1 = ktl::move(fifo1); |
| return ZX_OK; |
| } |
| |
| FifoDispatcher::FifoDispatcher(fbl::RefPtr<PeerHolder<FifoDispatcher>> holder, uint32_t /*options*/, |
| uint32_t count, uint32_t elem_size, ktl::unique_ptr<uint8_t[]> data) |
| : PeeredDispatcher(ktl::move(holder), ZX_FIFO_WRITABLE), |
| elem_count_(count), |
| elem_size_(elem_size), |
| head_(0u), |
| tail_(0u), |
| data_(ktl::move(data)) { |
| kcounter_add(dispatcher_fifo_create_count, 1); |
| } |
| |
| FifoDispatcher::~FifoDispatcher() { kcounter_add(dispatcher_fifo_destroy_count, 1); } |
| |
| // 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 { |
| peer_ = ktl::move(other); |
| peer_koid_ = peer_->get_koid(); |
| } |
| |
| zx_status_t FifoDispatcher::UserSignalSelfLocked(uint32_t clear_mask, uint32_t set_mask) { |
| canary_.Assert(); |
| UpdateStateLocked(clear_mask, set_mask); |
| return ZX_OK; |
| } |
| |
| void FifoDispatcher::on_zero_handles_locked() { canary_.Assert(); } |
| |
| void FifoDispatcher::OnPeerZeroHandlesLocked() { |
| canary_.Assert(); |
| |
| UpdateStateLocked(ZX_FIFO_WRITABLE, ZX_FIFO_PEER_CLOSED); |
| } |
| |
| zx_status_t FifoDispatcher::WriteFromUser(size_t elem_size, user_in_ptr<const uint8_t> ptr, |
| size_t count, |
| size_t* actual) TA_NO_THREAD_SAFETY_ANALYSIS { |
| canary_.Assert(); |
| |
| Guard<Mutex> guard{get_lock()}; |
| if (!peer_) |
| return ZX_ERR_PEER_CLOSED; |
| return peer_->WriteSelfLocked(elem_size, ptr, count, actual); |
| } |
| |
| zx_status_t FifoDispatcher::WriteSelfLocked(size_t elem_size, user_in_ptr<const uint8_t> ptr, |
| size_t count, |
| size_t* actual) TA_NO_THREAD_SAFETY_ANALYSIS { |
| canary_.Assert(); |
| |
| if (elem_size != elem_size_) |
| return ZX_ERR_OUT_OF_RANGE; |
| if (count == 0) |
| return ZX_ERR_OUT_OF_RANGE; |
| |
| 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_ % elem_count_); |
| |
| // 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 = |
| ptr.copy_array_from_user(&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 = ptr.byte_offset(to_copy * elem_size_); |
| } |
| |
| // if was empty, we've become readable |
| if (was_empty) |
| UpdateStateLocked(0u, ZX_FIFO_READABLE); |
| |
| // if now full, we're no longer writable |
| if (elem_count_ == (head_ - tail_)) |
| peer_->UpdateStateLocked(ZX_FIFO_WRITABLE, 0u); |
| |
| *actual = (head_ - old_head); |
| return ZX_OK; |
| } |
| |
| zx_status_t FifoDispatcher::ReadToUser(size_t elem_size, user_out_ptr<uint8_t> ptr, size_t count, |
| size_t* actual) TA_NO_THREAD_SAFETY_ANALYSIS { |
| canary_.Assert(); |
| |
| if (elem_size != elem_size_) |
| return ZX_ERR_OUT_OF_RANGE; |
| if (count == 0) |
| return ZX_ERR_OUT_OF_RANGE; |
| |
| Guard<Mutex> guard{get_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 peer_ ? ZX_ERR_SHOULD_WAIT : ZX_ERR_PEER_CLOSED; |
| |
| bool was_full = (avail == elem_count_); |
| |
| if (count > avail) |
| count = avail; |
| |
| while (count > 0) { |
| uint32_t offset = (tail_ % elem_count_); |
| |
| // 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 = ptr.copy_array_to_user(&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 = ptr.byte_offset(to_copy * elem_size_); |
| } |
| |
| // if we were full, we have become writable |
| if (was_full && peer_) |
| peer_->UpdateStateLocked(0u, ZX_FIFO_WRITABLE); |
| |
| // if we've become empty, we're no longer readable |
| if ((head_ - tail_) == 0) |
| UpdateStateLocked(ZX_FIFO_READABLE, 0u); |
| |
| *actual = (tail_ - old_tail); |
| return ZX_OK; |
| } |