blob: f49d677ea35c6ed9edd4ff63e851c02d14aa89e3 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2009 Corey Tabaka
// Copyright (c) 2015 Intel Corporation
//
// 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 <assert.h>
#include <debug.h>
#include <sys/types.h>
#include <trace.h>
#include <zircon/types.h>
#include <arch/regs.h>
#include <arch/x86.h>
#include <arch/x86/feature.h>
#include <arch/x86/platform_access.h>
#include <arch/x86/pv.h>
#include <fbl/algorithm.h>
#include <kernel/stats.h>
#include <kernel/thread.h>
#include <lk/init.h>
#include <platform/pc.h>
#include <platform/pc/acpi.h>
#include <platform/pic.h>
#include "dev/interrupt.h"
#include "interrupt_manager.h"
#include "lib/acpi_lite/apic.h"
#include "lib/acpi_lite/structures.h"
#include "platform_p.h"
namespace {
// Interface passed to InterruptManager to construct the real system interrupt
// manager.
class IoApic {
public:
static bool IsValidInterrupt(unsigned int vector, uint32_t flags) {
return is_valid_interrupt(vector, flags);
}
static uint8_t FetchIrqVector(unsigned int vector) { return apic_io_fetch_irq_vector(vector); }
static void ConfigureIrqVector(uint32_t global_irq, uint8_t x86_vector) {
apic_io_configure_irq_vector(global_irq, x86_vector);
}
static void ConfigureIrq(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) {
apic_io_configure_irq(global_irq, trig_mode, polarity, del_mode, mask, dst_mode, dst, vector);
}
static void MaskIrq(uint32_t global_irq, bool mask) { apic_io_mask_irq(global_irq, mask); }
static zx_status_t FetchIrqConfig(uint32_t global_irq, enum interrupt_trigger_mode* trig_mode,
enum interrupt_polarity* polarity) {
return apic_io_fetch_irq_config(global_irq, trig_mode, polarity);
}
};
// Singleton for managing interrupts. This is fully initialized in
// platform_init_apic().
InterruptManager<IoApic> kInterruptManager;
// Convert an ACPI entry into the format required by the platform's APIC code.
io_apic_isa_override ParseIsaOverride(const acpi_lite::AcpiMadtIntSourceOverrideEntry* record) {
// 0 means ISA, ISOs are only ever for ISA IRQs
if (record->bus != 0) {
panic("Invalid bus for IO APIC interrupt override.\n");
}
// "Conforms" below means conforms to the bus spec: edge triggered and active high.
const uint32_t flags = record->flags;
const interrupt_polarity polarity = [flags]() {
uint32_t polarity = flags & ACPI_MADT_FLAG_POLARITY_MASK;
switch (polarity) {
case ACPI_MADT_FLAG_POLARITY_CONFORMS:
case ACPI_MADT_FLAG_POLARITY_HIGH:
return IRQ_POLARITY_ACTIVE_HIGH;
case ACPI_MADT_FLAG_POLARITY_LOW:
return IRQ_POLARITY_ACTIVE_LOW;
default:
panic("Unknown IRQ polarity in override: %u\n", polarity);
}
}();
const interrupt_trigger_mode trigger_mode = [flags]() {
uint32_t trigger = flags & ACPI_MADT_FLAG_TRIGGER_MASK;
switch (trigger) {
case ACPI_MADT_FLAG_TRIGGER_CONFORMS:
case ACPI_MADT_FLAG_TRIGGER_EDGE:
return IRQ_TRIGGER_MODE_EDGE;
break;
case ACPI_MADT_FLAG_TRIGGER_LEVEL:
return IRQ_TRIGGER_MODE_LEVEL;
default:
panic("Unknown IRQ trigger in override: %u\n", trigger);
}
}();
return io_apic_isa_override{
.isa_irq = record->source,
.remapped = true,
.tm = trigger_mode,
.pol = polarity,
.global_irq = record->global_sys_interrupt,
};
}
} // namespace
static void platform_init_apic(uint level) {
pic_map(PIC1_BASE, PIC2_BASE);
pic_disable();
acpi_lite::AcpiParser& parser = GlobalAcpiLiteParser();
// Enumerate the IO APICs
fbl::Vector<io_apic_descriptor> descriptors;
zx_status_t status = acpi_lite::EnumerateIoApics(
parser, [&descriptors](const acpi_lite::AcpiMadtIoApicEntry& descriptor) {
fbl::AllocChecker ac;
descriptors.push_back(
io_apic_descriptor{
.apic_id = descriptor.io_apic_id,
.global_irq_base = descriptor.global_system_interrupt_base,
.paddr = descriptor.io_apic_address,
},
&ac);
return ac.check() ? ZX_OK : ZX_ERR_NO_MEMORY;
});
if (status != ZX_OK) {
panic("Could not get IO APIC details: %d\n", status);
}
// Enumerate interrupt source overrides.
fbl::Vector<io_apic_isa_override> overrides;
status = acpi_lite::EnumerateIoApicIsaOverrides(
parser, [&overrides](const acpi_lite::AcpiMadtIntSourceOverrideEntry& isa_override) {
fbl::AllocChecker ac;
overrides.push_back(ParseIsaOverride(&isa_override), &ac);
return ac.check() ? ZX_OK : ZX_ERR_NO_MEMORY;
});
if (status != ZX_OK) {
panic("Could not get interrupt source overrides: %d\n", status);
}
apic_vm_init();
apic_local_init();
apic_io_init(descriptors.data(), descriptors.size(), overrides.data(), overrides.size());
ASSERT(arch_ints_disabled());
// Initialize the delivery modes/targets for the ISA interrupts
uint8_t bsp_apic_id = apic_bsp_id();
for (uint8_t irq = 0; irq < 8; ++irq) {
// Explicitly skip mapping the PIC2 interrupt, since it is actually
// just used internally on the PICs for daisy chaining. QEMU remaps
// ISA IRQ 0 to global IRQ 2, but does not remap ISA IRQ 2 off of
// global IRQ 2, so skipping this mapping also prevents a collision
// with the PIT IRQ.
if (irq != ISA_IRQ_PIC2) {
apic_io_configure_isa_irq(irq, DELIVERY_MODE_FIXED, IO_APIC_IRQ_MASK, DST_MODE_PHYSICAL,
bsp_apic_id, 0);
}
apic_io_configure_isa_irq(static_cast<uint8_t>(irq + 8), DELIVERY_MODE_FIXED, IO_APIC_IRQ_MASK,
DST_MODE_PHYSICAL, bsp_apic_id, 0);
}
status = kInterruptManager.Init();
ASSERT(status == ZX_OK);
}
LK_INIT_HOOK(apic, &platform_init_apic, LK_INIT_LEVEL_VM + 2)
zx_status_t mask_interrupt(unsigned int vector) { return kInterruptManager.MaskInterrupt(vector); }
zx_status_t unmask_interrupt(unsigned int vector) {
return kInterruptManager.UnmaskInterrupt(vector);
}
zx_status_t configure_interrupt(unsigned int vector, enum interrupt_trigger_mode tm,
enum interrupt_polarity pol) {
return kInterruptManager.ConfigureInterrupt(vector, tm, pol);
}
zx_status_t get_interrupt_config(unsigned int vector, enum interrupt_trigger_mode* tm,
enum interrupt_polarity* pol) {
return kInterruptManager.GetInterruptConfig(vector, tm, pol);
}
void platform_irq(iframe_t* frame) {
CPU_STATS_INC(interrupts);
// get the current vector
uint64_t x86_vector = frame->vector;
DEBUG_ASSERT(x86_vector >= X86_INT_PLATFORM_BASE && x86_vector <= X86_INT_PLATFORM_MAX);
// deliver the interrupt
kInterruptManager.InvokeX86Vector(static_cast<uint8_t>(x86_vector));
// NOTE: On x86, we always deactivate the interrupt.
apic_issue_eoi();
}
zx_status_t register_int_handler(unsigned int vector, int_handler handler, void* arg) {
return kInterruptManager.RegisterInterruptHandler(vector, handler, arg);
}
zx_status_t register_permanent_int_handler(unsigned int vector, int_handler handler, void* arg) {
return kInterruptManager.RegisterInterruptHandler(vector, handler, arg, true /* Permanent */);
}
uint32_t interrupt_get_base_vector(void) {
// Intel Software Developer's Manual v3 chapter 6.2
// Vectors 0-31 are reserved for architecture defined interrupts & exceptions.
// Of that, 16-31 can be allocated to the PCI bus by ACPI and are valid for
// interrupt objects.
return 16;
}
uint32_t interrupt_get_max_vector(void) {
// x64 APIC supports 256 total vectors
return 255;
}
bool is_valid_interrupt(unsigned int vector, uint32_t flags) {
return apic_io_is_valid_irq(vector);
}
unsigned int remap_interrupt(unsigned int vector) {
if (vector > NUM_ISA_IRQS) {
return vector;
}
return apic_io_isa_to_global(static_cast<uint8_t>(vector));
}
void shutdown_interrupts(void) { pic_disable(); }
void shutdown_interrupts_curr_cpu(void) {
if (x86_hypervisor_has_pv_eoi()) {
MsrAccess msr;
PvEoi::get()->Disable(&msr);
}
// TODO(maniscalco): Walk interrupt redirection entries and make sure nothing targets this CPU.
}
// Intel 64 socs support the IOAPIC and Local APIC which support MSI by default.
// See 10.1, 10.4, and 10.11 of Intel® 64 and IA-32 Architectures Software Developer’s
// Manual 3A
bool msi_is_supported(void) { return true; }
bool msi_supports_masking() { return false; }
// Since we do not support masking on x64 it is an error to call |msi_mask_unmask|.
void msi_mask_unmask(const msi_block_t* block, uint msi_id, bool mask) { PANIC_UNIMPLEMENTED; }
zx_status_t msi_alloc_block(uint requested_irqs, bool can_target_64bit, bool is_msix,
msi_block_t* out_block) {
return kInterruptManager.MsiAllocBlock(requested_irqs, can_target_64bit, is_msix, out_block);
}
void msi_free_block(msi_block_t* block) { return kInterruptManager.MsiFreeBlock(block); }
void msi_register_handler(const msi_block_t* block, uint msi_id, int_handler handler, void* ctx) {
return kInterruptManager.MsiRegisterHandler(block, msi_id, handler, ctx);
}