You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
541 lines
15 KiB
541 lines
15 KiB
/* |
|
* Copyright (c) 1997-1998, 2000-2002, 2004, 2006-2008, 2011-2015 Wind River |
|
* Systems, Inc. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT intel_ioapic |
|
|
|
/** |
|
* @file |
|
* @brief Intel IO APIC/xAPIC driver |
|
* |
|
* This module is a driver for the IO APIC/xAPIC (Advanced Programmable |
|
* Interrupt Controller) for P6 (PentiumPro, II, III) family processors |
|
* and P7 (Pentium4) family processors. The IO APIC/xAPIC is included |
|
* in the Intel's system chip set, such as ICH2. Software intervention |
|
* may be required to enable the IO APIC/xAPIC in some chip sets. |
|
* The 8259A interrupt controller is intended for use in a uni-processor |
|
* system, IO APIC can be used in either a uni-processor or multi-processor |
|
* system. The IO APIC handles interrupts very differently than the 8259A. |
|
* Briefly, these differences are: |
|
* - Method of Interrupt Transmission. The IO APIC transmits interrupts |
|
* through a 3-wire bus and interrupts are handled without the need for |
|
* the processor to run an interrupt acknowledge cycle. |
|
* - Interrupt Priority. The priority of interrupts in the IO APIC is |
|
* independent of the interrupt number. For example, interrupt 10 can |
|
* be given a higher priority than interrupt 3. |
|
* - More Interrupts. The IO APIC supports a total of 24 interrupts. |
|
* |
|
* The IO APIC unit consists of a set of interrupt input signals, a 24-entry |
|
* by 64-bit Interrupt Redirection Table, programmable registers, and a message |
|
* unit for sending and receiving APIC messages over the APIC bus or the |
|
* Front-Side (system) bus. IO devices inject interrupts into the system by |
|
* asserting one of the interrupt lines to the IO APIC. The IO APIC selects the |
|
* corresponding entry in the Redirection Table and uses the information in that |
|
* entry to format an interrupt request message. Each entry in the Redirection |
|
* Table can be individually programmed to indicate edge/level sensitive interrupt |
|
* signals, the interrupt vector and priority, the destination processor, and how |
|
* the processor is selected (statically and dynamically). The information in |
|
* the table is used to transmit a message to other APIC units (via the APIC bus |
|
* or the Front-Side (system) bus). IO APIC is used in the Symmetric IO Mode. |
|
* The base address of IO APIC is determined in loapic_init() and stored in the |
|
* global variable ioApicBase and ioApicData. |
|
* The lower 32 bit value of the redirection table entries for IRQ 0 |
|
* to 15 are edge triggered positive high, and for IRQ 16 to 23 are level |
|
* triggered positive low. |
|
* |
|
* This implementation doesn't support multiple IO APICs. |
|
* |
|
* INCLUDE FILES: ioapic.h loapic.h |
|
* |
|
*/ |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/arch/cpu.h> |
|
|
|
#include <zephyr/toolchain.h> |
|
#include <zephyr/linker/sections.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/pm/device.h> |
|
#include <string.h> |
|
|
|
#include <zephyr/drivers/interrupt_controller/ioapic.h> /* public API declarations */ |
|
#include <zephyr/drivers/interrupt_controller/loapic.h> /* public API declarations and registers */ |
|
#include "intc_ioapic_priv.h" |
|
|
|
DEVICE_MMIO_TOPLEVEL_STATIC(ioapic_regs, DT_DRV_INST(0)); |
|
|
|
#define IOAPIC_REG DEVICE_MMIO_TOPLEVEL_GET(ioapic_regs) |
|
|
|
/* |
|
* Destination field (bits[56:63]) defines a set of processors, which is |
|
* used to be compared with local LDR to determine which local APICs accept |
|
* the interrupt. |
|
* |
|
* XAPIC: in logical destination mode and flat model (determined by DFR). |
|
* LDR bits[24:31] can accommodate up to 8 logical APIC IDs. |
|
* |
|
* X2APIC: in logical destination mode and cluster model. |
|
* In this case, LDR is read-only to system software and supports up to 16 |
|
* logical IDs. (Cluster ID: don't care to IO APIC). |
|
* |
|
* In either case, regardless how many CPUs in the system, 0xff implies that |
|
* it's intended to deliver to all possible 8 local APICs. |
|
*/ |
|
#define DEFAULT_RTE_DEST (0xFFU << 24) |
|
|
|
static __pinned_bss uint32_t ioapic_rtes; |
|
|
|
#ifdef CONFIG_PM_DEVICE |
|
|
|
#define BITS_PER_IRQ 4 |
|
#define IOAPIC_BITFIELD_HI_LO 0 |
|
#define IOAPIC_BITFIELD_LVL_EDGE 1 |
|
#define IOAPIC_BITFIELD_ENBL_DSBL 2 |
|
#define IOAPIC_BITFIELD_DELIV_MODE 3 |
|
|
|
#define BIT_POS_FOR_IRQ_OPTION(irq, option) ((irq) * BITS_PER_IRQ + (option)) |
|
|
|
/* Allocating up to 256 irq bits bufffer for RTEs, RTEs are dynamically found |
|
* so let's just assume the maximum, it's only 128 bytes in total. |
|
*/ |
|
#define SUSPEND_BITS_REQD (ROUND_UP((256 * BITS_PER_IRQ), 32)) |
|
|
|
__pinned_bss |
|
uint32_t ioapic_suspend_buf[SUSPEND_BITS_REQD / 32] = {0}; |
|
#endif |
|
|
|
static uint32_t __IoApicGet(int32_t offset); |
|
static void __IoApicSet(int32_t offset, uint32_t value); |
|
static void ioApicRedSetHi(unsigned int irq, uint32_t upper32); |
|
static void ioApicRedSetLo(unsigned int irq, uint32_t lower32); |
|
static uint32_t ioApicRedGetLo(unsigned int irq); |
|
static void IoApicRedUpdateLo(unsigned int irq, uint32_t value, |
|
uint32_t mask); |
|
|
|
#if defined(CONFIG_INTEL_VTD_ICTL) && \ |
|
!defined(CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH) |
|
|
|
#include <zephyr/drivers/interrupt_controller/intel_vtd.h> |
|
#include <zephyr/acpi/acpi.h> |
|
|
|
static const struct device *const vtd = |
|
DEVICE_DT_GET_OR_NULL(DT_INST(0, intel_vt_d)); |
|
static uint16_t ioapic_id; |
|
|
|
static bool get_vtd(void) |
|
{ |
|
if (!device_is_ready(vtd)) { |
|
return false; |
|
} |
|
|
|
if (ioapic_id != 0) { |
|
return true; |
|
} |
|
|
|
return acpi_dmar_ioapic_get(&ioapic_id) == 0; |
|
} |
|
#endif /* CONFIG_INTEL_VTD_ICTL && !INTEL_VTD_ICTL_XAPIC_PASSTHROUGH */ |
|
|
|
/* |
|
* The functions irq_enable() and irq_disable() are implemented in the |
|
* interrupt controller driver due to the IRQ virtualization imposed by |
|
* the x86 architecture. |
|
*/ |
|
|
|
/** |
|
* @brief Initialize the IO APIC or xAPIC |
|
* |
|
* This routine initializes the IO APIC or xAPIC. |
|
* |
|
* @retval 0 on success. |
|
*/ |
|
__boot_func |
|
int ioapic_init(const struct device *unused) |
|
{ |
|
ARG_UNUSED(unused); |
|
|
|
DEVICE_MMIO_TOPLEVEL_MAP(ioapic_regs, K_MEM_CACHE_NONE); |
|
|
|
/* Reading MRE: this will give the number of RTEs available */ |
|
ioapic_rtes = ((__IoApicGet(IOAPIC_VERS) & |
|
IOAPIC_MRE_MASK) >> IOAPIC_MRE_POS) + 1; |
|
|
|
#ifdef CONFIG_IOAPIC_MASK_RTE |
|
int32_t ix; /* redirection table index */ |
|
uint32_t rteValue; /* value to copy into redirection table entry */ |
|
|
|
rteValue = IOAPIC_EDGE | IOAPIC_HIGH | IOAPIC_FIXED | IOAPIC_INT_MASK | |
|
IOAPIC_LOGICAL | 0 /* dummy vector */; |
|
|
|
for (ix = 0; ix < ioapic_rtes; ix++) { |
|
ioApicRedSetHi(ix, DEFAULT_RTE_DEST); |
|
ioApicRedSetLo(ix, rteValue); |
|
} |
|
#endif |
|
return 0; |
|
} |
|
|
|
__pinned_func |
|
uint32_t z_ioapic_num_rtes(void) |
|
{ |
|
return ioapic_rtes; |
|
} |
|
|
|
/** |
|
* @brief Enable a specified APIC interrupt input line |
|
* |
|
* This routine enables a specified APIC interrupt input line. |
|
* |
|
* @param irq IRQ number to enable |
|
*/ |
|
__pinned_func |
|
void z_ioapic_irq_enable(unsigned int irq) |
|
{ |
|
IoApicRedUpdateLo(irq, 0, IOAPIC_INT_MASK); |
|
} |
|
|
|
/** |
|
* @brief Disable a specified APIC interrupt input line |
|
* |
|
* This routine disables a specified APIC interrupt input line. |
|
* @param irq IRQ number to disable |
|
*/ |
|
__pinned_func |
|
void z_ioapic_irq_disable(unsigned int irq) |
|
{ |
|
IoApicRedUpdateLo(irq, IOAPIC_INT_MASK, IOAPIC_INT_MASK); |
|
} |
|
|
|
|
|
#ifdef CONFIG_PM_DEVICE |
|
|
|
__pinned_func |
|
void store_flags(unsigned int irq, uint32_t flags) |
|
{ |
|
/* Currently only the following four flags are modified */ |
|
if (flags & IOAPIC_LOW) { |
|
sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_HI_LO)); |
|
} |
|
|
|
if (flags & IOAPIC_LEVEL) { |
|
sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_LVL_EDGE)); |
|
} |
|
|
|
if (flags & IOAPIC_INT_MASK) { |
|
sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_ENBL_DSBL)); |
|
} |
|
|
|
/* |
|
* We support lowest priority and fixed mode only, so only one bit |
|
* needs to be saved. |
|
*/ |
|
if (flags & IOAPIC_LOWEST) { |
|
sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_DELIV_MODE)); |
|
} |
|
} |
|
|
|
__pinned_func |
|
uint32_t restore_flags(unsigned int irq) |
|
{ |
|
uint32_t flags = 0U; |
|
|
|
if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_HI_LO))) { |
|
flags |= IOAPIC_LOW; |
|
} |
|
|
|
if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_LVL_EDGE))) { |
|
flags |= IOAPIC_LEVEL; |
|
} |
|
|
|
if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_ENBL_DSBL))) { |
|
flags |= IOAPIC_INT_MASK; |
|
} |
|
|
|
if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf, |
|
BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_DELIV_MODE))) { |
|
flags |= IOAPIC_LOWEST; |
|
} |
|
|
|
return flags; |
|
} |
|
|
|
|
|
__pinned_func |
|
int ioapic_suspend(const struct device *port) |
|
{ |
|
int irq; |
|
uint32_t rte_lo; |
|
|
|
ARG_UNUSED(port); |
|
(void)memset(ioapic_suspend_buf, 0, (SUSPEND_BITS_REQD >> 3)); |
|
for (irq = 0; irq < ioapic_rtes; irq++) { |
|
/* |
|
* The following check is to figure out the registered |
|
* IRQ lines, so as to limit ourselves to saving the |
|
* flags for them only. |
|
*/ |
|
if (_irq_to_interrupt_vector[irq]) { |
|
rte_lo = ioApicRedGetLo(irq); |
|
store_flags(irq, rte_lo); |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
__pinned_func |
|
int ioapic_resume_from_suspend(const struct device *port) |
|
{ |
|
int irq; |
|
uint32_t flags; |
|
uint32_t rteValue; |
|
|
|
ARG_UNUSED(port); |
|
|
|
for (irq = 0; irq < ioapic_rtes; irq++) { |
|
if (_irq_to_interrupt_vector[irq]) { |
|
/* Get the saved flags */ |
|
flags = restore_flags(irq); |
|
/* Appending the flags that are never modified */ |
|
flags = flags | IOAPIC_LOGICAL; |
|
|
|
rteValue = (_irq_to_interrupt_vector[irq] & |
|
IOAPIC_VEC_MASK) | flags; |
|
} else { |
|
/* Initialize the other RTEs to sane values */ |
|
rteValue = IOAPIC_EDGE | IOAPIC_HIGH | |
|
IOAPIC_FIXED | IOAPIC_INT_MASK | |
|
IOAPIC_LOGICAL | 0 ; /* dummy vector*/ |
|
} |
|
ioApicRedSetHi(irq, DEFAULT_RTE_DEST); |
|
ioApicRedSetLo(irq, rteValue); |
|
} |
|
return 0; |
|
} |
|
|
|
/* |
|
* Implements the driver control management functionality |
|
* the *context may include IN data or/and OUT data |
|
*/ |
|
__pinned_func |
|
static int ioapic_pm_action(const struct device *dev, |
|
enum pm_device_action action) |
|
{ |
|
int ret = 0; |
|
|
|
switch (action) { |
|
case PM_DEVICE_ACTION_RESUME: |
|
ret = ioapic_resume_from_suspend(dev); |
|
break; |
|
case PM_DEVICE_ACTION_SUSPEND: |
|
ret = ioapic_suspend(dev); |
|
break; |
|
default: |
|
ret = -ENOTSUP; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
#endif /*CONFIG_PM_DEVICE*/ |
|
|
|
/** |
|
* @brief Programs the interrupt redirection table |
|
* |
|
* This routine sets up the redirection table entry for the specified IRQ |
|
* @param irq Virtualized IRQ |
|
* @param vector Vector number |
|
* @param flags Interrupt flags |
|
*/ |
|
__boot_func |
|
void z_ioapic_irq_set(unsigned int irq, unsigned int vector, uint32_t flags) |
|
{ |
|
uint32_t rteValue; /* value to copy into redirection table entry */ |
|
#if defined(CONFIG_INTEL_VTD_ICTL) && \ |
|
!defined(CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH) |
|
int irte_idx; |
|
|
|
if (!get_vtd()) { |
|
goto no_vtd; |
|
} |
|
|
|
irte_idx = vtd_get_irte_by_vector(vtd, vector); |
|
if (irte_idx < 0) { |
|
irte_idx = vtd_get_irte_by_irq(vtd, irq); |
|
} |
|
|
|
if (irte_idx >= 0 && !vtd_irte_is_msi(vtd, irte_idx)) { |
|
/* Enable interrupt remapping format and set the irte index */ |
|
rteValue = IOAPIC_VTD_REMAP_FORMAT | |
|
IOAPIC_VTD_INDEX(irte_idx); |
|
ioApicRedSetHi(irq, rteValue); |
|
|
|
/* Remapped: delivery mode is Fixed (000) and |
|
* destination mode is no longer present as it is replaced by |
|
* the 15th bit of irte index, which is always 0 in our case. |
|
*/ |
|
rteValue = IOAPIC_INT_MASK | |
|
(vector & IOAPIC_VEC_MASK) | |
|
(flags & IOAPIC_TRIGGER_MASK) | |
|
(flags & IOAPIC_POLARITY_MASK); |
|
ioApicRedSetLo(irq, rteValue); |
|
|
|
vtd_remap(vtd, irte_idx, vector, flags, ioapic_id); |
|
} else { |
|
no_vtd: |
|
#else |
|
{ |
|
#endif /* CONFIG_INTEL_VTD_ICTL && !CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH */ |
|
/* the delivery mode is determined by the flags |
|
* passed from drivers |
|
*/ |
|
rteValue = IOAPIC_INT_MASK | IOAPIC_LOGICAL | |
|
(vector & IOAPIC_VEC_MASK) | flags; |
|
ioApicRedSetHi(irq, DEFAULT_RTE_DEST); |
|
ioApicRedSetLo(irq, rteValue); |
|
} |
|
} |
|
|
|
/** |
|
* @brief Program interrupt vector for specified irq |
|
* |
|
* The routine writes the interrupt vector in the Interrupt Redirection |
|
* Table for specified irq number |
|
* |
|
* @param irq Interrupt number |
|
* @param vector Vector number |
|
*/ |
|
__boot_func |
|
void z_ioapic_int_vec_set(unsigned int irq, unsigned int vector) |
|
{ |
|
IoApicRedUpdateLo(irq, vector, IOAPIC_VEC_MASK); |
|
} |
|
|
|
/** |
|
* @brief Read a 32 bit IO APIC register |
|
* |
|
* This routine reads the specified IO APIC register using indirect addressing. |
|
* @param offset Register offset (8 bits) |
|
* |
|
* @return register value |
|
*/ |
|
__pinned_func |
|
static uint32_t __IoApicGet(int32_t offset) |
|
{ |
|
uint32_t value; /* value */ |
|
unsigned int key; /* interrupt lock level */ |
|
|
|
/* lock interrupts to ensure indirect addressing works "atomically" */ |
|
|
|
key = irq_lock(); |
|
|
|
*((volatile uint32_t *) (IOAPIC_REG + IOAPIC_IND)) = (unsigned char)offset; |
|
value = *((volatile uint32_t *)(IOAPIC_REG + IOAPIC_DATA)); |
|
|
|
irq_unlock(key); |
|
|
|
return value; |
|
} |
|
|
|
/** |
|
* @brief Write a 32 bit IO APIC register |
|
* |
|
* This routine writes the specified IO APIC register using indirect addressing. |
|
* |
|
* @param offset Register offset (8 bits) |
|
* @param value Value to set the register |
|
*/ |
|
__pinned_func |
|
static void __IoApicSet(int32_t offset, uint32_t value) |
|
{ |
|
unsigned int key; /* interrupt lock level */ |
|
|
|
/* lock interrupts to ensure indirect addressing works "atomically" */ |
|
|
|
key = irq_lock(); |
|
|
|
*(volatile uint32_t *)(IOAPIC_REG + IOAPIC_IND) = (unsigned char)offset; |
|
*((volatile uint32_t *)(IOAPIC_REG + IOAPIC_DATA)) = value; |
|
|
|
irq_unlock(key); |
|
} |
|
|
|
/** |
|
* @brief Get low 32 bits of Redirection Table entry |
|
* |
|
* This routine reads the low-order 32 bits of a Redirection Table entry. |
|
* |
|
* @param irq INTIN number |
|
* @return 32 low-order bits |
|
*/ |
|
__pinned_func |
|
static uint32_t ioApicRedGetLo(unsigned int irq) |
|
{ |
|
int32_t offset = IOAPIC_REDTBL + (irq << 1); /* register offset */ |
|
|
|
return __IoApicGet(offset); |
|
} |
|
|
|
/** |
|
* @brief Set low 32 bits of Redirection Table entry |
|
* |
|
* This routine writes the low-order 32 bits of a Redirection Table entry. |
|
* |
|
* @param irq INTIN number |
|
* @param lower32 Value to be written |
|
*/ |
|
__pinned_func |
|
static void ioApicRedSetLo(unsigned int irq, uint32_t lower32) |
|
{ |
|
int32_t offset = IOAPIC_REDTBL + (irq << 1); /* register offset */ |
|
|
|
__IoApicSet(offset, lower32); |
|
} |
|
|
|
/** |
|
* @brief Set high 32 bits of Redirection Table entry |
|
* |
|
* This routine writes the high-order 32 bits of a Redirection Table entry. |
|
* |
|
* @param irq INTIN number |
|
* @param upper32 Value to be written |
|
*/ |
|
__pinned_func |
|
static void ioApicRedSetHi(unsigned int irq, uint32_t upper32) |
|
{ |
|
int32_t offset = IOAPIC_REDTBL + (irq << 1) + 1; /* register offset */ |
|
|
|
__IoApicSet(offset, upper32); |
|
} |
|
|
|
/** |
|
* @brief Modify low 32 bits of Redirection Table entry |
|
* |
|
* This routine modifies selected portions of the low-order 32 bits of a |
|
* Redirection Table entry, as indicated by the associate bit mask. |
|
* |
|
* @param irq INTIN number |
|
* @param value Value to be written |
|
* @param mask Mask of bits to be modified |
|
*/ |
|
__pinned_func |
|
static void IoApicRedUpdateLo(unsigned int irq, |
|
uint32_t value, |
|
uint32_t mask) |
|
{ |
|
ioApicRedSetLo(irq, (ioApicRedGetLo(irq) & ~mask) | (value & mask)); |
|
} |
|
|
|
PM_DEVICE_DT_INST_DEFINE(0, ioapic_pm_action); |
|
|
|
DEVICE_DT_INST_DEFINE(0, ioapic_init, PM_DEVICE_DT_INST_GET(0), NULL, NULL, |
|
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);
|
|
|