zephyr/drivers/interrupt_controller/loapic_intr.c

463 lines
15 KiB
C

/* loApicIntr.c - Intel Pentium[234] Local APIC/xAPIC driver */
/*
* Copyright (c) 1984-2008, 2011-2015 Wind River Systems, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* DESCRIPTION
* 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 <nanokernel.h>
#include <arch/cpu.h>
#include <stdint.h>
#include "board.h"
#include <toolchain.h>
#include <sections.h>
#include <drivers/loapic.h> /* public API declarations */
/* 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 */
/**
*
* @brief Initialize the Local APIC or xAPIC
*
* This routine initializes Local APIC or xAPIC.
*
* @return N/A
*
*/
int _loapic_init(struct device *unused)
{
ARG_UNUSED(unused);
int32_t loApicMaxLvt; /* local APIC Max LVT */
/* enable the Local APIC */
_loapic_enable();
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;
/* discard a pending interrupt if any */
_loapic_eoi();
return 0;
}
/**
*
* @brief Enable the Local xAPIC
*
* This routine enables the Local xAPIC.
*
* @return N/A
*/
void _loapic_enable(void)
{
int32_t oldLevel = irq_lock(); /* LOCK INTERRUPTS */
*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR) |= LOAPIC_ENABLE;
irq_unlock(oldLevel); /* UNLOCK INTERRUPTS */
}
/**
*
* @brief Disable the Local xAPIC
*
* This routine disables the Local xAPIC.
*
* @return N/A
*/
void _loapic_disable(void)
{
int32_t oldLevel = irq_lock(); /* LOCK INTERRUPTS */
*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_SVR) &=
~LOAPIC_ENABLE;
irq_unlock(oldLevel); /* UNLOCK INTERRUPTS */
}
/**
*
* @brief Set the vector field in the specified RTE
*
* This routine is utilized by the interrupt controller's _SysIntVecAlloc()
* routine (which exists to support the irq_connect() API). Once
* a vector has been allocated, this routine is invoked to update the LVT
* entry associated with <irq> with the vector.
*
* @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 */
int32_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 Trigger an interrupt from software
*
* This is the required method to trigger an interrupt from software,
* use this instead of hard-coded asm "int <vector>" operations. Sends a
* self-directed inter-processor interrupt to the LOAPIC which in turn
* triggers the IRQ handler for the specified vector.
*
* @param vector IRQ vector in the IDT to trigger
*/
void loapic_int_vec_trigger(unsigned int vector)
{
uint32_t icr_cmd;
/*
* Bit 14 : level ASSERT (1)
* Bit 18-19: Destination shorthand SELF (01)
* Bit 15 : trigger mode EDGE (0)
*
* Destination mode ignored,
*/
icr_cmd = vector | (1 << 14) | (1 << 18);
*(volatile int *)(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_ICRLO) = icr_cmd;
}
/**
*
* @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 */
int32_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 */
int32_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 indentification of the interrupt
* being currently processed.
*
* 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.
*/
int _loapic_isr_vector_get(void)
{
/* pointer to ISR vector table */
volatile int *pReg;
int block = 0;
while (block < 8) {
pReg = (volatile int *)
(CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_ISR + (block * 0x10));
if (*pReg) {
return (block * 32) + (find_lsb_set(*pReg) - 1);
}
block++;
}
return 0;
}