]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[undi] Ensure forward progress is made even if UNDI IRQ is stuck
authorMichael Brown <mcb30@ipxe.org>
Wed, 26 Mar 2025 13:58:22 +0000 (13:58 +0000)
committerMichael Brown <mcb30@ipxe.org>
Wed, 26 Mar 2025 14:56:20 +0000 (14:56 +0000)
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>
src/arch/x86/drivers/net/undiisr.S
src/arch/x86/drivers/net/undinet.c

index 8ba5c53549bece8f1f0c793739630b4b7e0db3e7..0b07cb396ad44b15b71d0cf1be1808bac353fb2d 100644 (file)
@@ -33,8 +33,16 @@ undiisr:
        /* Check that we have an UNDI entry point */
        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
index cdb39c74fae92a6fdf6ccdbea76af670bf69c077..60e7ad799a41662736fc9b6a9b18f7a0a8152e84 100644 (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"