| // 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 <lib/ktrace.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls/hypervisor.h> |
| #include <zircon/types.h> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <hypervisor/ktrace.h> |
| #include <hypervisor/trap_map.h> |
| #include <kernel/range_check.h> |
| |
| namespace { |
| |
| constexpr size_t kMaxPacketsPerRange = 256; |
| |
| bool ValidRange(uint32_t kind, zx_gpaddr_t addr, size_t len) { |
| if (len == 0) { |
| return false; |
| } |
| zx_gpaddr_t end; |
| if (add_overflow(addr, len, &end)) { |
| return false; |
| } |
| #ifdef ARCH_X86 |
| if (kind == ZX_GUEST_TRAP_IO && end > UINT16_MAX) { |
| return false; |
| } |
| #endif // ARCH_X86 |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace hypervisor { |
| |
| BlockingPortAllocator::BlockingPortAllocator() : semaphore_(kMaxPacketsPerRange) {} |
| |
| zx::status<> BlockingPortAllocator::Init() { |
| zx_status_t status = arena_.Init("hypervisor-packets", kMaxPacketsPerRange); |
| return zx::make_status(status); |
| } |
| |
| PortPacket* BlockingPortAllocator::AllocBlocking() { |
| ktrace_vcpu(TAG_VCPU_BLOCK, VCPU_PORT); |
| zx_status_t status = semaphore_.Wait(Deadline::infinite()); |
| ktrace_vcpu(TAG_VCPU_UNBLOCK, VCPU_PORT); |
| if (status != ZX_OK) { |
| return nullptr; |
| } |
| return Alloc(); |
| } |
| |
| PortPacket* BlockingPortAllocator::Alloc() { |
| return arena_.New(this /* handle */, this /* allocator */); |
| } |
| |
| void BlockingPortAllocator::Free(PortPacket* port_packet) { |
| arena_.Delete(port_packet); |
| semaphore_.Post(); |
| } |
| |
| Trap::Trap(uint32_t kind, zx_gpaddr_t addr, size_t len, fbl::RefPtr<PortDispatcher> port, |
| uint64_t key) |
| : kind_(kind), addr_(addr), len_(len), port_(ktl::move(port)), key_(key) {} |
| |
| Trap::~Trap() { |
| if (port_ == nullptr) { |
| return; |
| } |
| port_->CancelQueued(&port_allocator_ /* handle */, key_); |
| } |
| |
| zx::status<> Trap::Init() { return port_allocator_.Init(); } |
| |
| zx::status<> Trap::Queue(const zx_port_packet_t& packet, StateInvalidator* invalidator) { |
| if (invalidator != nullptr) { |
| invalidator->Invalidate(); |
| } |
| if (port_ == nullptr) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| PortPacket* port_packet = port_allocator_.AllocBlocking(); |
| if (port_packet == nullptr) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| port_packet->packet = packet; |
| zx_status_t status = port_->Queue(port_packet, ZX_SIGNAL_NONE); |
| if (status != ZX_OK) { |
| port_allocator_.Free(port_packet); |
| if (status == ZX_ERR_BAD_HANDLE) { |
| // If the last handle to the port has been closed, then we're in a bad state. |
| status = ZX_ERR_BAD_STATE; |
| } |
| } |
| return zx::make_status(status); |
| } |
| |
| zx::status<> TrapMap::InsertTrap(uint32_t kind, zx_gpaddr_t addr, size_t len, |
| fbl::RefPtr<PortDispatcher> port, uint64_t key) { |
| if (!ValidRange(kind, addr, len)) { |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| TrapTree* traps = TreeOf(kind); |
| if (traps == nullptr) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<Trap> range(new (&ac) Trap(kind, addr, len, ktl::move(port), key)); |
| if (!ac.check()) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| if (auto result = range->Init(); result.is_error()) { |
| return result.take_error(); |
| } |
| |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| auto iter = traps->upper_bound(addr); |
| // If `upper_bound()` does not return `end()`, check if the range intersects. |
| if (iter.IsValid() && Intersects(addr, len, iter->addr(), iter->len())) { |
| return zx::error(ZX_ERR_ALREADY_EXISTS); |
| } |
| // Decrement the iterator, and check if the next range intersects. |
| if (--iter; iter.IsValid() && Intersects(addr, len, iter->addr(), iter->len())) { |
| return zx::error(ZX_ERR_ALREADY_EXISTS); |
| } |
| traps->insert(ktl::move(range)); |
| return zx::ok(); |
| } |
| |
| zx::status<Trap*> TrapMap::FindTrap(uint32_t kind, zx_gpaddr_t addr) { |
| TrapTree* traps = TreeOf(kind); |
| if (traps == nullptr) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| Trap* found; |
| { |
| Guard<SpinLock, IrqSave> guard{&lock_}; |
| auto iter = --traps->upper_bound(addr); |
| if (!iter.IsValid()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| found = &*iter; |
| } |
| if (!found->Contains(addr)) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return zx::ok(found); |
| } |
| |
| TrapMap::TrapTree* TrapMap::TreeOf(uint32_t kind) { |
| switch (kind) { |
| case ZX_GUEST_TRAP_BELL: |
| case ZX_GUEST_TRAP_MEM: |
| return &mem_traps_; |
| #ifdef ARCH_X86 |
| case ZX_GUEST_TRAP_IO: |
| return &io_traps_; |
| #endif // ARCH_X86 |
| default: |
| return nullptr; |
| } |
| } |
| |
| } // namespace hypervisor |