| // 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 <hypervisor/packet_mux.h> |
| |
| #include <zircon/syscalls/hypervisor.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| |
| __UNUSED static const size_t kMaxPacketsPerRange = 256; |
| |
| BlockingPortAllocator::BlockingPortAllocator() : semaphore_(kMaxPacketsPerRange) {} |
| |
| zx_status_t BlockingPortAllocator::Init() { |
| return arena_.Init("hypervisor-packets", kMaxPacketsPerRange); |
| } |
| |
| PortPacket* BlockingPortAllocator::Alloc(StateReloader* reloader) { |
| PortPacket* port_packet = Alloc(); |
| zx_status_t status = semaphore_.Wait(INFINITE_TIME); |
| // If port_packet is NULL, then Wait would have blocked. So we need to: |
| // reload our state, check the status of Wait, and Alloc again. |
| if (port_packet == nullptr) { |
| reloader->Reload(); |
| if (status != ZX_OK) |
| return nullptr; |
| port_packet = Alloc(); |
| } |
| return port_packet; |
| } |
| |
| PortPacket* BlockingPortAllocator::Alloc() { |
| return arena_.New(nullptr, this); |
| } |
| |
| void BlockingPortAllocator::Free(PortPacket* port_packet) { |
| arena_.Delete(port_packet); |
| if (semaphore_.Post() > 0) |
| thread_reschedule(); |
| } |
| |
| PortRange::PortRange(zx_vaddr_t addr, size_t len, fbl::RefPtr<PortDispatcher> port, uint64_t key) |
| : addr_(addr), len_(len), port_(fbl::move(port)), key_(key) { |
| (void) key_; |
| } |
| |
| zx_status_t PortRange::Init() { |
| return port_allocator_.Init(); |
| } |
| |
| zx_status_t PortRange::Queue(const zx_port_packet_t& packet, StateReloader* reloader) { |
| PortPacket* port_packet = port_allocator_.Alloc(reloader); |
| if (port_packet == nullptr) |
| return ZX_ERR_NO_MEMORY; |
| port_packet->packet = packet; |
| port_packet->packet.key = key_; |
| port_packet->packet.type |= PKT_FLAG_EPHEMERAL; |
| zx_status_t status = port_->Queue(port_packet, ZX_SIGNAL_NONE, 0); |
| if (status != ZX_OK) |
| port_allocator_.Free(port_packet); |
| return status; |
| } |
| |
| zx_status_t PacketMux::AddPortRange(zx_vaddr_t addr, size_t len, |
| fbl::RefPtr<PortDispatcher> port, uint64_t key) { |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<PortRange> range(new (&ac) PortRange(addr, len, fbl::move(port), key)); |
| if (!ac.check()) |
| return ZX_ERR_NO_MEMORY; |
| zx_status_t status = range->Init(); |
| if (status != ZX_OK) |
| return status; |
| { |
| fbl::AutoLock lock(&mutex_); |
| ports_.insert(fbl::move(range)); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t PacketMux::FindPortRange(zx_vaddr_t addr, PortRange** port_range) { |
| PortTree::iterator iter; |
| { |
| fbl::AutoLock lock(&mutex_); |
| iter = ports_.upper_bound(addr); |
| } |
| --iter; |
| if (!iter.IsValid() || !iter->InRange(addr)) |
| return ZX_ERR_NOT_FOUND; |
| *port_range = const_cast<PortRange*>(&*iter); |
| return ZX_OK; |
| } |
| |
| zx_status_t PacketMux::Queue(zx_vaddr_t addr, const zx_port_packet_t& packet, |
| StateReloader* reloader) { |
| PortRange* port_range; |
| zx_status_t status = FindPortRange(addr, &port_range); |
| if (status != ZX_OK) |
| return status; |
| return port_range->Queue(packet, reloader); |
| } |