/* i8259Intr.c - Intel 8259A PIC (Programmable Interrupt Controller) driver */ /* * Copyright (c) 1993-2008,2014 Wind River Systems, Inc. * * The right to copy, distribute, modify or otherwise make use * of this software may be licensed only pursuant to the terms * of an applicable Wind River license agreement. */ /* modification history -------------------- 01x,24sep14,j_l VXW6-83369 - Use INCLUDE_SYMMETRIC_IO_MODE 01w,27aug08,jpb Renamed VSB header file 01v,18jun08,jpb Renamed _WRS_VX_SMP to _WRS_CONFIG_SMP. Added include path for kernel configurations options set in vsb. 01u,18jun07,scm remove reference to sysCpuNumGet, replace with vxCpuIndexGet... 01t,09oct06,scm remove sysBp dependencey for SMP... 01s,09nov05,mdo Fixes for apigen 01r,07may04,zmm Fix diab compiler warnings. 01q,11nov02,hdn fixed the IRQ15 spurious interrupt handling (spr 74209) 01p,16apr02,hdn added support for the Special Fully Nested Mode, IRQ0 Early EOI Mode, IRQ0 Special Mask Mode, AEOI Mode (spr 76411) 01o,29mar02,hdn added forgotten back slash to the optimized code 01n,21mar02,hdn fixed doc build by removing comments in the optimized code 01m,25feb02,hdn updated i8259Init() for HTT (spr 73738) added optimized version of EOI routines 01l,11sep01,hdn updated comment regarding the spr-28547 fix. 01k,27aug99,hdn fixed a bug for stray interrupt in i8259IntBoi(SPR 28547). 01j,11oct98,ms fixed compiler warning via typecast 01i,04jun98,hdn changed a method to exit i8259IntBoi(). 01h,25may98,hdn changed function name starting from "i8259". added i8259IntEoiMaster(), i8259IntEoiSlave(), i8259IntBoi(). removed sysIntEOI(). 01g,17mar97,hdn added sysIntLock(), sysIntUnlock() for the system mode. 01f,25jun96,hdn added sysIntLevel() for windview. 01e,23may96,wlf doc: cleanup. 01d,14jun95,hdn renamed sysEndOfInt to sysIntEOI. moved global function prototypes to sysLib.h. 01c,08aug94,hdn stopped toggling IRQ9 in enabling and disabling. 01b,22apr94,hdn made IRQ9 off in the initialization. moved sysVectorIRQ0 to sysLib.c. 01a,05sep93,hdn written. */ /* DESCRIPTION This module is a driver for the Intel 8259A PIC (Programmable Interrupt Controller). The Intel 8259A handles up to 8 vectored priority interrupts for the CPU. It is cascadable for up to 64 vectored priority interrupts, though this driver assumes two cascaded 8259A. It is initialized for "Fully Nested Mode", "Non-Specific EOI" mode. 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 service routine. While the IS bit is set, all further interrupts of the same or lower priority are inhibited, while higher level interrupts are allowed. The i8259IntEoiSlaveNfnm() routine is used to issue the EOI command. The PICs in a PC typically operate in this mode (normal nested mode). In this mode, while the slave PIC is being serviced by the master PIC, the slave PIC blocks all higher priority interrupt requests. Alternatively, to allow interrupts of a higher priority, enable the Special Fully Nested Mode. Special Fully Nested Mode: I8259_SPECIAL_FULLY_NESTED_MODE. This mode is similar to the Fully Nested Mode with the following exceptions: 1) When an interrupt request from a slave PIC is in service, the slave is not locked out from the master's priority logic and further interrupt requests from higher priority IRs within the slave will be recognized by the master and will initiate interrupts to the processor. 2) When exiting the interrupt service routine, the software must check whether or not the interrupt serviced was the only interrupt request from the slave. If it was the only interrupt request, a non-specific EOI is sent to the master. If not, no EOI is sent. This is implemented by the i8259EoiSlaveSfnm() routine. 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. Spurious/Phantom Interrupt: The IR inputs must remain high until after the falling edge of the first INTA. If the IR input goes low before this time, a DEFAULT(Spurious/Phantom) IR7 will occur when the CPU acknowledges the interrupt. The interrupt handler should simply return without sending an EOI command. The PIC(8259A) IRQ0 is hard wired to the PIT(8253) channel 0 in a PC motherboard. IRQ0 is the highest priority in the 8259A interrupt controller. Thus, the system clock interrupt handler blocks all lower level interrupts. This may cause a delay of the lower level interrupts in some situations even though the system clock interrupt handler finishes its job without any delay. This is quite natural from the hardware point of view, but may not be ideal from the application software standpoint. The following modes are supplied to mitigate this situation by providing corresponding configuration macros in the BSP. The three modes are mutually exclusive. Early EOI Issue in IRQ0 ISR. In this mode, the EOI command is issued before the IRQ0 system clock interrupt service routine starts the kernel work. This lowers the IRQ0 ISR blocking level to the next lower level. If no IRQs are in service, the next lower level is the lowest level. If IRQn is in service, the next lower level corresponds to the next lower priority. As a result, the kernel work in the system clock interrupt service routine can be interrupted by an interrupt with a higher priority than the blocking level. The i8259IntBoiEem() routine is called before the interrupt service routine, and no EOI is sent after the interrupt service routine. Special Mask Mode in IRQ0 ISR. In this mode, the Special Mask Mode is used in the IRQ0 system clock service routine. This lowers the blocking level to the specified level (currently hard coded to the lowest level). The i8259IntBoiSmm() routine is called before the interrupt service routine, and the i8259IntEoiSmm() routine is called after the interrupt service routine. Automatic EOI Mode: I8259_AUTO_EOI. This mode provides no nested multi-level interrupt structure in PIC1. The EOI command is automatically sent to the master PIC at the end of the interrupt acknowledge cycle. Thus, no software intervention is needed. The i8259IntBoi() routine is called before the IRQ7 and IRQ15 interrupt service routines. Either the i8259IntEoiSlaveNfnm() routine or the i8259IntEoiSlaveSfnm() routine is called after the slave PIC's interrupt service routine. SEE ALSO: pc386/target.ref INCLUDE FILES: i8259.h */ #include #include /* externs */ IMPORT void intBoiExit (); IMPORT UINT32 sysStrayIntCount; #ifndef _WRS_CONFIG_SMP IMPORT BOOL sysBp; /* TRUE(default) for BP, FALSE for AP */ #endif /* _WRS_CONFIG_SMP */ /* defines */ #define I8259_EOI_OPTIMIZED /* use the optimized version */ /* globals */ /* local */ #if !defined(SYMMETRIC_IO_MODE) && !defined(INCLUDE_SYMMETRIC_IO_MODE) LOCAL INT8 i8259IntMask1; /* interrupt mask for PIC1 */ LOCAL INT8 i8259IntMask2; /* interrupt mask for PIC2 */ LOCAL INT8 i8259Mask = 0; /* interrupt mask for PIC1 */ #endif /* !SYMMETRIC_IO_MODE && !INCLUDE_SYMMETRIC_IO_MODE */ /* forward static functions */ /******************************************************************************* * * i8259Init - initialize the PIC * * This routine initializes the PIC. * * RETURNS: N/A * * ERRNO */ VOID i8259Init (void) { UINT8 icw4 = 0x01; /* return if it is not the Boot Processor (BP) */ #ifndef _WRS_CONFIG_SMP if (sysBp == FALSE) #else if (vxCpuIndexGet() != 0) #endif /* _WRS_CONFIG_SMP */ return; /* initialize the PIC (Programmable Interrupt Controller) */ sysOutByte (PIC_port1 (PIC1_BASE_ADR),0x11); /* ICW1 */ sysOutByte (PIC_port2 (PIC1_BASE_ADR),INT_NUM_IRQ0); /* ICW2 */ sysOutByte (PIC_port2 (PIC1_BASE_ADR),0x04); /* ICW3 */ #ifdef I8259_SPECIAL_FULLY_NESTED_MODE icw4 |= 0x10; /* SFNM */ #endif /* I8259_SPECIAL_FULLY_NESTED_MODE */ #ifdef I8259_AUTO_EOI icw4 |= 0x02; /* AEOI */ #endif /* I8259_AUTO_EOI */ sysOutByte (PIC_port2 (PIC1_BASE_ADR),icw4); /* ICW4 */ sysOutByte (PIC_port1 (PIC2_BASE_ADR),0x11); /* ICW1 */ sysOutByte (PIC_port2 (PIC2_BASE_ADR),(char) (INT_NUM_IRQ0+8)); /* ICW2 */ sysOutByte (PIC_port2 (PIC2_BASE_ADR),0x02); /* ICW3 */ sysOutByte (PIC_port2 (PIC2_BASE_ADR),0x01); /* ICW4 */ /* disable interrupts */ sysOutByte (PIC_IMASK (PIC1_BASE_ADR), (char)0xfb); sysOutByte (PIC_IMASK (PIC2_BASE_ADR), (char)0xff); } #if !defined(SYMMETRIC_IO_MODE) && !defined(INCLUDE_SYMMETRIC_IO_MODE) /******************************************************************************* * * i8259IntBoiEem - issue EOI before the IRQ0 interrupt handler * * This routine is called before the IRQ0 interrupt handler that is PIT(8253) * channel 0 system clock interrupt handler in the Early EOI Mode. * * RETURNS: N/A * * ERRNO */ VOID i8259IntBoiEem ( int irqNo /* IRQ number of the interrupt */ ) { INT32 oldLevel = intLock (); /* LOCK INTERRUPT */ sysOutByte (PIC_IACK (PIC1_BASE_ADR), 0x20); /* NS EOI to PIC1 */ intUnlock (oldLevel); /* UNLOCK INTERRUPT */ } /******************************************************************************* * * i8259IntBoiSmm - enable Special Mask Mode before the IRQ0 interrupt handler * * This routine is called before the IRQ0 interrupt handler that is PIT(8253) * channel 0 system clock interrupt handler, in the Special Mask Mode. * * RETURNS: N/A * * ERRNO */ VOID i8259IntBoiSmm ( int irqNo /* IRQ number of the interrupt */ ) { INT32 oldLevel = intLock (); /* LOCK INTERRUPT */ sysOutByte (PIC_port1 (PIC1_BASE_ADR), 0x68); /* enable SMM PIC1 */ i8259Mask = sysInByte (PIC_IMASK (PIC1_BASE_ADR)); /* save int mask */ sysOutByte (PIC_IMASK (PIC1_BASE_ADR), 0x01); /* unlock except IRQ0 */ intUnlock (oldLevel); /* UNLOCK INTERRUPT */ } /******************************************************************************* * * i8259IntBoi - detect whether it is spurious interrupt or not * * This routine is called before the user's interrupt handler to detect the * spurious interrupt. * * RETURNS: N/A * * ERRNO */ VOID i8259IntBoi ( INT32 irqNo /* IRQ number of the interrupt */ ) { INT32 oldLevel; INT8 inserviceReg; /* we are interested in IRQ7 and IRQ15 */ if ((irqNo != 7) && (irqNo != 15)) return; /* if ISR bit is not set, we change the return address */ oldLevel = intLock (); /* LOCK INTERRUPT */ if (irqNo == 7) { sysOutByte (PIC_port1 (PIC1_BASE_ADR), 0x0b); inserviceReg = sysInByte (PIC_port1 (PIC1_BASE_ADR)); } else { sysOutByte (PIC_port1 (PIC2_BASE_ADR), 0x0b); inserviceReg = sysInByte (PIC_port1 (PIC2_BASE_ADR)); /* check bit7 for spurious interrupt from the slave PIC IRQ15 */ if ((inserviceReg & 0x80) == 0) { /* make sure no IRQs in the slave PIC are in service */ if (inserviceReg == 0) { /* check the master PIC's in-service register */ sysOutByte (PIC_port1 (PIC1_BASE_ADR), 0x0b); inserviceReg = sysInByte (PIC_port1 (PIC1_BASE_ADR)); /* send non-specific EOI to the master PIC IRQ2 */ if (inserviceReg & 0x04) sysOutByte (PIC_IACK (PIC1_BASE_ADR), I8259_EOI); } } } intUnlock (oldLevel); /* UNLOCK INTERRUPT */ /* * another implementation idea... * * *((UINT32 *)&irqNo - 1) = (UINT32)intBoiExit; * This changes the return addr on the stack. This is architecture * specific and tricky. Thus making following change may be good idea. * * - let this routine return OK or ERROR * - let interrupt stub code check the ret value and jump to intExit. * * New code would be like this: * : * if ((irqNo != 7) && (irqNo != 15)) * return (OK); * : * if ((inserviceReg & 0x80) == 0) * check bit7 for IRQ7 and IRQ15 * * { * sysStrayIntCount++; * increment the counter * * return (ERROR); * } * } * * 00 e8 kk kk kk kk call _intEnt * tell kernel * 05 50 pushl %eax * save regs * 06 52 pushl %edx * 07 51 pushl %ecx * 08 68 pp pp pp pp pushl $_parameterBoi * push BOI param * 13 e8 rr rr rr rr call _routineBoi * call BOI routine * addl $4, %esp * cmpl $0, %eax * jne intConnectCode0 * 18 68 pp pp pp pp pushl $_parameter * push param * 23 e8 rr rr rr rr call _routine * call C routine * addl $4, %esp * 28 68 pp pp pp pp pushl $_parameterEoi * push EOI param * 33 e8 rr rr rr rr call _routineEoi * call EOI routine * 38 83 c4 04 addl $4, %esp * pop param * intConnectCode0: * 41 59 popl %ecx * restore regs * 42 5a popl %edx * 43 58 popl %eax * 44 e9 kk kk kk kk jmp _intExit * exit via kernel */ if ((inserviceReg & 0x80) == 0) /* check bit7 for IRQ7 and IRQ15 */ { sysStrayIntCount++; /* increment the counter */ *((UINT32 *)&irqNo - 1) = (UINT32)intBoiExit; /* change return addr */ } } /******************************************************************************* * * i8259IntEoiSmm - disable Special Mask Mode with EOI signal to the master PIC * * This routine is called at the end of the IRQ0 interrupt handler that is * PIT(8253) channel 0 system clock interrupt handler in the Special Mask Mode. * * RETURNS: N/A * * ERRNO */ VOID i8259IntEoiSmm ( int irqNo /* IRQ number to send EOI */ ) { INT32 oldLevel = intLock (); /* LOCK INTERRUPT */ sysOutByte (PIC_IMASK (PIC1_BASE_ADR), i8259Mask); /* restore old mask */ sysOutByte (PIC_port1 (PIC1_BASE_ADR), 0x08); /* disable SMM PIC1 */ sysOutByte (PIC_IACK (PIC1_BASE_ADR), 0x20); /* NS EOI to PIC1 */ intUnlock (oldLevel); /* UNLOCK INTERRUPT */ } /******************************************************************************* * * i8259IntEoiMaster - send EOI(end of interrupt) signal to the master PIC. * * This routine is called at the end of the interrupt handler. * * RETURNS: N/A * * ERRNO */ VOID i8259IntEoiMaster ( INT32 irqNo /* IRQ number to send EOI */ ) { #ifdef I8259_EOI_OPTIMIZED /* optimized version */ WRS_ASM (" \ pushfl; \ cli; \ movl $0x20, %edx; \ movl $0x20, %eax; \ outb %al, %dx; \ popfl; \ "); #else /* portable version */ INT32 oldLevel = intLock (); /* LOCK INTERRUPTS */ sysOutByte (PIC_IACK (PIC1_BASE_ADR), I8259_EOI); /* non-specific EOI */ intUnlock (oldLevel); /* UNLOCK INTERRUPTS */ #endif /* I8259_EOI_OPTIMIZED */ } /******************************************************************************* * * i8259IntEoiSlave - 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. This is kept for the backward compatibility. * * RETURNS: N/A * * ERRNO */ VOID i8259IntEoiSlave ( INT32 irqNo /* IRQ number to send EOI */ ) { #ifdef I8259_EOI_OPTIMIZED /* optimized version */ WRS_ASM (" \ pushfl; \ cli; \ movl $0xa0, %edx; \ movl $0x20, %eax; \ outb %al, %dx; \ movl $0x20, %edx; \ outb %al, %dx; \ popfl; \ "); #else /* portable version */ INT32 oldLevel = intLock (); /* LOCK INTERRUPTS */ sysOutByte (PIC_IACK (PIC2_BASE_ADR), I8259_EOI); /* non-specific EOI */ sysOutByte (PIC_IACK (PIC1_BASE_ADR), I8259_EOI); /* non-specific EOI */ intUnlock (oldLevel); /* UNLOCK INTERRUPTS */ #endif /* I8259_EOI_OPTIMIZED */ } /******************************************************************************* * * i8259IntEoiSlaveNfnm - 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. * * RETURNS: N/A * * ERRNO */ VOID i8259IntEoiSlaveNfnm ( INT32 irqNo /* IRQ number to send EOI */ ) { #ifdef I8259_EOI_OPTIMIZED /* optimized version */ WRS_ASM (" \ pushfl; \ cli; \ movl $0xa0, %edx; \ movl $0x20, %eax; \ outb %al, %dx; \ movl $0x20, %edx; \ outb %al, %dx; \ popfl; \ "); #else /* portable version */ INT32 oldLevel = intLock (); /* LOCK INTERRUPTS */ sysOutByte (PIC_IACK (PIC2_BASE_ADR), I8259_EOI); /* non-specific EOI */ sysOutByte (PIC_IACK (PIC1_BASE_ADR), I8259_EOI); /* non-specific EOI */ intUnlock (oldLevel); /* UNLOCK INTERRUPTS */ #endif /* I8259_EOI_OPTIMIZED */ } /******************************************************************************* * * i8259IntEoiSlaveSfnm - send EOI(end of interrupt) signal to the slave PIC. * * This routine is called at the end of the interrupt handler in the Special * Fully Nested Mode. * * RETURNS: N/A * * ERRNO */ VOID i8259IntEoiSlaveSfnm ( int irqNo /* IRQ number to send EOI */ ) { INT8 inserviceReg; INT32 oldLevel = intLock (); /* LOCK INTERRUPT */ sysOutByte (PIC_IACK (PIC2_BASE_ADR), 0x20); /* NS EOI to PIC2 */ sysOutByte (PIC_port1 (PIC2_BASE_ADR), 0x0b); /* issue OCW3 */ inserviceReg = sysInByte (PIC_port1 (PIC2_BASE_ADR)); /* read ISR */ if (inserviceReg == 0) sysOutByte (PIC_IACK (PIC1_BASE_ADR), 0x20); /* NS EOI to PIC1 */ intUnlock (oldLevel); /* UNLOCK INTERRUPT */ } /******************************************************************************* * * i8259IntDisable - disable a specified PIC interrupt input line * * This routine disables a specified PIC interrupt input line. * * RETURNS: OK, always. * * SEE ALSO: i8259IntEnable() * * ERRNO */ LOCAL STATUS i8259IntDisable ( INT32 irqNo /* IRQ number to disable */ ) { if (irqNo < 8) { sysOutByte (PIC_IMASK (PIC1_BASE_ADR), sysInByte (PIC_IMASK (PIC1_BASE_ADR)) | (1 << irqNo)); } else { sysOutByte (PIC_IMASK (PIC2_BASE_ADR), sysInByte (PIC_IMASK (PIC2_BASE_ADR)) | (1 << (irqNo - 8))); } return (OK); } /******************************************************************************* * * i8259IntEnable - enable a specified PIC interrupt input line * * This routine enables a specified PIC interrupt input line. * * RETURNS: OK, always. * * SEE ALSO: i8259IntDisable() * * ERRNO */ LOCAL STATUS i8259IntEnable ( INT32 irqNo /* IRQ number to enable */ ) { if (irqNo < 8) { sysOutByte (PIC_IMASK (PIC1_BASE_ADR), sysInByte (PIC_IMASK (PIC1_BASE_ADR)) & ~(1 << irqNo)); } else { sysOutByte (PIC_IMASK (PIC2_BASE_ADR), sysInByte (PIC_IMASK (PIC2_BASE_ADR)) & ~(1 << (irqNo - 8))); } return (OK); } /******************************************************************************* * * i8259IntLock - lock out all PIC interrupts * * This routine saves the mask and locks out all PIC interrupts. * It should be called in the interrupt disable state(IF bit is 0). * * RETURNS: N/A * * SEE ALSO: i8259IntUnlock() * * ERRNO */ LOCAL VOID i8259IntLock (void) { i8259IntMask1 = sysInByte (PIC_IMASK (PIC1_BASE_ADR)); i8259IntMask2 = sysInByte (PIC_IMASK (PIC2_BASE_ADR)); sysOutByte (PIC_IMASK (PIC1_BASE_ADR), (char)0xff); sysOutByte (PIC_IMASK (PIC2_BASE_ADR), (char)0xff); } /******************************************************************************* * * i8259IntUnlock - unlock the PIC interrupts * * This routine restores the mask and unlocks the PIC interrupts * It should be called in the interrupt disable state(IF bit is 0). * * RETURNS: N/A * * SEE ALSO: i8259IntLock() * * ERRNO */ LOCAL VOID i8259IntUnlock (void) { sysOutByte (PIC_IMASK (PIC1_BASE_ADR), i8259IntMask1); sysOutByte (PIC_IMASK (PIC2_BASE_ADR), i8259IntMask2); } #if FALSE /* * There is a function sysIntLevel() which gets the interrupt level from the * interrupt stub, so we don't need it anymore. */ /******************************************************************************* * * i8259IntLevel - Get IRQ number by reading Interrupt Service Register. * * This routine is called to get an IRQ number in service. * * RETURNS: 0 - 15 * * ERRNO */ LOCAL INT32 i8259IntLevel (void) { INT32 inserviceReg; INT32 irq; INT32 oldLevel; INT32 retry; oldLevel = intLock (); for (retry=0; retry < 1; retry ++) { sysOutByte (PIC_port1 (PIC1_BASE_ADR), 0x0b); inserviceReg = sysInByte (PIC_port1 (PIC1_BASE_ADR)); for (irq=0; irq < 8; irq++) if ((inserviceReg & 1) && (irq != 2)) goto i8259IntLevelExit; else inserviceReg >>= 1; sysOutByte (PIC_port1 (PIC2_BASE_ADR), 0x0b); inserviceReg = sysInByte (PIC_port1 (PIC2_BASE_ADR)); for (irq=8; irq < 16; irq++) if (inserviceReg & 1) goto i8259IntLevelExit; else inserviceReg >>= 1; } i8259IntLevelExit: intUnlock (oldLevel); return (irq); } #endif /* FALSE */ #endif /* !SYMMETRIC_IO_MODE && !INCLUDE_SYMMETRIC_IO_MODE */