434 lines
11 KiB
C
434 lines
11 KiB
C
/*
|
|
* Copyright (c) 1984-2008, 2011-2015 Wind River Systems, Inc.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/*
|
|
* driver for x86 CPU local APIC (as an interrupt controller)
|
|
*/
|
|
|
|
#include <kernel.h>
|
|
#include <kernel_structs.h>
|
|
#include <arch/cpu.h>
|
|
#include <pm/device.h>
|
|
#include <zephyr/types.h>
|
|
#include <string.h>
|
|
#include <sys/__assert.h>
|
|
#include <arch/x86/msr.h>
|
|
|
|
#include <toolchain.h>
|
|
#include <linker/sections.h>
|
|
#include <drivers/interrupt_controller/loapic.h> /* public API declarations */
|
|
#include <device.h>
|
|
#include <drivers/interrupt_controller/sysapic.h>
|
|
#include <drivers/interrupt_controller/ioapic.h>
|
|
|
|
/* Local APIC Version Register Bits */
|
|
|
|
#define LOAPIC_VERSION_MASK 0x000000ff /* LO APIC Version mask */
|
|
#define LOAPIC_MAXLVT_MASK 0x00ff0000 /* LO APIC Max LVT mask */
|
|
#define LOAPIC_PENTIUM4 0x00000014 /* LO APIC in Pentium4 */
|
|
#define LOAPIC_LVT_PENTIUM4 5 /* LO APIC LVT - Pentium4 */
|
|
#define LOAPIC_LVT_P6 4 /* LO APIC LVT - P6 */
|
|
#define LOAPIC_LVT_P5 3 /* LO APIC LVT - P5 */
|
|
|
|
/* Local APIC Vector Table Bits */
|
|
|
|
#define LOAPIC_VECTOR 0x000000ff /* vectorNo */
|
|
#define LOAPIC_MODE 0x00000700 /* delivery mode */
|
|
#define LOAPIC_FIXED 0x00000000 /* delivery mode: FIXED */
|
|
#define LOAPIC_SMI 0x00000200 /* delivery mode: SMI */
|
|
#define LOAPIC_NMI 0x00000400 /* delivery mode: NMI */
|
|
#define LOAPIC_EXT 0x00000700 /* delivery mode: ExtINT */
|
|
#define LOAPIC_IDLE 0x00000000 /* delivery status: Idle */
|
|
#define LOAPIC_PEND 0x00001000 /* delivery status: Pend */
|
|
#define LOAPIC_HIGH 0x00000000 /* polarity: High */
|
|
#define LOAPIC_LOW 0x00002000 /* polarity: Low */
|
|
#define LOAPIC_REMOTE 0x00004000 /* remote IRR */
|
|
#define LOAPIC_EDGE 0x00000000 /* trigger mode: Edge */
|
|
#define LOAPIC_LEVEL 0x00008000 /* trigger mode: Level */
|
|
|
|
/* Local APIC Spurious-Interrupt Register Bits */
|
|
|
|
#define LOAPIC_ENABLE 0x100 /* APIC Enabled */
|
|
#define LOAPIC_FOCUS_DISABLE 0x200 /* Focus Processor Checking */
|
|
|
|
#if CONFIG_LOAPIC_SPURIOUS_VECTOR_ID == -1
|
|
#define LOAPIC_SPURIOUS_VECTOR_ID (CONFIG_IDT_NUM_VECTORS - 1)
|
|
#else
|
|
#define LOAPIC_SPURIOUS_VECTOR_ID CONFIG_LOAPIC_SPURIOUS_VECTOR_ID
|
|
#endif
|
|
|
|
#define LOPIC_SSPND_BITS_PER_IRQ 1 /* Just the one for enable disable*/
|
|
#define LOPIC_SUSPEND_BITS_REQD (ROUND_UP((LOAPIC_IRQ_COUNT * LOPIC_SSPND_BITS_PER_IRQ), 32))
|
|
#ifdef CONFIG_PM_DEVICE
|
|
#include <pm/device.h>
|
|
__pinned_bss
|
|
uint32_t loapic_suspend_buf[LOPIC_SUSPEND_BITS_REQD / 32] = {0};
|
|
#endif
|
|
|
|
#ifdef DEVICE_MMIO_IS_IN_RAM
|
|
__pinned_bss
|
|
mm_reg_t z_loapic_regs;
|
|
#endif
|
|
|
|
__pinned_func
|
|
void send_eoi(void)
|
|
{
|
|
x86_write_xapic(LOAPIC_EOI, 0);
|
|
}
|
|
|
|
/**
|
|
* @brief Enable and initialize the local APIC.
|
|
*
|
|
* Called from early assembly layer (e.g., crt0.S).
|
|
*/
|
|
__pinned_func
|
|
void z_loapic_enable(unsigned char cpu_number)
|
|
{
|
|
int32_t loApicMaxLvt; /* local APIC Max LVT */
|
|
|
|
#ifdef DEVICE_MMIO_IS_IN_RAM
|
|
device_map(&z_loapic_regs, CONFIG_LOAPIC_BASE_ADDRESS, 0x1000,
|
|
K_MEM_CACHE_NONE);
|
|
#endif /* DEVICE_MMIO_IS_IN_RAM */
|
|
#ifndef CONFIG_X2APIC
|
|
/*
|
|
* in xAPIC and flat model, bits 24-31 in LDR (Logical APIC ID) are
|
|
* bitmap of target logical APIC ID and it supports maximum 8 local
|
|
* APICs.
|
|
*
|
|
* The logical APIC ID could be arbitrarily selected by system software
|
|
* and is different from local APIC ID in local APIC ID register.
|
|
*
|
|
* We choose 0 for BSP, and the index to x86_cpuboot[] for secondary
|
|
* CPUs.
|
|
*
|
|
* in X2APIC, LDR is read-only.
|
|
*/
|
|
x86_write_xapic(LOAPIC_LDR, 1 << (cpu_number + 24));
|
|
#endif
|
|
|
|
/*
|
|
* enable the local APIC. note that we use xAPIC mode here, since
|
|
* x2APIC access is not enabled until the next step (if at all).
|
|
*/
|
|
|
|
x86_write_xapic(LOAPIC_SVR,
|
|
x86_read_xapic(LOAPIC_SVR) | LOAPIC_ENABLE);
|
|
|
|
#ifdef CONFIG_X2APIC
|
|
/*
|
|
* turn on x2APIC mode. we trust the config option, so
|
|
* we don't check CPUID to see if x2APIC is supported.
|
|
*/
|
|
|
|
uint64_t msr = z_x86_msr_read(X86_APIC_BASE_MSR);
|
|
msr |= X86_APIC_BASE_MSR_X2APIC;
|
|
z_x86_msr_write(X86_APIC_BASE_MSR, msr);
|
|
#endif
|
|
|
|
loApicMaxLvt = (x86_read_loapic(LOAPIC_VER) & LOAPIC_MAXLVT_MASK) >> 16;
|
|
|
|
/* reset the DFR, TPR, TIMER_CONFIG, and TIMER_ICR */
|
|
|
|
#ifndef CONFIG_X2APIC
|
|
/* Flat model */
|
|
x86_write_loapic(LOAPIC_DFR, 0xffffffff); /* no DFR in x2APIC mode */
|
|
#endif
|
|
|
|
x86_write_loapic(LOAPIC_TPR, 0x0);
|
|
x86_write_loapic(LOAPIC_TIMER_CONFIG, 0x0);
|
|
x86_write_loapic(LOAPIC_TIMER_ICR, 0x0);
|
|
|
|
/* program Local Vector Table for the Virtual Wire Mode */
|
|
|
|
/* skip LINT0/LINT1 for Jailhouse guest case, because we won't
|
|
* ever be waiting for interrupts on those
|
|
*/
|
|
/* set LINT0: extInt, high-polarity, edge-trigger, not-masked */
|
|
|
|
x86_write_loapic(LOAPIC_LINT0, (x86_read_loapic(LOAPIC_LINT0) &
|
|
~(LOAPIC_MODE | LOAPIC_LOW |
|
|
LOAPIC_LEVEL | LOAPIC_LVT_MASKED)) |
|
|
(LOAPIC_EXT | LOAPIC_HIGH | LOAPIC_EDGE));
|
|
|
|
/* set LINT1: NMI, high-polarity, edge-trigger, not-masked */
|
|
|
|
x86_write_loapic(LOAPIC_LINT1, (x86_read_loapic(LOAPIC_LINT1) &
|
|
~(LOAPIC_MODE | LOAPIC_LOW |
|
|
LOAPIC_LEVEL | LOAPIC_LVT_MASKED)) |
|
|
(LOAPIC_NMI | LOAPIC_HIGH | LOAPIC_EDGE));
|
|
|
|
/* lock the Local APIC interrupts */
|
|
|
|
x86_write_loapic(LOAPIC_TIMER, LOAPIC_LVT_MASKED);
|
|
x86_write_loapic(LOAPIC_ERROR, LOAPIC_LVT_MASKED);
|
|
|
|
if (loApicMaxLvt >= LOAPIC_LVT_P6) {
|
|
x86_write_loapic(LOAPIC_PMC, LOAPIC_LVT_MASKED);
|
|
}
|
|
|
|
if (loApicMaxLvt >= LOAPIC_LVT_PENTIUM4) {
|
|
x86_write_loapic(LOAPIC_THERMAL, LOAPIC_LVT_MASKED);
|
|
}
|
|
|
|
#if CONFIG_LOAPIC_SPURIOUS_VECTOR
|
|
x86_write_loapic(LOAPIC_SVR, (x86_read_loapic(LOAPIC_SVR) & 0xFFFFFF00) |
|
|
(LOAPIC_SPURIOUS_VECTOR_ID & 0xFF));
|
|
#endif
|
|
|
|
/* discard a pending interrupt if any */
|
|
x86_write_loapic(LOAPIC_EOI, 0);
|
|
}
|
|
|
|
/**
|
|
* @brief Dummy initialization function.
|
|
*
|
|
* The local APIC is initialized via z_loapic_enable() long before the
|
|
* kernel runs through its device initializations, so this is unneeded.
|
|
*/
|
|
__boot_func
|
|
static int loapic_init(const struct device *unused)
|
|
{
|
|
ARG_UNUSED(unused);
|
|
return 0;
|
|
}
|
|
|
|
|
|
__pinned_func
|
|
uint32_t z_loapic_irq_base(void)
|
|
{
|
|
return z_ioapic_num_rtes();
|
|
}
|
|
|
|
/**
|
|
* @brief Set the vector field in the specified RTE
|
|
*
|
|
* This associates an IRQ with the desired vector in the IDT.
|
|
*/
|
|
__boot_func
|
|
void z_loapic_int_vec_set(unsigned int irq, /* IRQ number of the interrupt */
|
|
unsigned int vector /* vector to copy into the LVT */
|
|
)
|
|
{
|
|
unsigned int oldLevel; /* previous interrupt lock level */
|
|
|
|
/*
|
|
* The following mappings are used:
|
|
*
|
|
* IRQ0 -> LOAPIC_TIMER
|
|
* IRQ1 -> LOAPIC_THERMAL
|
|
* IRQ2 -> LOAPIC_PMC
|
|
* IRQ3 -> LOAPIC_LINT0
|
|
* IRQ4 -> LOAPIC_LINT1
|
|
* IRQ5 -> LOAPIC_ERROR
|
|
*
|
|
* It's assumed that LVTs are spaced by 0x10 bytes
|
|
*/
|
|
|
|
/* update the 'vector' bits in the LVT */
|
|
|
|
oldLevel = irq_lock();
|
|
x86_write_loapic(LOAPIC_TIMER + (irq * 0x10),
|
|
(x86_read_loapic(LOAPIC_TIMER + (irq * 0x10)) &
|
|
~LOAPIC_VECTOR) | vector);
|
|
irq_unlock(oldLevel);
|
|
}
|
|
|
|
/**
|
|
* @brief Enable an individual LOAPIC interrupt (IRQ)
|
|
*
|
|
* @param irq the IRQ number of the interrupt
|
|
*
|
|
* This routine clears the interrupt mask bit in the LVT for the specified IRQ
|
|
*/
|
|
__pinned_func
|
|
void z_loapic_irq_enable(unsigned int irq)
|
|
{
|
|
unsigned int oldLevel; /* previous interrupt lock level */
|
|
|
|
/*
|
|
* See the comments in _LoApicLvtVecSet() regarding IRQ to LVT mappings
|
|
* and ths assumption concerning LVT spacing.
|
|
*/
|
|
|
|
/* clear the mask bit in the LVT */
|
|
|
|
oldLevel = irq_lock();
|
|
x86_write_loapic(LOAPIC_TIMER + (irq * 0x10),
|
|
x86_read_loapic(LOAPIC_TIMER + (irq * 0x10)) &
|
|
~LOAPIC_LVT_MASKED);
|
|
irq_unlock(oldLevel);
|
|
}
|
|
|
|
/**
|
|
* @brief Disable an individual LOAPIC interrupt (IRQ)
|
|
*
|
|
* @param irq the IRQ number of the interrupt
|
|
*
|
|
* This routine clears the interrupt mask bit in the LVT for the specified IRQ
|
|
*/
|
|
__pinned_func
|
|
void z_loapic_irq_disable(unsigned int irq)
|
|
{
|
|
unsigned int oldLevel; /* previous interrupt lock level */
|
|
|
|
/*
|
|
* See the comments in _LoApicLvtVecSet() regarding IRQ to LVT mappings
|
|
* and ths assumption concerning LVT spacing.
|
|
*/
|
|
|
|
/* set the mask bit in the LVT */
|
|
|
|
oldLevel = irq_lock();
|
|
x86_write_loapic(LOAPIC_TIMER + (irq * 0x10),
|
|
x86_read_loapic(LOAPIC_TIMER + (irq * 0x10)) |
|
|
LOAPIC_LVT_MASKED);
|
|
irq_unlock(oldLevel);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Find the currently executing interrupt vector, if any
|
|
*
|
|
* This routine finds the vector of the interrupt that is being processed.
|
|
* The ISR (In-Service Register) register contain the vectors of the interrupts
|
|
* in service. And the higher vector is the identification of the interrupt
|
|
* being currently processed.
|
|
*
|
|
* This function must be called with interrupts locked in interrupt context.
|
|
*
|
|
* ISR registers' offsets:
|
|
* --------------------
|
|
* | Offset | bits |
|
|
* --------------------
|
|
* | 0100H | 0:31 |
|
|
* | 0110H | 32:63 |
|
|
* | 0120H | 64:95 |
|
|
* | 0130H | 96:127 |
|
|
* | 0140H | 128:159 |
|
|
* | 0150H | 160:191 |
|
|
* | 0160H | 192:223 |
|
|
* | 0170H | 224:255 |
|
|
* --------------------
|
|
*
|
|
* @return The vector of the interrupt that is currently being processed, or -1
|
|
* if no IRQ is being serviced.
|
|
*/
|
|
__pinned_func
|
|
int z_irq_controller_isr_vector_get(void)
|
|
{
|
|
int pReg, block;
|
|
|
|
/* Block 0 bits never lit up as these are all exception or reserved
|
|
* vectors
|
|
*/
|
|
for (block = 7; likely(block > 0); block--) {
|
|
pReg = x86_read_loapic(LOAPIC_ISR + (block * 0x10));
|
|
if (pReg) {
|
|
return (block * 32) + (find_msb_set(pReg) - 1);
|
|
}
|
|
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
__pinned_func
|
|
static int loapic_suspend(const struct device *port)
|
|
{
|
|
volatile uint32_t lvt; /* local vector table entry value */
|
|
int loapic_irq;
|
|
|
|
ARG_UNUSED(port);
|
|
|
|
(void)memset(loapic_suspend_buf, 0, (LOPIC_SUSPEND_BITS_REQD >> 3));
|
|
|
|
for (loapic_irq = 0; loapic_irq < LOAPIC_IRQ_COUNT; loapic_irq++) {
|
|
|
|
if (_irq_to_interrupt_vector[z_loapic_irq_base() + loapic_irq]) {
|
|
|
|
/* Since vector numbers are already present in RAM/ROM,
|
|
* We save only the mask bits here.
|
|
*/
|
|
lvt = x86_read_loapic(LOAPIC_TIMER + (loapic_irq * 0x10));
|
|
|
|
if ((lvt & LOAPIC_LVT_MASKED) == 0U) {
|
|
sys_bitfield_set_bit((mem_addr_t)loapic_suspend_buf,
|
|
loapic_irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
__pinned_func
|
|
int loapic_resume(const struct device *port)
|
|
{
|
|
int loapic_irq;
|
|
|
|
ARG_UNUSED(port);
|
|
|
|
/* Assuming all loapic device registers lose their state, the call to
|
|
* z_loapic_init(), should bring all the registers to a sane state.
|
|
*/
|
|
loapic_init(NULL);
|
|
|
|
for (loapic_irq = 0; loapic_irq < LOAPIC_IRQ_COUNT; loapic_irq++) {
|
|
|
|
if (_irq_to_interrupt_vector[z_loapic_irq_base() + loapic_irq]) {
|
|
/* Configure vector and enable the required ones*/
|
|
z_loapic_int_vec_set(loapic_irq,
|
|
_irq_to_interrupt_vector[z_loapic_irq_base() +
|
|
loapic_irq]);
|
|
|
|
if (sys_bitfield_test_bit((mem_addr_t) loapic_suspend_buf,
|
|
loapic_irq)) {
|
|
z_loapic_irq_enable(loapic_irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Implements the driver control management functionality
|
|
* the *context may include IN data or/and OUT data
|
|
*/
|
|
__pinned_func
|
|
static int loapic_pm_action(const struct device *dev,
|
|
enum pm_device_action action)
|
|
{
|
|
int ret = 0;
|
|
|
|
switch (action) {
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
ret = loapic_suspend(dev);
|
|
break;
|
|
case PM_DEVICE_ACTION_RESUME:
|
|
ret = loapic_resume(dev);
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_PM_DEVICE */
|
|
|
|
PM_DEVICE_DEFINE(loapic, loapic_pm_action);
|
|
|
|
DEVICE_DEFINE(loapic, "loapic", loapic_init, PM_DEVICE_GET(loapic), NULL, NULL,
|
|
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);
|
|
|
|
#if CONFIG_LOAPIC_SPURIOUS_VECTOR
|
|
extern void z_loapic_spurious_handler(void);
|
|
|
|
NANO_CPU_INT_REGISTER(z_loapic_spurious_handler, NANO_SOFT_IRQ,
|
|
LOAPIC_SPURIOUS_VECTOR_ID >> 4,
|
|
LOAPIC_SPURIOUS_VECTOR_ID, 0);
|
|
#endif
|