| // Copyright 2016 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 <align.h> |
| #include <assert.h> |
| #include <lib/console.h> |
| #include <lib/root_resource_filter.h> |
| #include <trace.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <new> |
| |
| #include <arch/x86/interrupts.h> |
| #include <fbl/array.h> |
| #include <kernel/lockdep.h> |
| #include <kernel/spinlock.h> |
| #include <vm/physmap.h> |
| #include <vm/pmm.h> |
| #include <vm/vm_aspace.h> |
| |
| #include "arch/x86/apic.h" |
| |
| #define IO_APIC_IND(base) ((volatile uint32_t*)(((uint8_t*)(base)) + IO_APIC_IOREGSEL)) |
| #define IO_APIC_DAT(base) ((volatile uint32_t*)(((uint8_t*)(base)) + IO_APIC_IOWIN)) |
| #define IO_APIC_EOIR(base) ((volatile uint32_t*)(((uint8_t*)(base)) + 0x40)) |
| // The minimum address space required past the base address |
| #define IO_APIC_WINDOW_SIZE 0x44 |
| // The minimum version that supported the EOIR |
| #define IO_APIC_EOIR_MIN_VERSION 0x20 |
| |
| // IO APIC register offsets |
| #define IO_APIC_REG_RTE(idx) (0x10 + 2 * (idx)) |
| |
| // Macros for extracting data from REG_ID |
| #define IO_APIC_ID_ID(v) (((v) >> 24) & 0xf) |
| // Macros for extracting data from REG_VER |
| #define IO_APIC_VER_MAX_REDIR_ENTRY(v) (((v) >> 16) & 0xff) |
| #define IO_APIC_VER_VERSION(v) ((v)&0xff) |
| // Macros for writing REG_RTE entries |
| #define IO_APIC_RTE_DST(v) (((uint64_t)(v)) << 56) |
| #define IO_APIC_RTE_EXTENDED_DST_ID(v) (((uint64_t)((v)&0xf)) << 48) |
| #define IO_APIC_RTE_MASKED (1ULL << 16) |
| #define IO_APIC_RTE_TRIGGER_MODE(tm) (((uint64_t)(tm)) << 15) |
| #define IO_APIC_RTE_POLARITY(p) (((uint64_t)(p)) << 13) |
| #define IO_APIC_RTE_DST_MODE(dm) (((uint64_t)(dm)) << 11) |
| #define IO_APIC_RTE_DELIVERY_MODE(dm) ((((uint64_t)(dm)) & 0x7) << 8) |
| #define IO_APIC_RTE_VECTOR(x) (((uint64_t)(x)) & 0xff) |
| #define IO_APIC_RTE_MASK IO_APIC_RTE_VECTOR(0xff) |
| // Macros for reading REG_RTE entries |
| #define IO_APIC_RTE_REMOTE_IRR (1ULL << 14) |
| #define IO_APIC_RTE_DELIVERY_STATUS (1ULL << 12) |
| #define IO_APIC_RTE_GET_POLARITY(r) ((enum interrupt_polarity)(((r) >> 13) & 0x1)) |
| #define IO_APIC_RTE_GET_TRIGGER_MODE(r) ((enum interrupt_trigger_mode)(((r) >> 15) & 0x1)) |
| #define IO_APIC_RTE_GET_VECTOR(r) ((uint8_t)((r)&0xFF)) |
| |
| // Technically this can be larger, but the spec as of the 100-Series doesn't |
| // guarantee where the additional redirections will be. |
| #define IO_APIC_NUM_REDIRECTIONS 120 |
| |
| #define LOCAL_TRACE 0 |
| |
| // Struct for tracking all we need to know about each IO APIC |
| struct io_apic { |
| struct io_apic_descriptor desc; |
| |
| // Virtual address of the base of this IOAPIC's MMIO |
| void* vaddr; |
| |
| uint8_t version; |
| // The index of the last redirection entry |
| uint8_t max_redirection_entry; |
| |
| // Pre-allocated space for suspend/resume bookkeeping |
| uint64_t saved_rtes[IO_APIC_NUM_REDIRECTIONS]; |
| }; |
| |
| // This lock guards all access to IO APIC registers |
| DECLARE_SINGLETON_SPINLOCK(io_apic_lock); |
| |
| // General register accessors |
| static inline uint32_t apic_io_read_reg(struct io_apic* io_apic, uint8_t reg) |
| TA_REQ(io_apic_lock::Get()); |
| static inline void apic_io_write_reg(struct io_apic* io_apic, uint8_t reg, uint32_t val) |
| TA_REQ(io_apic_lock::Get()); |
| |
| // Register-specific accessors |
| static uint64_t apic_io_read_redirection_entry(struct io_apic* io_apic, uint32_t global_irq) |
| TA_REQ(io_apic_lock::Get()); |
| static void apic_io_write_redirection_entry(struct io_apic* io_apic, uint32_t global_irq, |
| uint64_t value) TA_REQ(io_apic_lock::Get()); |
| |
| // Utility for finding the right IO APIC for a specific global IRQ, cannot fail |
| static struct io_apic* apic_io_resolve_global_irq(uint32_t irq); |
| // Utility for finding the right IO APIC for a specific global IRQ, can fail |
| static struct io_apic* apic_io_resolve_global_irq_no_panic(uint32_t irq); |
| |
| // Routine to print the ioapic state |
| static void apic_io_debug_nolock() TA_REQ(io_apic_lock::Get()); |
| |
| // Track all IO APICs in the system |
| static fbl::Array<io_apic> io_apics; |
| static size_t num_io_apics; |
| |
| // The first 16 global IRQs are identity mapped to the legacy ISA IRQs unless |
| // we are told otherwise. This tracks the actual mapping. |
| // Read-only after initialization in apic_io_init() |
| static struct io_apic_isa_override isa_overrides[NUM_ISA_IRQS]; |
| |
| void apic_io_init(struct io_apic_descriptor* io_apic_descs, size_t num_io_apic_descs, |
| struct io_apic_isa_override* overrides, size_t num_overrides) { |
| ASSERT(!io_apics); |
| |
| num_io_apics = num_io_apic_descs; |
| { |
| fbl::AllocChecker ac; |
| io_apics.reset(new (&ac) io_apic[num_io_apics], num_io_apics); |
| ASSERT(ac.check()); |
| } |
| for (size_t i = 0; i < num_io_apics; ++i) { |
| io_apics[i].desc = io_apic_descs[i]; |
| } |
| |
| // Allocate windows to their control pages |
| for (size_t i = 0; i < num_io_apics; ++i) { |
| struct io_apic* apic = &io_apics[i]; |
| paddr_t paddr = apic->desc.paddr; |
| void* vaddr = paddr_to_physmap(paddr); |
| paddr_t paddr_page_base = ROUNDDOWN(paddr, PAGE_SIZE); |
| |
| // Make sure that user mode cannot ever gain access to these registers using |
| // zx_vmo_alloc_physical and the root resource. |
| root_resource_filter_add_deny_region(paddr_page_base, PAGE_SIZE, ZX_RSRC_KIND_MMIO); |
| |
| // If the window isn't mapped yet (multiple IO APICs can be in the |
| // same page), map it in. |
| if (vaddr == nullptr) { |
| ASSERT(paddr + IO_APIC_WINDOW_SIZE <= paddr_page_base + PAGE_SIZE); |
| zx_status_t res = VmAspace::kernel_aspace()->AllocPhysical( |
| "ioapic", |
| PAGE_SIZE, // size |
| &vaddr, // requested virtual vaddress |
| PAGE_SIZE_SHIFT, // alignment log2 |
| paddr_page_base, // physical vaddress |
| 0, // vmm flags |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | |
| ARCH_MMU_FLAG_UNCACHED_DEVICE); // arch mmu flags |
| ASSERT(res == ZX_OK); |
| vaddr = (void*)((uintptr_t)vaddr + paddr - paddr_page_base); |
| } |
| |
| // Populate the rest of the descriptor |
| apic->vaddr = vaddr; |
| |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| |
| uint32_t ver = apic_io_read_reg(apic, IO_APIC_REG_VER); |
| apic->version = IO_APIC_VER_VERSION(ver); |
| apic->max_redirection_entry = IO_APIC_VER_MAX_REDIR_ENTRY(ver); |
| LTRACEF("Found an IO APIC at phys %p, virt %p: ver %08x\n", (void*)paddr, vaddr, ver); |
| if (apic->max_redirection_entry > IO_APIC_NUM_REDIRECTIONS - 1) { |
| TRACEF("IO APIC supports more redirections than kernel: %08x\n", ver); |
| apic->max_redirection_entry = IO_APIC_NUM_REDIRECTIONS - 1; |
| } |
| |
| // Cleanout the redirection entries |
| for (unsigned int j = 0; j <= apic->max_redirection_entry; ++j) { |
| apic_io_write_redirection_entry(apic, j + apic->desc.global_irq_base, IO_APIC_RTE_MASKED); |
| } |
| } |
| |
| // Process ISA IRQ overrides |
| for (unsigned int i = 0; i < num_overrides; ++i) { |
| uint8_t isa_irq = overrides[i].isa_irq; |
| ASSERT(isa_irq < NUM_ISA_IRQS); |
| isa_overrides[isa_irq] = overrides[i]; |
| LTRACEF("ISA IRQ override for ISA IRQ %u, mapping to %u\n", isa_irq, overrides[i].global_irq); |
| } |
| } |
| |
| static struct io_apic* apic_io_resolve_global_irq_no_panic(uint32_t irq) { |
| for (size_t i = 0; i < num_io_apics; ++i) { |
| uint32_t start = io_apics[i].desc.global_irq_base; |
| uint32_t end = start + io_apics[i].max_redirection_entry; |
| if (start <= irq && irq <= end) { |
| return &io_apics[i]; |
| } |
| } |
| return nullptr; |
| } |
| |
| static struct io_apic* apic_io_resolve_global_irq(uint32_t irq) { |
| struct io_apic* res = apic_io_resolve_global_irq_no_panic(irq); |
| if (res) { |
| return res; |
| } |
| // Treat this as fatal, since dealing with an unmapped IRQ is a bug. |
| panic("Could not resolve global IRQ: %u\n", irq); |
| } |
| |
| static inline uint32_t apic_io_read_reg(struct io_apic* io_apic, uint8_t reg) { |
| ASSERT(io_apic != nullptr); |
| io_apic_lock::Get()->lock().AssertHeld(); |
| *IO_APIC_IND(io_apic->vaddr) = reg; |
| uint32_t val = *IO_APIC_DAT(io_apic->vaddr); |
| return val; |
| } |
| |
| static inline void apic_io_write_reg(struct io_apic* io_apic, uint8_t reg, uint32_t val) { |
| ASSERT(io_apic != nullptr); |
| io_apic_lock::Get()->lock().AssertHeld(); |
| *IO_APIC_IND(io_apic->vaddr) = reg; |
| *IO_APIC_DAT(io_apic->vaddr) = val; |
| } |
| |
| static uint64_t apic_io_read_redirection_entry(struct io_apic* io_apic, uint32_t global_irq) { |
| io_apic_lock::Get()->lock().AssertHeld(); |
| |
| ASSERT(global_irq >= io_apic->desc.global_irq_base); |
| uint32_t offset = global_irq - io_apic->desc.global_irq_base; |
| ASSERT(offset <= io_apic->max_redirection_entry); |
| |
| uint8_t reg_id = (uint8_t)IO_APIC_REG_RTE(offset); |
| uint64_t result = 0; |
| result |= apic_io_read_reg(io_apic, reg_id); |
| result |= ((uint64_t)apic_io_read_reg(io_apic, (uint8_t)(reg_id + 1))) << 32; |
| return result; |
| } |
| |
| static void apic_io_write_redirection_entry(struct io_apic* io_apic, uint32_t global_irq, |
| uint64_t value) { |
| io_apic_lock::Get()->lock().AssertHeld(); |
| |
| ASSERT(global_irq >= io_apic->desc.global_irq_base); |
| uint32_t offset = global_irq - io_apic->desc.global_irq_base; |
| ASSERT(offset <= io_apic->max_redirection_entry); |
| |
| uint8_t reg_id = (uint8_t)IO_APIC_REG_RTE(offset); |
| apic_io_write_reg(io_apic, reg_id, (uint32_t)value); |
| apic_io_write_reg(io_apic, (uint8_t)(reg_id + 1), (uint32_t)(value >> 32)); |
| } |
| |
| bool apic_io_is_valid_irq(uint32_t global_irq) { |
| return apic_io_resolve_global_irq_no_panic(global_irq) != nullptr; |
| } |
| |
| /* |
| * To correctly use this function, we need to do some work first. |
| * 1) We need to check for EOI-broadcast suppression support in the local APIC |
| * version register. |
| * 2) We need to check that the IOAPIC is new enough to support the EOI |
| * 3) We need to enable suppression in the spurious interrupt register. |
| * 4) Call this function after calling apic_issue_eoi() (or maybe modify |
| * apic_issue_eoi() to call this automatically). |
| * |
| * In the mean time, IO APIC EOIs are automatically issued via broadcast to |
| * all IO APICs whenever the local APIC receives an EOI for a level-triggered |
| * interrupt. |
| */ |
| void apic_io_issue_eoi(uint32_t global_irq, uint8_t vec) { |
| struct io_apic* io_apic = apic_io_resolve_global_irq(global_irq); |
| |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| |
| ASSERT(io_apic->version >= IO_APIC_EOIR_MIN_VERSION); |
| *IO_APIC_EOIR(io_apic->vaddr) = vec; |
| } |
| |
| void apic_io_mask_irq(uint32_t global_irq, bool mask) { |
| struct io_apic* io_apic = apic_io_resolve_global_irq(global_irq); |
| |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| |
| uint64_t reg = apic_io_read_redirection_entry(io_apic, global_irq); |
| if (mask) { |
| reg |= IO_APIC_RTE_MASKED; |
| } else { |
| /* If we are unmasking, we had better have been assigned a valid vector */ |
| DEBUG_ASSERT_MSG((IO_APIC_RTE_GET_VECTOR(reg) >= X86_INT_PLATFORM_BASE) && |
| (IO_APIC_RTE_GET_VECTOR(reg) <= X86_INT_PLATFORM_MAX), |
| "reg = %#lx RTE_GET_VECTOR(reg) = %u", reg, IO_APIC_RTE_GET_VECTOR(reg)); |
| reg &= ~IO_APIC_RTE_MASKED; |
| } |
| apic_io_write_redirection_entry(io_apic, global_irq, reg); |
| } |
| |
| void apic_io_configure_irq(uint32_t global_irq, enum interrupt_trigger_mode trig_mode, |
| enum interrupt_polarity polarity, |
| enum apic_interrupt_delivery_mode del_mode, bool mask, |
| enum apic_interrupt_dst_mode dst_mode, uint8_t dst, uint8_t vector) { |
| struct io_apic* io_apic = apic_io_resolve_global_irq(global_irq); |
| |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| |
| /* If we are configuring an invalid vector, for the IRQ to be masked. */ |
| if ((del_mode == DELIVERY_MODE_FIXED || del_mode == DELIVERY_MODE_LOWEST_PRI) && |
| ((vector < X86_INT_PLATFORM_BASE) || (vector > X86_INT_PLATFORM_MAX))) { |
| mask = true; |
| } |
| |
| uint64_t reg = 0; |
| reg |= IO_APIC_RTE_TRIGGER_MODE(trig_mode); |
| reg |= IO_APIC_RTE_POLARITY(polarity); |
| reg |= IO_APIC_RTE_DELIVERY_MODE(del_mode); |
| reg |= IO_APIC_RTE_DST_MODE(dst_mode); |
| reg |= IO_APIC_RTE_DST(dst); |
| reg |= IO_APIC_RTE_VECTOR(vector); |
| if (mask) { |
| reg |= IO_APIC_RTE_MASKED; |
| } |
| apic_io_write_redirection_entry(io_apic, global_irq, reg); |
| } |
| |
| zx_status_t apic_io_fetch_irq_config(uint32_t global_irq, enum interrupt_trigger_mode* trig_mode, |
| enum interrupt_polarity* polarity) { |
| struct io_apic* io_apic = apic_io_resolve_global_irq(global_irq); |
| |
| if (!io_apic) |
| return ZX_ERR_INVALID_ARGS; |
| |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| |
| uint64_t reg = apic_io_read_redirection_entry(io_apic, global_irq); |
| if (trig_mode) |
| *trig_mode = IO_APIC_RTE_GET_TRIGGER_MODE(reg); |
| if (polarity) |
| *polarity = IO_APIC_RTE_GET_POLARITY(reg); |
| |
| return ZX_OK; |
| } |
| |
| void apic_io_configure_irq_vector(uint32_t global_irq, uint8_t vector) { |
| struct io_apic* io_apic = apic_io_resolve_global_irq(global_irq); |
| |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| |
| uint64_t reg = apic_io_read_redirection_entry(io_apic, global_irq); |
| |
| /* If we are configuring an invalid vector, automatically mask the IRQ. */ |
| if ((IO_APIC_RTE_GET_VECTOR(reg) < X86_INT_PLATFORM_BASE) || |
| (IO_APIC_RTE_GET_VECTOR(reg) > X86_INT_PLATFORM_MAX)) { |
| reg |= IO_APIC_RTE_MASKED; |
| } |
| |
| reg &= ~IO_APIC_RTE_MASK; |
| reg |= IO_APIC_RTE_VECTOR(vector); |
| apic_io_write_redirection_entry(io_apic, global_irq, reg); |
| } |
| |
| uint8_t apic_io_fetch_irq_vector(uint32_t global_irq) { |
| struct io_apic* io_apic = apic_io_resolve_global_irq(global_irq); |
| |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| |
| uint64_t reg = apic_io_read_redirection_entry(io_apic, global_irq); |
| uint8_t vector = IO_APIC_RTE_GET_VECTOR(reg); |
| |
| return vector; |
| } |
| |
| void apic_io_mask_isa_irq(uint8_t isa_irq, bool mask) { |
| ASSERT(isa_irq < NUM_ISA_IRQS); |
| uint32_t global_irq = isa_irq; |
| if (isa_overrides[isa_irq].remapped) { |
| global_irq = isa_overrides[isa_irq].global_irq; |
| } |
| apic_io_mask_irq(global_irq, mask); |
| } |
| |
| void apic_io_configure_isa_irq(uint8_t isa_irq, enum apic_interrupt_delivery_mode del_mode, |
| bool mask, enum apic_interrupt_dst_mode dst_mode, uint8_t dst, |
| uint8_t vector) { |
| ASSERT(isa_irq < NUM_ISA_IRQS); |
| uint32_t global_irq = isa_irq; |
| enum interrupt_trigger_mode trig_mode = IRQ_TRIGGER_MODE_EDGE; |
| enum interrupt_polarity polarity = IRQ_POLARITY_ACTIVE_HIGH; |
| if (isa_overrides[isa_irq].remapped) { |
| global_irq = isa_overrides[isa_irq].global_irq; |
| trig_mode = isa_overrides[isa_irq].tm; |
| polarity = isa_overrides[isa_irq].pol; |
| } |
| |
| apic_io_configure_irq(global_irq, trig_mode, polarity, del_mode, mask, dst_mode, dst, vector); |
| } |
| |
| // Convert a legacy ISA IRQ number into a global IRQ number |
| uint32_t apic_io_isa_to_global(uint8_t isa_irq) { |
| // It is a programming bug for this to be invoked with an invalid value. |
| ASSERT(isa_irq < NUM_ISA_IRQS); |
| if (isa_overrides[isa_irq].remapped) { |
| return isa_overrides[isa_irq].global_irq; |
| } |
| return isa_irq; |
| } |
| |
| void apic_io_save(void) { |
| DEBUG_ASSERT(arch_ints_disabled()); |
| Guard<SpinLock, NoIrqSave> guard{io_apic_lock::Get()}; |
| for (size_t i = 0; i < num_io_apics; ++i) { |
| struct io_apic* apic = &io_apics[i]; |
| for (uint8_t j = 0; j <= apic->max_redirection_entry; ++j) { |
| uint32_t global_irq = apic->desc.global_irq_base + j; |
| uint64_t reg = apic_io_read_redirection_entry(apic, global_irq); |
| apic->saved_rtes[j] = reg; |
| } |
| } |
| } |
| |
| void apic_io_restore(void) { |
| DEBUG_ASSERT(arch_ints_disabled()); |
| Guard<SpinLock, NoIrqSave> guard{io_apic_lock::Get()}; |
| for (size_t i = 0; i < num_io_apics; ++i) { |
| struct io_apic* apic = &io_apics[i]; |
| for (uint8_t j = 0; j <= apic->max_redirection_entry; ++j) { |
| uint32_t global_irq = apic->desc.global_irq_base + j; |
| apic_io_write_redirection_entry(apic, global_irq, apic->saved_rtes[j]); |
| } |
| } |
| } |
| |
| GsiRange apic_io_get_gsi_range() { |
| ZX_ASSERT(num_io_apics); |
| Guard<SpinLock, NoIrqSave> guard{io_apic_lock::Get()}; |
| GsiRange range{.start = std::numeric_limits<uint32_t>::max(), .end = 0}; |
| |
| // If we could be certain the MADT tables were always in increasing order this |
| // could be a constant time operation, but since no such guarantee exists we |
| // need to walk the descriptors. |
| for (const io_apic& io_apic : io_apics) { |
| range.start = std::min(range.start, io_apic.desc.global_irq_base); |
| // max_redirection_entry is the offset of the last entry, not the number of entries. |
| range.end = |
| std::max(range.end, io_apic.desc.global_irq_base + io_apic.max_redirection_entry + 1u); |
| } |
| |
| return range; |
| } |
| |
| static void apic_io_debug_nolock(void) { |
| for (size_t i = 0; i < num_io_apics; ++i) { |
| struct io_apic* apic = &io_apics[i]; |
| printf("IO APIC idx %lu:\n", i); |
| printf(" id: %08x\n", apic->desc.apic_id); |
| printf(" version: %08hhx\n", apic->version); |
| printf(" entries: %08x\n", apic->max_redirection_entry + 1U); |
| for (uint8_t j = 0; j <= apic->max_redirection_entry; ++j) { |
| uint32_t global_irq = apic->desc.global_irq_base + j; |
| uint64_t reg = apic_io_read_redirection_entry(apic, global_irq); |
| printf(" %4u: dst: %s %02hhx, %s, %s, %s, dm %hhx, vec %2hhx, %s %s\n", global_irq, |
| (reg & (1 << 11)) ? "l" : "p", (uint8_t)(reg >> 56), |
| (reg & IO_APIC_RTE_MASKED) ? "masked" : "unmasked", |
| IO_APIC_RTE_GET_TRIGGER_MODE(reg) ? "level" : "edge", |
| IO_APIC_RTE_GET_POLARITY(reg) ? "low" : "high", (uint8_t)((reg >> 8) & 0x7), |
| (uint8_t)reg, (reg & (1 << 12)) ? "pending" : "", (reg & (1 << 14)) ? "RIRR" : ""); |
| } |
| } |
| printf("ISA Overrides:\n"); |
| for (const auto& o : isa_overrides) { |
| if (o.remapped) { |
| printf(" isa_irq %u global_irq %u\n", o.isa_irq, o.global_irq); |
| } |
| } |
| } |
| |
| void apic_io_debug(void) { |
| Guard<SpinLock, IrqSave> guard{io_apic_lock::Get()}; |
| apic_io_debug_nolock(); |
| } |
| |
| static int cmd_ioapic(int argc, const cmd_args* argv, uint32_t flags) { |
| apic_io_debug(); |
| return true; |
| } |
| |
| STATIC_COMMAND_START |
| STATIC_COMMAND("ioapic", "Print IO APIC descriptor information", &cmd_ioapic) |
| STATIC_COMMAND_END(ioapic) |