zephyr/drivers/interrupt_controller/i8259.c

328 lines
9.6 KiB
C

/* i8259.c - Intel 8259A PIC (Programmable Interrupt Controller) driver */
/*
* Copyright (c) 2010-2015 Wind River Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2) Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3) Neither the name of Wind River Systems nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
DESCRIPTION
This module implements a kernel device driver for the Intel 8259A PIC
(Programmable Interrupt Controller). In summary, the i8259A supports
up to 8 vectored priority interrupts. Cascading up to 8 PICs allows support
of up to 64 vectored priority interrupts. This device driver assumes two
cascaded 8259A (which is the standard configuration for desktop PC platforms).
This driver initializes the PICs to operate in fully nested mode, and
issues non-specified EOI mode (an exported routine that must be invoked
just prior to an ISR returning).
The following is an description of "Fully Nested Mode":
In this mode, interrupt requests are ordered in priority from 0 through 7
(0 is the highest priority). When an interrupt is acknowledged, the highest
priority request is determined and its vector is placed on the bus.
Additionally, a bit of the Interrupt Service (IS) register is set. This bit
remains set until the microprocessor issues an EOI command immediately before
returning from the interrupt service routine (ISR). While the IS bit is set,
all further interrupts of the same or lower priority are inhibited, while
higher level interrupts are allowed. The _i8259_eoi() routine is used by
the ISR to issue an EOI command. The PICs in a PC typically operate
in this mode. In this mode, while the slave PIC is being serviced by the
master PIC, the slave PIC blocks all higher priority interrupt requests.
Non-Specific EOI: When the 8259A is operated in the Fully Nested Mode, it
can determine which IS bit to reset on EOI. When a non-specific EOI
command is issued, the 8259A will automatically reset the highest IS bit of
those that are set, since in the fully nested mode the highest IS level is
the last level acknowledged and serviced.
*/
/*
* A board support package's board.h header must provide definitions for the
* following constants:
*
* PIC_REG_ADDR_INTERVAL
* PIC_MASTER_BASE_ADRS
* PIC_SLAVE_BASE_ADRS
* INT_VEC_IRQ0
*
* ...and the following register access macros:
*
* PLB_BYTE_REG_WRITE
* PLB_BYTE_REG_READ
*/
/*
* The _i8259_boi_master and _i8259_boi_slave functions are platform
* specific and implemented in assembler
*/
#include <nanokernel.h>
#include <arch/cpu.h>
#include <toolchain.h>
#include <sections.h>
#include <drivers/pic.h>
#include <board.h>
#define OCW3_DEF 0x08 /* 3rd default control word */
#define OCW3_PCB 0x04 /* Polling Control Bit */
#define OCW3_ISR 0x03 /* Read in-service reg */
#define OCW3_IRR 0x02 /* Read inter request reg */
#ifndef CONFIG_SHUTOFF_PIC
unsigned int _i8259_spurious_interrupt_count =
0; /* track # of spurious interrupts */
/*
* The public interface for enabling/disabling a specific IRQ for the IA-32
* architecture is defined as follows in arch/nanokernel/Intel/arch.h
*
* extern void irq_enable (unsigned int irq);
* extern void irq_disable (unsigned int irq);
*
* Provide the symbolic alias 'irq_enable' and 'irq_disable'
* to the Intel 8259A device driver specific routines _i8259_irq_enable()
* and _i8259_irq_disable(), respectively.
*/
FUNC_ALIAS(_i8259_irq_enable, irq_enable, void);
FUNC_ALIAS(_i8259_irq_disable, irq_disable, void);
#endif /* CONFIG_SHUTOFF_PIC */
/**
*
* @brief Initialize the Intel 8259A PIC device driver
*
* This routine initializes the Intel 8259A PIC device driver and the device
* itself.
*
* @return N/A
*/
void _i8259_init(void)
{
/*
* Initialize the Master PIC device.
*
* Whenever a command is issued with A0=0 and D4=1, this is interpreted
* as Initialization Command Word 1 (ICW1).
* D0 = 1 (ICW4 required)
* D1 = 0 (Cascaded PIC configuration)
* D2 = X (ADI: only used in MCS-80/85 mode)
* D3 = 0 (level triggered interrupt mode: edge triggered)
* D4 = 1 (initiates initialization sequence: see above)
* D5 -> D7 = X (only used in MCS-80/85 mode)
*/
PLB_BYTE_REG_WRITE(0x11, PIC_PORT1(PIC_MASTER_BASE_ADRS));
/*
* ICW2 = upper 5 bits of vector presented by 8259 during /INTA cycle
*/
PLB_BYTE_REG_WRITE(INT_VEC_IRQ0, PIC_PORT2(PIC_MASTER_BASE_ADRS));
/*
* ICW3 (Master): indicate which IRQ has slave connection. On PC
* systems
* the slave PIC is connect to IRQ2.
*/
PLB_BYTE_REG_WRITE(0x04, PIC_PORT2(PIC_MASTER_BASE_ADRS));
/*
* ICW4
* D0 = 1 (Mode: 0=8085, 1=8086)
* D1 = 0 (AEOI: 1=Auto End of Interrupt, 0=Normal)
* D2 = X (Master/Slave in buffered mode: 1=Master, 0=Slave)
* D3 = 0 (Buffer mode: 0 = non-buffered mode)
* D4 = 0 (SFNM: 1= Special fully nested mode)
*/
PLB_BYTE_REG_WRITE(0x01, PIC_PORT2(PIC_MASTER_BASE_ADRS));
/*
* Initialize the Slave PIC device.
*/
PLB_BYTE_REG_WRITE(0x11, PIC_PORT1(PIC_SLAVE_BASE_ADRS)); /* ICW1 */
PLB_BYTE_REG_WRITE(INT_VEC_IRQ0 + 8, PIC_PORT2(PIC_SLAVE_BASE_ADRS));
PLB_BYTE_REG_WRITE(0x02, PIC_PORT2(PIC_SLAVE_BASE_ADRS)); /* ICW3 */
PLB_BYTE_REG_WRITE(0x01, PIC_PORT2(PIC_SLAVE_BASE_ADRS)); /* ICW4 */
/* disable interrupts */
PLB_BYTE_REG_WRITE(0xfb, PIC_IMASK(PIC_MASTER_BASE_ADRS));
PLB_BYTE_REG_WRITE(0xff, PIC_IMASK(PIC_SLAVE_BASE_ADRS));
}
#ifndef CONFIG_SHUTOFF_PIC
/**
*
* @brief Send EOI(end of interrupt) signal to the master PIC.
*
* This routine is called at the end of the interrupt handler.
*
* @return N/A
*
* ERRNO
*/
void _i8259_eoi_master(unsigned int irq /* IRQ number to
send EOI: unused */
)
{
ARG_UNUSED(irq);
/* no need to disable interrupts since only writing to a single port */
PLB_BYTE_REG_WRITE(I8259_EOI, PIC_IACK(PIC_MASTER_BASE_ADRS));
}
/**
*
* @brief Send EOI(end of interrupt) signal to the slave PIC.
*
* This routine is called at the end of the interrupt handler in the Normal
* Fully Nested Mode.
*
* @return N/A
*
* ERRNO
*/
void _i8259_eoi_slave(unsigned int irq /* IRQ number to
send EOI: unused */
)
{
ARG_UNUSED(irq);
/* lock interrupts */
__asm__ volatile(
"pushfl;\n\t"
"cli;\n\t");
PLB_BYTE_REG_WRITE(I8259_EOI, PIC_IACK(PIC_SLAVE_BASE_ADRS));
PLB_BYTE_REG_WRITE(I8259_EOI, PIC_IACK(PIC_MASTER_BASE_ADRS));
/* unlock interrupts */
__asm__ volatile("popfl;\n\t");
}
/**
*
* @brief Enable/disable a specified PIC interrupt input line
*
* This routine enables or disables a specified PIC interrupt input line. To
* enable an interrupt input line, the parameter <enable> must be non-zero.
*
* The nanokernel exports the irq_enable() and irq_disable()
* APIs (mapped to _i8259_irq_enable() and _i8259_irq_disable(), respectively).
* This function is called by _i8259_irq_enable() and _i8259_irq_disable() to
* perform the actual enabling/disabling of an IRQ to minimize footprint.
*
* @return N/A
*
* see also: _i8259_irq_disable()/_i8259_irq_enable
*/
static void __I8259IntEnable(
unsigned int irq, /* IRQ number to enable */
unsigned char enable /* 0 = disable, otherwise enable */
)
{
unsigned char imask;
unsigned char *picBaseAdrs;
if (irq < 8)
picBaseAdrs = (unsigned char *)PIC_MASTER_BASE_ADRS;
else
picBaseAdrs = (unsigned char *)PIC_SLAVE_BASE_ADRS;
/*
* BSPs that utilize this interrupt controller driver virtualize IRQs
* as follows:
*
* - IRQ0 to IRQ7 are provided by the master i8259 PIC
* - IRQ8 to IRQ15 are provided by the slave i8259 PIC
*
* Thus translate the virtualized IRQ parameter to a physical IRQ
*/
irq %= 8;
imask = PLB_BYTE_REG_READ(PIC_IMASK(picBaseAdrs));
if (enable == 0)
PLB_BYTE_REG_WRITE(imask | (1 << irq), PIC_IMASK(picBaseAdrs));
else
PLB_BYTE_REG_WRITE(imask & ~(1 << irq), PIC_IMASK(picBaseAdrs));
}
/**
*
* @brief Disable a specified PIC interrupt input line
*
* This routine disables a specified PIC interrupt input line.
*
* @return N/A
*
* SEE ALSO: _i8259_irq_enable()
*/
void _i8259_irq_disable(unsigned int irq /* IRQ number to disable */
)
{
return __I8259IntEnable(irq, 0);
}
/**
*
* @brief Enable a specified PIC interrupt input line
*
* This routine enables a specified PIC interrupt input line.
*
* @return N/A
*
* SEE ALSO: _i8259_irq_disable()
*/
void _i8259_irq_enable(unsigned int irq /* IRQ number to enable */
)
{
return __I8259IntEnable(irq, 1);
}
#endif /* CONFIG_SHUTOFF_PIC */