diff options
author | Chris Johns <chrisj@rtems.org> | 2016-05-06 17:55:29 +1000 |
---|---|---|
committer | Chris Johns <chrisj@rtems.org> | 2016-05-11 11:45:01 +1000 |
commit | 93fb8797961f602ea70ac5b846a066b05db44b18 (patch) | |
tree | 5f0fa3a6427b178a0763ec8c6255895133e75214 /c/src/lib/libbsp/i386/shared/irq/irq.c | |
parent | i386/pc386: Fix printk with the console changes. (diff) | |
download | rtems-93fb8797961f602ea70ac5b846a066b05db44b18.tar.bz2 |
i386/pc386: Fix interrupt support.
Fix the interrupt and stop the spurious interrupt from happening.
The fix moves the EOI to C code and cleans that functionality out
of the asm part of the ISR handler.
The code checks the ISR and IRR registers on the enable.
Only ack the master for a slave IRQ if the slave has no other pending
requests.
Diffstat (limited to 'c/src/lib/libbsp/i386/shared/irq/irq.c')
-rw-r--r-- | c/src/lib/libbsp/i386/shared/irq/irq.c | 278 |
1 files changed, 205 insertions, 73 deletions
diff --git a/c/src/lib/libbsp/i386/shared/irq/irq.c b/c/src/lib/libbsp/i386/shared/irq/irq.c index 2bee7643a9..0511257fa1 100644 --- a/c/src/lib/libbsp/i386/shared/irq/irq.c +++ b/c/src/lib/libbsp/i386/shared/irq/irq.c @@ -19,6 +19,9 @@ #include <stdio.h> #include <inttypes.h> + +#include "elcr.h" + /* * pointer to the mask representing the additionnal irq vectors * that must be disabled when a particular entry is activated. @@ -27,138 +30,174 @@ * CAUTION : this table is accessed directly by interrupt routine * prologue. */ -rtems_i8259_masks irq_mask_or_tbl[BSP_IRQ_LINES_NUMBER]; +static rtems_i8259_masks irq_mask_or_tbl[BSP_IRQ_LINES_NUMBER]; -uint32_t irq_count[BSP_IRQ_LINES_NUMBER] = {0}; +/* + * Stats of interrupts dispatched. + */ +static uint32_t irq_count[BSP_IRQ_VECTOR_NUMBER] = {0}; +static uint32_t spurious_count; -uint32_t -BSP_irq_count_dump(FILE *f) -{ -uint32_t tot = 0; -int i; - if ( !f ) - f = stdout; - for ( i=0; i<BSP_IRQ_LINES_NUMBER; i++ ) { - tot += irq_count[i]; - fprintf(f,"IRQ %2u: %9"PRIu32"\n", i, irq_count[i]); - } - return tot; -} +/* + * Edge or level trigger interrupts. + */ +static enum intr_trigger irq_trigger[BSP_IRQ_LINES_NUMBER]; /*-------------------------------------------------------------------------+ -| Cache for 1st and 2nd PIC IRQ line's status (enabled or disabled) register. +| Cache for 1st and 2nd PIC IRQ line's mssk (enabled or disabled) register. +--------------------------------------------------------------------------*/ /* * lower byte is interrupt mask on the master PIC. * while upper bits are interrupt on the slave PIC. * This cache is initialized in ldseg.s */ -rtems_i8259_masks i8259s_cache = 0xFFFB; -rtems_i8259_masks i8259s_super_imr = 0xFFFB; +static rtems_i8259_masks i8259a_cache = 0xFFFB; + +/* + * Print the stats. + */ +uint32_t BSP_irq_count_dump(FILE *f) +{ + uint32_t tot = 0; + int i; + if ( !f ) + f = stdout; + fprintf(f,"SPURIOUS: %9"PRIu32"\n", spurious_count); + for ( i = 0; i < BSP_IRQ_VECTOR_NUMBER; i++ ) { + char type = '-'; + if (i < BSP_IRQ_LINES_NUMBER) + type = irq_trigger[i] == INTR_TRIGGER_EDGE ? 'E' : 'L'; + tot += irq_count[i]; + fprintf(f,"IRQ %2u: %c %9"PRIu32"\n", i, type, irq_count[i]); + } + return tot; +} + +/* + * Is the IRQ valid? + */ +static inline bool BSP_i8259a_irq_valid(const rtems_irq_number irqLine) +{ + return ((int)irqLine >= BSP_IRQ_VECTOR_LOWEST_OFFSET) && + ((int)irqLine <= BSP_IRQ_MAX_ON_i8259A); +} + +/* + * Read the IRR register. The default. + */ +static inline uint8_t BSP_i8259a_irq_int_request_reg(uint32_t ioport) +{ + uint8_t isr; + inport_byte(ioport, isr); + return isr; +} + +/* + * Read the ISR register. Keep the default of the IRR. + */ +static inline uint8_t BSP_i8259a_irq_in_service_reg(uint32_t ioport) +{ + uint8_t isr; + outport_byte(ioport, PIC_OCW3_SEL | PIC_OCW3_RR | PIC_OCW3_RIS); + inport_byte(ioport, isr); + outport_byte(ioport, PIC_OCW3_SEL | PIC_OCW3_RR); + return isr; +} /*-------------------------------------------------------------------------+ -| Function: BSP_irq_disable_at_i8259s +| Function: BSP_irq_disable_at_i8259a | Description: Mask IRQ line in appropriate PIC chip. -| Global Variables: i8259s_cache +| Global Variables: i8259a_cache | Arguments: vector_offset - number of IRQ line to mask. -| Returns: Nothing. +| Returns: 0 is OK. +--------------------------------------------------------------------------*/ -int BSP_irq_disable_at_i8259s (const rtems_irq_number irqLine) +static int BSP_irq_disable_at_i8259a(const rtems_irq_number irqLine) { unsigned short mask; rtems_interrupt_level level; - if ( ((int)irqLine < BSP_LOWEST_OFFSET) || - ((int)irqLine > BSP_MAX_ON_i8259S ) - ) - return 1; - rtems_interrupt_disable(level); mask = 1 << irqLine; - i8259s_cache |= mask; - i8259s_super_imr |= mask; + i8259a_cache |= mask; if (irqLine < 8) { - outport_byte(PIC_MASTER_IMR_IO_PORT, i8259s_cache & 0xff); + outport_byte(PIC_MASTER_IMR_IO_PORT, i8259a_cache & 0xff); } else { - outport_byte(PIC_SLAVE_IMR_IO_PORT, ((i8259s_cache & 0xff00) >> 8)); + outport_byte(PIC_SLAVE_IMR_IO_PORT, (i8259a_cache >> 8) & 0xff); } + rtems_interrupt_enable(level); return 0; } /*-------------------------------------------------------------------------+ -| Function: BSP_irq_enable_at_i8259s +| Function: BSP_irq_enable_at_i8259a | Description: Unmask IRQ line in appropriate PIC chip. -| Global Variables: i8259s_cache +| Global Variables: i8259a_cache | Arguments: irqLine - number of IRQ line to mask. | Returns: Nothing. +--------------------------------------------------------------------------*/ -int BSP_irq_enable_at_i8259s (const rtems_irq_number irqLine) +static int BSP_irq_enable_at_i8259a(const rtems_irq_number irqLine) { unsigned short mask; rtems_interrupt_level level; - - if ( ((int)irqLine < BSP_LOWEST_OFFSET) || - ((int)irqLine > BSP_MAX_ON_i8259S ) - ) - return 1; + uint8_t isr; + uint8_t irr; rtems_interrupt_disable(level); - mask = ~(1 << irqLine); - i8259s_cache &= mask; - i8259s_super_imr &= mask; + mask = 1 << irqLine; + i8259a_cache &= ~mask; if (irqLine < 8) { - outport_byte(PIC_MASTER_IMR_IO_PORT, i8259s_cache & 0xff); + isr = BSP_i8259a_irq_in_service_reg(PIC_MASTER_COMMAND_IO_PORT); + irr = BSP_i8259a_irq_int_request_reg(PIC_MASTER_COMMAND_IO_PORT); + outport_byte(PIC_MASTER_IMR_IO_PORT, i8259a_cache & 0xff); } else { - outport_byte(PIC_SLAVE_IMR_IO_PORT, ((i8259s_cache & 0xff00) >> 8)); + isr = BSP_i8259a_irq_in_service_reg(PIC_SLAVE_COMMAND_IO_PORT); + irr = BSP_i8259a_irq_int_request_reg(PIC_SLAVE_COMMAND_IO_PORT); + outport_byte(PIC_SLAVE_IMR_IO_PORT, (i8259a_cache >> 8) & 0xff); } + + if (((isr ^ irr) & mask) != 0) + printk("i386: isr=%x irr=%x\n", isr, irr); + rtems_interrupt_enable(level); return 0; } /* mask_irq */ -int BSP_irq_enabled_at_i8259s (const rtems_irq_number irqLine) -{ - unsigned short mask; - - if ( ((int)irqLine < BSP_LOWEST_OFFSET) || - ((int)irqLine > BSP_MAX_ON_i8259S ) - ) - return 1; - - mask = (1 << irqLine); - return (~(i8259s_cache & mask)); -} - /*-------------------------------------------------------------------------+ -| Function: BSP_irq_ack_at_i8259s +| Function: BSP_irq_ack_at_i8259a | Description: Signal generic End Of Interrupt (EOI) to appropriate PIC. | Global Variables: None. | Arguments: irqLine - number of IRQ line to acknowledge. | Returns: Nothing. +--------------------------------------------------------------------------*/ -int BSP_irq_ack_at_i8259s (const rtems_irq_number irqLine) +static int BSP_irq_ack_at_i8259a(const rtems_irq_number irqLine) { - if ( ((int)irqLine < BSP_LOWEST_OFFSET) || - ((int)irqLine > BSP_MAX_ON_i8259S ) - ) - return 1; + uint8_t slave_isr = 0; if (irqLine >= 8) { outport_byte(PIC_SLAVE_COMMAND_IO_PORT, PIC_EOI); + slave_isr = BSP_i8259a_irq_in_service_reg(PIC_SLAVE_COMMAND_IO_PORT); } - outport_byte(PIC_MASTER_COMMAND_IO_PORT, PIC_EOI); + + /* + * Only issue the EOI to the master if there are no more interrupts in + * service for the slave. i8259a data sheet page 18, The Special Fully Nested + * Mode, b. + */ + if (slave_isr == 0) + outport_byte(PIC_MASTER_COMMAND_IO_PORT, PIC_EOI); return 0; @@ -179,7 +218,7 @@ static rtems_irq_prio irqPrioTable[BSP_IRQ_LINES_NUMBER]={ */ 0,0, 255, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static void compute_i8259_masks_from_prio (void) @@ -208,22 +247,29 @@ static void compute_i8259_masks_from_prio (void) rtems_interrupt_enable(level); } -rtems_status_code bsp_interrupt_vector_enable(rtems_vector_number vector) +static inline bool bsp_interrupt_vector_is_valid(rtems_vector_number vector) { - BSP_irq_enable_at_i8259s(vector); + return BSP_i8259a_irq_valid((const rtems_irq_number) vector); +} +rtems_status_code bsp_interrupt_vector_enable(rtems_vector_number vector) +{ + if (bsp_interrupt_vector_is_valid(vector)) + BSP_irq_enable_at_i8259a(vector); return RTEMS_SUCCESSFUL; } rtems_status_code bsp_interrupt_vector_disable(rtems_vector_number vector) { - BSP_irq_disable_at_i8259s(vector); - + if (bsp_interrupt_vector_is_valid(vector)) + BSP_irq_disable_at_i8259a(vector); return RTEMS_SUCCESSFUL; } rtems_status_code bsp_interrupt_facility_initialize(void) { + int i; + /* * set up internal tables used by rtems interrupt prologue */ @@ -232,13 +278,99 @@ rtems_status_code bsp_interrupt_facility_initialize(void) /* * must enable slave pic anyway */ - BSP_irq_enable_at_i8259s(2); + BSP_irq_enable_at_i8259a(2); + + /* + * Probe the ELCR. + */ + elcr_probe(); + + for (i = 0; i < BSP_IRQ_LINES_NUMBER; i++) + irq_trigger[i] = elcr_read_trigger(i); return RTEMS_SUCCESSFUL; } -void C_dispatch_isr(int vector) +/* + * Global so the asm handler can call it. + */ +void BSP_dispatch_isr(int vector); + +void BSP_dispatch_isr(int vector) { - irq_count[vector]++; - bsp_interrupt_handler_dispatch(vector); + uint16_t old_imr = 0; + + if (vector < BSP_IRQ_VECTOR_NUMBER) { + /* + * Hardware? + */ + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + /* + * See if this is a spurious interrupt. + */ + if ((vector == 7 || vector == 15)) { + /* + * Only check it there no handler for 7 or 15. + */ + if (bsp_interrupt_handler_is_empty(vector)) { + /* + * Read the ISR register to see if IRQ 7/15 is really pending. + */ + uint8_t isr = BSP_i8259a_irq_in_service_reg(PIC_MASTER_COMMAND_IO_PORT); + if ((isr & (1 << 7)) == 0) { + ++spurious_count; + return; + } + } + } + + /* + * Save the current cached value for the IMR. It will have the bit for this + * vector clear. + */ + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + old_imr = i8259a_cache; + i8259a_cache |= irq_mask_or_tbl[vector]; + outport_byte(PIC_MASTER_IMR_IO_PORT, i8259a_cache & 0xff); + outport_byte(PIC_SLAVE_IMR_IO_PORT, (i8259a_cache >> 8) & 0xff); + } + + /* + * Do not use auto-EOI as some slave PIC do not work correctly. + */ + BSP_irq_ack_at_i8259a(vector); + } + + /* + * Count the interrupt. + */ + irq_count[vector]++; + + /* + * Allow nesting. + */ + __asm__ __volatile__("sti"); + + bsp_interrupt_handler_dispatch(vector); + + /* + * Disallow nesting. + */ + __asm__ __volatile__("cli"); + + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + /* + * Put the mask back but keep this vector masked if the trigger type is + * level. The driver or a thread level interrupt server needs to enable it + * again. + */ + if (vector <= BSP_IRQ_MAX_ON_i8259A) { + if (irq_trigger[vector] == INTR_TRIGGER_LEVEL) + old_imr |= 1 << vector; + i8259a_cache = old_imr; + outport_byte(PIC_MASTER_IMR_IO_PORT, i8259a_cache & 0xff); + outport_byte(PIC_SLAVE_IMR_IO_PORT, (i8259a_cache >> 8) & 0xff); + } + } + } } |