/* * Copyright (c) 1984-2008, 2011-2015 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief LoApicIntr.c - Intel Pentium[234] Local APIC/xAPIC driver * * This module is a driver for the local APIC/xAPIC (Advanced Programmable * Interrupt Controller) in P6 (PentiumPro, II, III) family processors * and P7 (Pentium4) family processors. The local APIC/xAPIC is included * in selected P6 (PentiumPro, II, III) and P7 (Pentium4) family processors. * Beginning with the P6 family processors, the presence or absence of an * on-chip local APIC can be detected using the CPUID instruction. When the * CPUID instruction is executed, bit 9 of the feature flags returned in the * EDX register indicates the presence (set) or absence (clear) of an on-chip * local APIC. * * The local APIC performs two main functions for the processor: * - It processes local external interrupts that the processor receives at its * interrupt pins and local internal interrupts that software generates. * - It communicates with an external IO APIC * chip. The external IO APIC receives external interrupt events from * peripheral and direct them to the local APIC. The IO APIC is * part of Intel's system chip set. * The local APIC controls the dispatching of interrupts (to its associated * processor) that it receives either locally or from the IO APIC. It provides * facilities for queuing, nesting and masking of interrupts. It handles the * interrupt delivery protocol with its local processor and accesses to APIC * registers. * A timer on the local APIC allows local generation of interrupts, and * local interrupt pins permit local reception of processor-specific interrupts. * The local APIC can be disabled and used in conjunction with a standard 8259A * style interrupt controller. Disabling the local APIC can be done in hardware * for the Pentium processors or in software for the P6 and P7 (Pentium4) family * processors. * * The local APIC in the Pentium4 processors (called the xAPIC) is an extension * of the local APIC found in the P6 family processors. The primary difference * between the APIC architecture and xAPIC architecture is that with Pentium4 * processors, the local xAPICs and IO xAPIC communicate with one another * through the processors system bus; whereas, with the P6 family processors, * communication between the local APICs and the IO APIC is handled through a * dedicated 3-wire APIC bus. Also, some of the architectural features of the * local APIC have been extended and/or modified in the local xAPIC. * * This driver contains three routines for use. They are: * _loapic_init() initializes the Local APIC for the interrupt mode chosen. * _loapic_enable()/disable() enables / disables the Local APIC. * * Local APIC is used in the Virtual Wire Mode: delivery mode ExtINT. * * Virtual Wire Mode is one of three interrupt modes defined by the MP * specification. In this mode, interrupts are generated by the 8259A * equivalent PICs (if present) and delivered to the Boot Strap Processor by * the local APIC that is programmed to act as a "virtual Wire"; that * is, the local APIC is logically indistinguishable from a hardware * connection. This is a uniprocessor compatibility mode. * * The local and IO APICs support interrupts in the range of 32 to 255. * Interrupt priority is implied by its vector, according to the following * relationship: "priority = vector / 16". * Here the quotient is rounded down to the nearest integer value to determine * the priority, with 1 being the lowest and 15 is the highest. Because vectors * 0 through 31 are reserved for exclusive use by the processor, the priority of * user defined interrupts range from 2 to 15. A value of 15 in the Interrupt * Class field of the Task Priority Register (TPR) will mask off all interrupts, * which require interrupt service. * The P6 family processor's local APIC includes an in-service entry and a * holding entry for each priority level. To avoid losing interrupts, software * should allocate no more than 2 interrupt vectors per priority. The Pentium4 * processor expands this support of all acceptance of two interrupts per vector * rather than per priority level. * * INCLUDE FILES: loapic.h */ #include #include #include #include #include #include "board.h" #include #include #include /* public API declarations */ #include #include /* IA32_APIC_BASE MSR Bits */ #define LOAPIC_BASE_MASK 0xfffff000 /* LO APIC Base Addr mask */ #define LOAPIC_GLOBAL_ENABLE 0x00000800 /* LO APIC Global Enable */ /* Local APIC ID Register Bits */ #define LOAPIC_ID_MASK 0x0f000000 /* LO APIC ID mask */ /* 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 */ /* Local Vector's lock-unlock macro used in loApicIntLock/Unlock */ #define LOCKED_TIMER 0x01 #define LOCKED_PMC 0x02 #define LOCKED_LINT0 0x04 #define LOCKED_LINT1 0x08 #define LOCKED_ERROR 0x10 #define LOCKED_THERMAL 0x20 /* Interrupt Command Register: delivery mode and status */ #define MODE_FIXED 0x0 /* delivery mode: Fixed */ #define MODE_LOWEST 0x1 /* delivery mode: Lowest */ #define MODE_SMI 0x2 /* delivery mode: SMI */ #define MODE_NMI 0x4 /* delivery mode: NMI */ #define MODE_INIT 0x5 /* delivery mode: INIT */ #define MODE_STARTUP 0x6 /* delivery mode: StartUp */ #define STATUS_PEND 0x1000 /* delivery status: Pend */ /* MP Configuration Table Entries */ #define MP_ENTRY_CPU 0 /* Entry Type: CPU */ #define MP_ENTRY_BUS 1 /* Entry Type: BUS */ #define MP_ENTRY_IOAPIC 2 /* Entry Type: IO APIC */ #define MP_ENTRY_IOINTERRUPT 3 /* Entry Type: IO INT */ #define MP_ENTRY_LOINTERRUPT 4 /* Entry Type: LO INT */ /* Extended MP Configuration Table Entries */ #define EXT_MP_ENTRY_SASM 128 /* Entry Type: System Address Space Map */ #define EXT_MP_ENTRY_BHD 129 /* Entry Type: Bus Hierarchy Descriptor */ #define EXT_MP_ENTRY_CBASM 130 /* Entry Type: Comp Address Space Modifier */ /* MP Configuration Table CPU Flags */ #define MP_CPU_FLAGS_BP 0x02 /* IMCR related bits */ #define IMCR_ADRS 0x22 /* IMCR addr reg */ #define IMCR_DATA 0x23 /* IMCR data reg */ #define IMCR_REG_SEL 0x70 /* IMCR reg select */ #define IMCR_IOAPIC_ON 0x01 /* IMCR IOAPIC route enable */ #define IMCR_IOAPIC_OFF 0x00 /* IMCR IOAPIC route disable */ #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_DEVICE_POWER_MANAGEMENT #include u32_t loapic_suspend_buf[LOPIC_SUSPEND_BITS_REQD / 32] = {0}; static u32_t loapic_device_power_state = DEVICE_PM_ACTIVE_STATE; #endif /** * * @brief Initialize the Local APIC or xAPIC * * This routine initializes Local APIC or xAPIC. * * @return N/A * */ static int _loapic_init(struct device *unused) { ARG_UNUSED(unused); s32_t loApicMaxLvt; /* local APIC Max LVT */ /* enable the Local APIC */ sys_write32(sys_read32(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR) | LOAPIC_ENABLE, CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR); loApicMaxLvt = (*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_VER) & LOAPIC_MAXLVT_MASK) >> 16; /* reset the DFR, TPR, TIMER_CONFIG, and TIMER_ICR */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_DFR) = (int)0xffffffff; *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TPR) = (int)0x0; *(volatile int *) (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER_CONFIG) = (int)0x0; *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER_ICR) = (int)0x0; /* program Local Vector Table for the Virtual Wire Mode */ /* set LINT0: extInt, high-polarity, edge-trigger, not-masked */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_LINT0) = (*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + 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 */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_LINT1) = (*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_LINT1) & ~(LOAPIC_MODE | LOAPIC_LOW | LOAPIC_LEVEL | LOAPIC_LVT_MASKED)) | (LOAPIC_NMI | LOAPIC_HIGH | LOAPIC_EDGE); /* lock the Local APIC interrupts */ *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER) = LOAPIC_LVT_MASKED; *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_ERROR) = LOAPIC_LVT_MASKED; if (loApicMaxLvt >= LOAPIC_LVT_P6) *(volatile int *) (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_PMC) = LOAPIC_LVT_MASKED; if (loApicMaxLvt >= LOAPIC_LVT_PENTIUM4) *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_THERMAL) = LOAPIC_LVT_MASKED; #if CONFIG_LOAPIC_SPURIOUS_VECTOR *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR) = (*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR) & 0xFFFFFF00) | (LOAPIC_SPURIOUS_VECTOR_ID & 0xFF); #endif /* discard a pending interrupt if any */ #if CONFIG_EOI_FORWARDING_BUG _lakemont_eoi(); #else *(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_EOI) = 0; #endif return 0; } /** * * @brief Set the vector field in the specified RTE * * This associates an IRQ with the desired vector in the IDT. * * @return N/A */ void _loapic_int_vec_set(unsigned int irq, /* IRQ number of the interrupt */ unsigned int vector /* vector to copy into the LVT */ ) { volatile int *pLvt; /* pointer to local vector table */ s32_t 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 */ pLvt = (volatile int *) (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER + (irq * 0x10)); /* update the 'vector' bits in the LVT */ oldLevel = irq_lock(); *pLvt = (*pLvt & ~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 * * @return N/A */ void _loapic_irq_enable(unsigned int irq) { volatile int *pLvt; /* pointer to local vector table */ s32_t oldLevel; /* previous interrupt lock level */ /* * See the comments in _LoApicLvtVecSet() regarding IRQ to LVT mappings * and ths assumption concerning LVT spacing. */ pLvt = (volatile int *) (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER + (irq * 0x10)); /* clear the mask bit in the LVT */ oldLevel = irq_lock(); *pLvt = *pLvt & ~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 * * @return N/A */ void _loapic_irq_disable(unsigned int irq) { volatile int *pLvt; /* pointer to local vector table */ s32_t oldLevel; /* previous interrupt lock level */ /* * See the comments in _LoApicLvtVecSet() regarding IRQ to LVT mappings * and ths assumption concerning LVT spacing. */ pLvt = (volatile int *) (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER + (irq * 0x10)); /* set the mask bit in the LVT */ oldLevel = irq_lock(); *pLvt = *pLvt | 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. */ int __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 = sys_read32(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_ISR + (block * 0x10)); if (pReg) { return (block * 32) + (find_msb_set(pReg) - 1); } } return -1; } #ifdef CONFIG_DEVICE_POWER_MANAGEMENT static int loapic_suspend(struct device *port) { volatile int *pLvt; /* pointer to local vector table */ int loapic_irq; ARG_UNUSED(port); 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[LOAPIC_IRQ_BASE + loapic_irq]) { /* Since vector numbers are already present in RAM/ROM, * We save only the mask bits here. */ pLvt = (volatile int *) (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER + (loapic_irq * 0x10)); if ((*pLvt & LOAPIC_LVT_MASKED) == 0) { sys_bitfield_set_bit((mem_addr_t)loapic_suspend_buf, loapic_irq); } } } loapic_device_power_state = DEVICE_PM_SUSPEND_STATE; return 0; } int loapic_resume(struct device *port) { int loapic_irq; ARG_UNUSED(port); /* Assuming all loapic device registers lose their state, the call to * _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[LOAPIC_IRQ_BASE + loapic_irq]) { /* Configure vector and enable the required ones*/ _loapic_int_vec_set(loapic_irq, _irq_to_interrupt_vector[LOAPIC_IRQ_BASE + loapic_irq]); if (sys_bitfield_test_bit((mem_addr_t) loapic_suspend_buf, loapic_irq)) { _loapic_irq_enable(loapic_irq); } } } loapic_device_power_state = DEVICE_PM_ACTIVE_STATE; return 0; } /* * Implements the driver control management functionality * the *context may include IN data or/and OUT data */ static int loapic_device_ctrl(struct device *port, u32_t ctrl_command, void *context) { if (ctrl_command == DEVICE_PM_SET_POWER_STATE) { if (*((u32_t *)context) == DEVICE_PM_SUSPEND_STATE) { return loapic_suspend(port); } else if (*((u32_t *)context) == DEVICE_PM_ACTIVE_STATE) { return loapic_resume(port); } } else if (ctrl_command == DEVICE_PM_GET_POWER_STATE) { *((u32_t *)context) = loapic_device_power_state; return 0; } return 0; } SYS_DEVICE_DEFINE("loapic", _loapic_init, loapic_device_ctrl, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); #else SYS_INIT(_loapic_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); #endif /* CONFIG_DEVICE_POWER_MANAGEMENT */ #if CONFIG_LOAPIC_SPURIOUS_VECTOR extern void _loapic_spurious_handler(void); NANO_CPU_INT_REGISTER(_loapic_spurious_handler, NANO_SOFT_IRQ, LOAPIC_SPURIOUS_VECTOR_ID >> 4, LOAPIC_SPURIOUS_VECTOR_ID, 0); #endif