[undi] Ensure forward progress is made even if UNDI IRQ is stuck

If the UNDI interrupt remains constantly asserted (e.g. because the
BIOS has enabled interrupts for an unrelated device sharing the same
IRQ, or because of bugs in the OEM UNDI driver), then we may get stuck
in an interrupt storm.

We cannot safely chain to the previous interrupt handler (which could
plausibly handle an unrelated device interrupt) since there is no
well-defined behaviour for previous interrupt handlers.  We have
observed BIOSes to provide default interrupt handlers that variously
do nothing, send EOI, disable the IRQ, or crash the system.

Fix by disabling the UNDI interrupt whenever our handler is triggered,
and rearm it as needed when polling the network device.  This ensures
that forward progress continues to be made even if something causes
the interrupt to be constantly asserted.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/1417/merge
Michael Brown 2025-03-26 13:58:22 +00:00
parent 4134280bcd
commit 0b606221cb
2 changed files with 32 additions and 1 deletions

View File

@ -34,7 +34,15 @@ undiisr:
cmpw $0, undinet_entry_point
je chain
/* Mask interrupt and set rearm flag */
movw undiisr_imr, %dx
inb %dx, %al
orb undiisr_bit, %al
outb %al, %dx
movb %al, undiisr_rearm
/* Issue UNDI API call */
movw %ds, %ax
movw %ax, %es
movw $undinet_params, %di
movw $PXENV_UNDI_ISR, %bx

View File

@ -373,6 +373,18 @@ extern void undiisr ( void );
uint8_t __data16 ( undiisr_irq );
#define undiisr_irq __use_data16 ( undiisr_irq )
/** IRQ mask register */
uint16_t __data16 ( undiisr_imr );
#define undiisr_imr __use_data16 ( undiisr_imr )
/** IRQ mask bit */
uint8_t __data16 ( undiisr_bit );
#define undiisr_bit __use_data16 ( undiisr_bit )
/** IRQ rearm flag */
uint8_t __data16 ( undiisr_rearm );
#define undiisr_rearm __use_data16 ( undiisr_rearm )
/** IRQ chain vector */
struct segoff __data16 ( undiisr_next_handler );
#define undiisr_next_handler __use_data16 ( undiisr_next_handler )
@ -395,6 +407,9 @@ static void undinet_hook_isr ( unsigned int irq ) {
assert ( undiisr_irq == 0 );
undiisr_irq = irq;
undiisr_imr = IMR_REG ( irq );
undiisr_bit = IMR_BIT ( irq );
undiisr_rearm = 0;
hook_bios_interrupt ( IRQ_INT ( irq ), ( ( intptr_t ) undiisr ),
&undiisr_next_handler );
}
@ -588,6 +603,14 @@ static void undinet_poll ( struct net_device *netdev ) {
* support interrupts.
*/
if ( ! undinet_isr_triggered() ) {
/* Rearm interrupt if needed */
if ( undiisr_rearm ) {
undiisr_rearm = 0;
assert ( undinic->irq != 0 );
enable_irq ( undinic->irq );
}
/* Allow interrupt to occur */
profile_start ( &undinet_irq_profiler );
__asm__ __volatile__ ( "sti\n\t"