549 lines
15 KiB
C
549 lines
15 KiB
C
/*
|
|
* 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 (0xFF << 24)
|
|
|
|
static __pinned_bss uint32_t ioapic_rtes;
|
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
#include <zephyr/pm/device.h>
|
|
|
|
#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/arch/x86/acpi.h>
|
|
|
|
static const struct device *vtd;
|
|
static uint16_t ioapic_id;
|
|
|
|
|
|
static bool get_vtd(void)
|
|
{
|
|
if (vtd != NULL) {
|
|
return true;
|
|
}
|
|
|
|
#define DRV_COMPAT_BAK DT_DRV_COMPAT
|
|
#undef DT_DRV_COMPAT
|
|
#define DT_DRV_COMPAT intel_vt_d
|
|
vtd = DEVICE_DT_GET_OR_NULL(DT_DRV_INST(0));
|
|
#undef DT_DRV_COMPAT
|
|
#define DT_DRV_COMPAT DRV_COMPAT_BAK
|
|
#undef DRV_COMPAT_BAK
|
|
|
|
ioapic_id = z_acpi_get_dev_id_from_dmar(ACPI_DRHD_DEV_SCOPE_IOAPIC);
|
|
|
|
return vtd == NULL ? false : true;
|
|
}
|
|
#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)) = (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) = (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_DEFINE(ioapic, ioapic_pm_action);
|
|
|
|
DEVICE_DEFINE(ioapic, "ioapic", ioapic_init, PM_DEVICE_GET(ioapic), NULL, NULL,
|
|
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);
|