blob: 9722e1552abcfcb49bd7c356c02f19179a7d3efe [file] [log] [blame]
/*
* Copyright (c) 1997-1998, 2000-2002, 2004, 2006-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.
*/
/**
* @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 <nanokernel.h>
#include <arch/cpu.h>
#include "board.h"
#include <toolchain.h>
#include <sections.h>
#include <init.h>
#include <string.h>
#include <drivers/ioapic.h> /* public API declarations */
#include <drivers/loapic.h> /* public API declarations and registers */
#include "ioapic_priv.h"
#define BITS_PER_IRQ 3
#define IOAPIC_BITFIELD_HI_LO 0
#define IOAPIC_BITFIELD_LVL_EDGE 1
#define IOAPIC_BITFIELD_ENBL_DSBL 2
#define BIT_POS_FOR_IRQ_OPTION(irq, option) ((irq) * BITS_PER_IRQ + (option))
#define SUSPEND_BITS_REQD (ROUND_UP((CONFIG_IOAPIC_NUM_RTES * BITS_PER_IRQ), 32))
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
#include <power.h>
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);
/*
* 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.
*
* @return N/A
*/
int _ioapic_init(struct device *unused)
{
ARG_UNUSED(unused);
int32_t ix; /* redirection table index */
uint32_t rteValue; /* value to copy into redirection table entry */
/*
* The platform must set the Kconfig option IOAPIC_NUM_RTES to indicate
* the number of redirection table entries supported by the IOAPIC.
*
* Note: The number of actual IRQs supported by the IOAPIC can be
* determined at runtime by computing:
*
* ((__IoApicGet(IOAPIC_VERS) & IOAPIC_MRE_MASK) >> 16) + 1
*/
/*
* Initialize the redirection table entries with default settings;
* actual interrupt vectors are specified during irq_connect_dynamic().
*
* A future enhancement should make this initialization "table driven":
* use data provided by the platform to specify the initial state
*/
rteValue = IOAPIC_EDGE | IOAPIC_HIGH | IOAPIC_FIXED | IOAPIC_INT_MASK |
IOAPIC_PHYSICAL | 0 /* dummy vector */;
for (ix = 0; ix < CONFIG_IOAPIC_NUM_RTES; ix++) {
ioApicRedSetHi(ix, 0);
ioApicRedSetLo(ix, rteValue);
}
return 0;
}
/**
*
* @brief Enable a specified APIC interrupt input line
*
* This routine enables a specified APIC interrupt input line.
* @param irq IRQ number to enable
*
* @return N/A
*/
void _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
*
* @return N/A
*/
void _ioapic_irq_disable(unsigned int irq)
{
_IoApicRedUpdateLo(irq, IOAPIC_INT_MASK, IOAPIC_INT_MASK);
}
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
void store_flags(unsigned int irq, uint32_t flags)
{
/* Currently only the following three 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));
}
}
uint32_t restore_flags(unsigned int irq)
{
uint32_t flags = 0;
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;
}
return flags;
}
int ioapic_suspend(struct device *port, int pm_policy)
{
int irq;
uint32_t rte_lo;
ARG_UNUSED(port);
if (pm_policy == SYS_PM_DEEP_SLEEP) {
memset(ioapic_suspend_buf, 0, (SUSPEND_BITS_REQD >> 3));
for (irq = 0; irq < CONFIG_IOAPIC_NUM_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;
}
int ioapic_resume(struct device *port, int pm_policy)
{
int irq;
uint32_t flags;
uint32_t rteValue;
ARG_UNUSED(port);
if (pm_policy == SYS_PM_DEEP_SLEEP) {
for (irq = 0; irq < CONFIG_IOAPIC_NUM_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_FIXED | IOAPIC_PHYSICAL;
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_PHYSICAL | 0 ; /* dummy vector*/
}
ioApicRedSetHi(irq, 0);
ioApicRedSetLo(irq, rteValue);
}
}
return 0;
}
#endif /*CONFIG_DEVICE_POWER_MANAGEMENT*/
/**
*
* @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
*
* @return N/A
*/
void _ioapic_irq_set(unsigned int irq, unsigned int vector, uint32_t flags)
{
uint32_t rteValue; /* value to copy into redirection table entry */
rteValue = IOAPIC_FIXED | IOAPIC_INT_MASK | IOAPIC_PHYSICAL |
(vector & IOAPIC_VEC_MASK) | flags;
ioApicRedSetHi(irq, 0);
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
* @return N/A
*/
void _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
*/
static uint32_t __IoApicGet(int32_t offset)
{
uint32_t value; /* value */
int key; /* interrupt lock level */
/* lock interrupts to ensure indirect addressing works "atomically" */
key = irq_lock();
*((volatile char *)
(CONFIG_IOAPIC_BASE_ADDRESS + IOAPIC_IND)) = (char)offset;
value = *((volatile uint32_t *)(CONFIG_IOAPIC_BASE_ADDRESS + 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
* @return N/A
*/
static void __IoApicSet(int32_t offset, uint32_t value)
{
int key; /* interrupt lock level */
/* lock interrupts to ensure indirect addressing works "atomically" */
key = irq_lock();
*(volatile char *)(CONFIG_IOAPIC_BASE_ADDRESS + IOAPIC_IND) = (char)offset;
*((volatile uint32_t *)(CONFIG_IOAPIC_BASE_ADDRESS + 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
*/
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
* @return N/A
*/
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
* @return N/A
*/
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
* @return N/A
*/
static void _IoApicRedUpdateLo(unsigned int irq,
uint32_t value,
uint32_t mask)
{
ioApicRedSetLo(irq, (ioApicRedGetLo(irq) & ~mask) | (value & mask));
}
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
struct device_pm_ops ioapic_pm_ops = {
.suspend = ioapic_suspend,
.resume = ioapic_resume
};
SYS_INIT_PM("ioapic", _ioapic_init, &ioapic_pm_ops, PRIMARY,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#else
SYS_INIT(_ioapic_init, PRIMARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif