| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string.h> |
| |
| #include <hypervisor/address.h> |
| #include <hypervisor/bits.h> |
| #include <hypervisor/io_apic.h> |
| #include <hypervisor/vcpu.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/hypervisor.h> |
| |
| // clang-format off |
| |
| /* IO APIC register addresses. */ |
| #define IO_APIC_IOREGSEL 0x00 |
| #define IO_APIC_IOWIN 0x10 |
| |
| /* IO APIC register addresses. */ |
| #define IO_APIC_REGISTER_ID 0x00 |
| #define IO_APIC_REGISTER_VER 0x01 |
| #define IO_APIC_REGISTER_ARBITRATION 0x02 |
| |
| /* IO APIC configuration constants. */ |
| #define IO_APIC_VERSION 0x11 |
| #define FIRST_REDIRECT_OFFSET 0x10 |
| #define LAST_REDIRECT_OFFSET (FIRST_REDIRECT_OFFSET + IO_APIC_REDIRECT_OFFSETS - 1) |
| |
| /* DESTMOD register. */ |
| #define IO_APIC_DESTMOD_PHYSICAL 0x00 |
| #define IO_APIC_DESTMOD_LOGICAL 0x01 |
| |
| #define LOCAL_APIC_DFR_FLAT_MODEL 0xf |
| |
| // clang-format on |
| |
| void io_apic_init(io_apic_t* io_apic) { |
| memset(io_apic, 0, sizeof(*io_apic)); |
| } |
| |
| zx_status_t io_apic_register_local_apic(io_apic_t* io_apic, uint8_t local_apic_id, |
| local_apic_t* local_apic) { |
| if (local_apic_id >= IO_APIC_MAX_LOCAL_APICS) |
| return ZX_ERR_OUT_OF_RANGE; |
| if (io_apic->local_apic[local_apic_id] != NULL) |
| return ZX_ERR_ALREADY_EXISTS; |
| |
| local_apic->regs->id.u32 = local_apic_id; |
| io_apic->local_apic[local_apic_id] = local_apic; |
| return ZX_OK; |
| } |
| |
| zx_status_t io_apic_redirect(const io_apic_t* io_apic, uint32_t global_irq, uint8_t* out_vector, |
| zx_handle_t* out_vcpu) { |
| if (global_irq >= IO_APIC_REDIRECTS) |
| return ZX_ERR_OUT_OF_RANGE; |
| |
| mtx_lock((mtx_t*)&io_apic->mutex); |
| uint32_t lower = io_apic->redirect[global_irq * 2]; |
| uint32_t upper = io_apic->redirect[global_irq * 2 + 1]; |
| mtx_unlock((mtx_t*)&io_apic->mutex); |
| |
| uint32_t vector = bits_shift(lower, 7, 0); |
| |
| // The "destination mode" (DESTMOD) determines how the dest field in the |
| // redirection entry should be interpreted. |
| // |
| // With a 'physical' mode, the destination is interpreted as the APIC ID |
| // of the target APIC to receive the interrupt. |
| // |
| // With a 'logical' mode, the target depends on the 'logical destination |
| // register' and the 'destination format register' in the connected local |
| // APICs. |
| // |
| // See 82093AA (IOAPIC) Section 2.3.4. |
| // See Intel Volume 3, Section 10.6.2. |
| uint8_t destmod = BIT_SHIFT(lower, 11); |
| if (destmod == IO_APIC_DESTMOD_PHYSICAL) { |
| uint32_t dest = bits_shift(upper, 27, 24); |
| local_apic_t* apic = dest < IO_APIC_MAX_LOCAL_APICS ? io_apic->local_apic[dest] : NULL; |
| if (apic == NULL) |
| return ZX_ERR_NOT_FOUND; |
| *out_vector = static_cast<uint8_t>(vector); |
| *out_vcpu = apic->vcpu; |
| return ZX_OK; |
| } |
| |
| // Logical DESTMOD. |
| uint32_t dest = bits_shift(upper, 31, 24); |
| for (uint8_t local_apic_id = 0; local_apic_id < IO_APIC_MAX_LOCAL_APICS; ++local_apic_id) { |
| local_apic_t* local_apic = io_apic->local_apic[local_apic_id]; |
| if (local_apic == NULL) |
| continue; |
| |
| // Intel Volume 3, Section 10.6.2.2: Each local APIC performs a |
| // bit-wise AND of the MDA and its logical APIC ID. |
| uint32_t logical_apic_id = bits_shift(local_apic->regs->ldr.u32, 31, 24); |
| if (!(logical_apic_id & dest)) |
| continue; |
| |
| // There also exists a 'cluster' model that is not implemented. |
| uint32_t model = bits_shift(local_apic->regs->dfr.u32, 31, 28); |
| if (model != LOCAL_APIC_DFR_FLAT_MODEL) { |
| fprintf(stderr, "APIC only supports the flat model.\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // Note we're not currently respecting the DELMODE field and |
| // instead are only delivering to the fist local APIC that is |
| // targeted. |
| *out_vector = static_cast<uint8_t>(vector); |
| *out_vcpu = local_apic->vcpu; |
| return ZX_OK; |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t io_apic_interrupt(const io_apic_t* io_apic, uint32_t global_irq) { |
| uint8_t vector; |
| zx_handle_t vcpu; |
| zx_status_t status = io_apic_redirect(io_apic, global_irq, &vector, &vcpu); |
| if (status != ZX_OK) |
| return status; |
| return zx_vcpu_interrupt(vcpu, vector); |
| } |
| |
| static zx_status_t io_apic_register_handler(io_apic_t* io_apic, const instruction_t* inst) { |
| switch (io_apic->select) { |
| case IO_APIC_REGISTER_ID: |
| return inst_rw32(inst, &io_apic->id); |
| case IO_APIC_REGISTER_VER: |
| // There are two redirect offsets per redirection entry. We return |
| // the maximum redirection entry index. |
| // |
| // From Intel 82093AA, Section 3.2.2. |
| return inst_read32(inst, (IO_APIC_REDIRECT_OFFSETS / 2 - 1) << 16 | IO_APIC_VERSION); |
| case IO_APIC_REGISTER_ARBITRATION: |
| // Since we have a single I/O APIC, it is always the winner |
| // of arbitration and its arbitration register is always 0. |
| return inst_read32(inst, 0); |
| case FIRST_REDIRECT_OFFSET ... LAST_REDIRECT_OFFSET: { |
| uint32_t i = io_apic->select - FIRST_REDIRECT_OFFSET; |
| return inst_rw32(inst, io_apic->redirect + i); |
| } |
| default: |
| fprintf(stderr, "Unhandled IO APIC register %#x\n", io_apic->select); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t io_apic_handler(io_apic_t* io_apic, const zx_packet_guest_mem_t* mem, |
| const instruction_t* inst) { |
| ZX_ASSERT(mem->addr >= IO_APIC_PHYS_BASE); |
| zx_vaddr_t offset = mem->addr - IO_APIC_PHYS_BASE; |
| |
| switch (offset) { |
| case IO_APIC_IOREGSEL: { |
| uint32_t select; |
| zx_status_t status = inst_write32(inst, &select); |
| if (status != ZX_OK) |
| return status; |
| mtx_lock((mtx_t*)&io_apic->mutex); |
| io_apic->select = select; |
| mtx_unlock((mtx_t*)&io_apic->mutex); |
| return select > UINT8_MAX ? ZX_ERR_INVALID_ARGS : ZX_OK; |
| } |
| case IO_APIC_IOWIN: { |
| mtx_lock((mtx_t*)&io_apic->mutex); |
| zx_status_t status = io_apic_register_handler(io_apic, inst); |
| mtx_unlock((mtx_t*)&io_apic->mutex); |
| return status; |
| } |
| default: |
| fprintf(stderr, "Unhandled IO APIC address %#lx\n", offset); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |