Report RX errors via netdev_rx_err()
[people/holger/gpxe.git] / src / arch / i386 / drivers / net / undinet.c
index 030a8b6..03515fd 100644 (file)
 #include <pic8259.h>
 #include <biosint.h>
 #include <pnpbios.h>
-#include <gpxe/pkbuff.h>
+#include <basemem_packet.h>
+#include <gpxe/iobuf.h>
 #include <gpxe/netdevice.h>
 #include <gpxe/if_ether.h>
 #include <gpxe/ethernet.h>
+#include <undi.h>
+#include <undinet.h>
+
 
 /** @file
  *
 
 /** An UNDI NIC */
 struct undi_nic {
-       /** Entry point */
-       SEGOFF16_t entry;
        /** Assigned IRQ number */
        unsigned int irq;
+       /** Currently processing ISR */
+       int isr_processing;
+       /** Bug workarounds */
+       int hacks;
 };
 
-static void undi_close ( struct net_device *netdev );
+/**
+ * @defgroup undi_hacks UNDI workarounds
+ * @{
+ */
+
+/** Work around Etherboot 5.4 bugs */
+#define UNDI_HACK_EB54         0x0001
+
+/** @} */
+
+static void undinet_close ( struct net_device *netdev );
 
 /*****************************************************************************
  *
@@ -57,40 +73,12 @@ static void undi_close ( struct net_device *netdev );
  * @ret name           API call name
  */
 static inline __attribute__ (( always_inline )) const char *
-undi_function_name ( unsigned int function ) {
+undinet_function_name ( unsigned int function ) {
        switch ( function ) {
-       case PXENV_UNLOAD_STACK:
-               return "PXENV_UNLOAD_STACK";
-       case PXENV_GET_CACHED_INFO:
-               return "PXENV_GET_CACHED_INFO";
-       case PXENV_RESTART_TFTP:
-               return "PXENV_RESTART_TFTP";
        case PXENV_START_UNDI:
                return "PXENV_START_UNDI";
        case PXENV_STOP_UNDI:
                return "PXENV_STOP_UNDI";
-       case PXENV_START_BASE:
-               return "PXENV_START_BASE";
-       case PXENV_STOP_BASE:
-               return "PXENV_STOP_BASE";
-       case PXENV_TFTP_OPEN:
-               return "PXENV_TFTP_OPEN";
-       case PXENV_TFTP_CLOSE:
-               return "PXENV_TFTP_CLOSE";
-       case PXENV_TFTP_READ:
-               return "PXENV_TFTP_READ";
-       case PXENV_TFTP_READ_FILE:
-               return "PXENV_TFTP_READ_FILE";
-       case PXENV_TFTP_GET_FSIZE:
-               return "PXENV_TFTP_GET_FSIZE";
-       case PXENV_UDP_OPEN:
-               return "PXENV_UDP_OPEN";
-       case PXENV_UDP_CLOSE:
-               return "PXENV_UDP_CLOSE";
-       case PXENV_UDP_WRITE:
-               return "PXENV_UDP_WRITE";
-       case PXENV_UDP_READ:
-               return "PXENV_UDP_READ";
        case PXENV_UNDI_STARTUP:
                return "PXENV_UNDI_STARTUP";
        case PXENV_UNDI_CLEANUP:
@@ -148,37 +136,35 @@ undi_function_name ( unsigned int function ) {
  * Used as the paramter block for all UNDI API calls.  Resides in base
  * memory.
  */
-static union u_PXENV_ANY __data16 ( undi_params );
-#define undi_params __use_data16 ( undi_params )
+static union u_PXENV_ANY __data16 ( undinet_params );
+#define undinet_params __use_data16 ( undinet_params )
 
 /** UNDI entry point
  *
  * Used as the indirection vector for all UNDI API calls.  Resides in
  * base memory.
  */
-static SEGOFF16_t __data16 ( undi_entry_point );
-#define undi_entry_point __use_data16 ( undi_entry_point )
+SEGOFF16_t __data16 ( undinet_entry_point );
+#define undinet_entry_point __use_data16 ( undinet_entry_point )
 
 /**
  * Issue UNDI API call
  *
- * @v undi             UNDI NIC
+ * @v undinic          UNDI NIC
  * @v function         API call number
  * @v params           UNDI parameter block
  * @v params_len       Length of UNDI parameter block
  * @ret rc             Return status code
  */
-static int undi_call ( struct undi_nic *undi, unsigned int function,
-                      void *params, size_t params_len ) {
-       union u_PXENV_ANY *pxenv_any = params;
+static int undinet_call ( struct undi_nic *undinic, unsigned int function,
+                         void *params, size_t params_len ) {
        PXENV_EXIT_t exit;
        int discard_b, discard_D;
        int rc;
 
        /* Copy parameter block and entry point */
-       assert ( params_len <= sizeof ( undi_params ) );
-       memcpy ( &undi_params, params, params_len );
-       undi_entry_point = undi->entry;
+       assert ( params_len <= sizeof ( undinet_params ) );
+       memcpy ( &undinet_params, params, params_len );
 
        /* Call real-mode entry point.  This calling convention will
         * work with both the !PXE and the PXENV+ entry points.
@@ -190,9 +176,9 @@ static int undi_call ( struct undi_nic *undi, unsigned int function,
                                           "addw $6, %%sp\n\t" )
                               : "=a" ( exit ), "=b" ( discard_b ),
                                 "=D" ( discard_D )
-                              : "p" ( & __from_data16 ( undi_entry_point ) ),
+                              : "p" ( &__from_data16 ( undinet_entry_point )),
                                 "b" ( function ),
-                                "D" ( & __from_data16 ( undi_params ) )
+                                "D" ( &__from_data16 ( undinet_params ) )
                               : "ecx", "edx", "esi", "ebp" );
 
        /* UNDI API calls may rudely change the status of A20 and not
@@ -205,16 +191,13 @@ static int undi_call ( struct undi_nic *undi, unsigned int function,
         */     
        gateA20_set();
 
-       /* Copy parameter block back */
-       memcpy ( params, &undi_params, params_len );
-
        /* Determine return status code based on PXENV_EXIT and
         * PXENV_STATUS
         */
        if ( exit == PXENV_EXIT_SUCCESS ) {
                rc = 0;
        } else {
-               rc = -pxenv_any->Status;
+               rc = -undinet_params.Status;
                /* Paranoia; don't return success for the combination
                 * of PXENV_EXIT_FAILURE but PXENV_STATUS_SUCCESS
                 */
@@ -222,10 +205,31 @@ static int undi_call ( struct undi_nic *undi, unsigned int function,
                        rc = -EIO;
        }
 
+       /* If anything goes wrong, print as much debug information as
+        * it's possible to give.
+        */
        if ( rc != 0 ) {
-               DBGC ( undi, "UNDI %p %s failed: %s\n", undi,
-                      undi_function_name ( function ), strerror ( rc ) );
+               SEGOFF16_t rm_params = {
+                       .segment = rm_ds,
+                       .offset = (intptr_t) &__from_data16 ( undinet_params ),
+               };
+
+               DBGC ( undinic, "UNDINIC %p %s failed: %s\n", undinic,
+                      undinet_function_name ( function ), strerror ( rc ) );
+               DBGC ( undinic, "UNDINIC %p parameters at %04x:%04x length "
+                      "%#02x, entry point at %04x:%04x\n", undinic,
+                      rm_params.segment, rm_params.offset, params_len,
+                      undinet_entry_point.segment,
+                      undinet_entry_point.offset );
+               DBGC ( undinic, "UNDINIC %p parameters provided:\n", undinic );
+               DBGC_HDA ( undinic, rm_params, params, params_len );
+               DBGC ( undinic, "UNDINIC %p parameters returned:\n", undinic );
+               DBGC_HDA ( undinic, rm_params, &undinet_params, params_len );
        }
+
+       /* Copy parameter block back */
+       memcpy ( params, &undinet_params, params_len );
+
        return rc;
 }
 
@@ -239,42 +243,39 @@ static int undi_call ( struct undi_nic *undi, unsigned int function,
 /**
  * UNDI interrupt service routine
  *
- * The UNDI ISR simply increments a counter (@c trigger_count) and
- * exits.
+ * The UNDI ISR increments a counter (@c trigger_count) and exits.
  */
-extern void undi_isr ( void );
+extern void undiisr ( void );
 
-/** Dummy chain vector */
-static struct segoff prev_handler[ IRQ_MAX + 1 ];
+/** IRQ number */
+uint8_t __data16 ( undiisr_irq );
+#define undiisr_irq __use_data16 ( undiisr_irq )
+
+/** IRQ chain vector */
+struct segoff __data16 ( undiisr_next_handler );
+#define undiisr_next_handler __use_data16 ( undiisr_next_handler )
 
 /** IRQ trigger count */
-static volatile uint8_t __text16 ( trigger_count ) = 0;
-#define trigger_count __use_text16 ( trigger_count )
+volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
+#define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
+
+/** Last observed trigger count */
+static unsigned int last_trigger_count = 0;
 
 /**
  * Hook UNDI interrupt service routine
  *
  * @v irq              IRQ number
- *
- * The UNDI ISR specifically does @b not chain to the previous
- * interrupt handler.  BIOSes seem to install somewhat perverse
- * default interrupt handlers; some do nothing other than an iret (and
- * so will cause a screaming interrupt if there really is another
- * interrupting device) and some disable the interrupt at the PIC (and
- * so will bring our own interrupts to a shuddering halt).
  */
-static void undi_hook_isr ( unsigned int irq ) {
+static void undinet_hook_isr ( unsigned int irq ) {
 
        assert ( irq <= IRQ_MAX );
+       assert ( undiisr_irq == 0 );
 
-       __asm__ __volatile__ ( TEXT16_CODE ( "\nundi_isr:\n\t"
-                                            "incb %%cs:%c0\n\t"
-                                            "iret\n\t" )
-                              : : "p" ( & __from_text16 ( trigger_count ) ) );
-
-       hook_bios_interrupt ( IRQ_INT ( irq ), ( ( unsigned int ) undi_isr ),
-                             &prev_handler[irq] );
-
+       undiisr_irq = irq;
+       hook_bios_interrupt ( IRQ_INT ( irq ),
+                             ( ( unsigned int ) undiisr ),
+                             &undiisr_next_handler );
 }
 
 /**
@@ -282,12 +283,14 @@ static void undi_hook_isr ( unsigned int irq ) {
  *
  * @v irq              IRQ number
  */
-static void undi_unhook_isr ( unsigned int irq ) {
+static void undinet_unhook_isr ( unsigned int irq ) {
 
        assert ( irq <= IRQ_MAX );
 
-       unhook_bios_interrupt ( IRQ_INT ( irq ), ( ( unsigned int ) undi_isr ),
-                               &prev_handler[irq] );
+       unhook_bios_interrupt ( IRQ_INT ( irq ),
+                               ( ( unsigned int ) undiisr ),
+                               &undiisr_next_handler );
+       undiisr_irq = 0;
 }
 
 /**
@@ -295,12 +298,11 @@ static void undi_unhook_isr ( unsigned int irq ) {
  *
  * @ret triggered      ISR has been triggered since last check
  */
-static int undi_isr_triggered ( void ) {
-       static unsigned int last_trigger_count = 0;
+static int undinet_isr_triggered ( void ) {
        unsigned int this_trigger_count;
 
        /* Read trigger_count.  Do this only once; it is volatile */
-       this_trigger_count = trigger_count;
+       this_trigger_count = undiisr_trigger_count;
 
        if ( this_trigger_count == last_trigger_count ) {
                /* Not triggered */
@@ -319,114 +321,122 @@ static int undi_isr_triggered ( void ) {
  *****************************************************************************
  */
 
-/** Maximum length of a packet transmitted via the UNDI API */
-#define UNDI_PKB_LEN 1514
-
-/** A packet transmitted via the UNDI API */
-struct undi_packet {
-       uint8_t bytes[UNDI_PKB_LEN];
-};
-
-/** UNDI packet buffer */
-static struct undi_packet __data16 ( undi_pkb );
-#define undi_pkb __use_data16 ( undi_pkb )
-
 /** UNDI transmit buffer descriptor */
-static struct s_PXENV_UNDI_TBD __data16 ( undi_tbd );
-#define undi_tbd __use_data16 ( undi_tbd )
+static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
+#define undinet_tbd __use_data16 ( undinet_tbd )
 
 /**
  * Transmit packet
  *
  * @v netdev           Network device
- * @v pkb              Packet buffer
+ * @v iobuf            I/O buffer
  * @ret rc             Return status code
  */
-static int undi_transmit ( struct net_device *netdev, struct pk_buff *pkb ) {
-       struct undi_nic *undi = netdev->priv;
+static int undinet_transmit ( struct net_device *netdev,
+                             struct io_buffer *iobuf ) {
+       struct undi_nic *undinic = netdev->priv;
        struct s_PXENV_UNDI_TRANSMIT undi_transmit;
-       size_t len = pkb_len ( pkb );
+       size_t len = iob_len ( iobuf );
        int rc;
 
-       /* Copy packet to UNDI packet buffer */
-       if ( len > sizeof ( undi_pkb ) )
-               len = sizeof ( undi_pkb );
-       memcpy ( &undi_pkb, pkb->data, len );
+       /* Technically, we ought to make sure that the previous
+        * transmission has completed before we re-use the buffer.
+        * However, many PXE stacks (including at least some Intel PXE
+        * stacks and Etherboot 5.4) fail to generate TX completions.
+        * In practice this won't be a problem, since our TX datapath
+        * has a very low packet volume and we can get away with
+        * assuming that a TX will be complete by the time we want to
+        * transmit the next packet.
+        */
+
+       /* Copy packet to UNDI I/O buffer */
+       if ( len > sizeof ( basemem_packet ) )
+               len = sizeof ( basemem_packet );
+       memcpy ( &basemem_packet, iobuf->data, len );
 
        /* Create PXENV_UNDI_TRANSMIT data structure */
        memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
        undi_transmit.DestAddr.segment = rm_ds;
        undi_transmit.DestAddr.offset
-               = ( ( unsigned ) & __from_data16 ( undi_tbd ) );
+               = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
        undi_transmit.TBD.segment = rm_ds;
        undi_transmit.TBD.offset
-               = ( ( unsigned ) & __from_data16 ( undi_tbd ) );
+               = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
 
        /* Create PXENV_UNDI_TBD data structure */
-       undi_tbd.ImmedLength = len;
-       undi_tbd.Xmit.segment = rm_ds;
-       undi_tbd.Xmit.offset 
-               = ( ( unsigned ) & __from_data16 ( undi_pkb ) );
+       undinet_tbd.ImmedLength = len;
+       undinet_tbd.Xmit.segment = rm_ds;
+       undinet_tbd.Xmit.offset 
+               = ( ( unsigned ) & __from_data16 ( basemem_packet ) );
 
        /* Issue PXE API call */
-       rc = undi_call ( undi, PXENV_UNDI_TRANSMIT, &undi_transmit,
-                        sizeof ( undi_transmit ) );
+       if ( ( rc = undinet_call ( undinic, PXENV_UNDI_TRANSMIT,
+                                  &undi_transmit,
+                                  sizeof ( undi_transmit ) ) ) != 0 )
+               goto done;
 
-       /* Free packet buffer and return */
-       free_pkb ( pkb );
+       /* Free I/O buffer */
+       netdev_tx_complete ( netdev, iobuf );
+
+ done:
        return rc;
 }
 
 /** 
  * Poll for received packets
  *
- * @v netdev   Network device
+ * @v netdev           Network device
  *
  * Fun, fun, fun.  UNDI drivers don't use polling; they use
  * interrupts.  We therefore cheat and pretend that an interrupt has
- * occurred every time undi_poll() is called.  This isn't too much of
- * a hack; PCI devices share IRQs and so the first thing that a proper
- * ISR should do is call PXENV_UNDI_ISR to determine whether or not
- * the UNDI NIC generated the interrupt; there is no harm done by
+ * occurred every time undinet_poll() is called.  This isn't too much
+ * of a hack; PCI devices share IRQs and so the first thing that a
+ * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
+ * not the UNDI NIC generated the interrupt; there is no harm done by
  * spurious calls to PXENV_UNDI_ISR.  Similarly, we wouldn't be
- * handling them any more rapidly than the usual rate of undi_poll()
- * being called even if we did implement a full ISR.  So it should
- * work.  Ha!
+ * handling them any more rapidly than the usual rate of
+ * undinet_poll() being called even if we did implement a full ISR.
+ * So it should work.  Ha!
  *
  * Addendum (21/10/03).  Some cards don't play nicely with this trick,
  * so instead of doing it the easy way we have to go to all the hassle
  * of installing a genuine interrupt service routine and dealing with
  * the wonderful 8259 Programmable Interrupt Controller.  Joy.
+ *
+ * Addendum (10/07/07).  When doing things such as iSCSI boot, in
+ * which we have to co-operate with a running OS, we can't get away
+ * with the "ISR-just-increments-a-counter-and-returns" trick at all,
+ * because it involves tying up the PIC for far too long, and other
+ * interrupt-dependent components (e.g. local disks) start breaking.
+ * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR
+ * from within interrupt context in order to deassert the device
+ * interrupt, and sends EOI if applicable.
  */
-static void undi_poll ( struct net_device *netdev ) {
-       struct undi_nic *undi = netdev->priv;
+static void undinet_poll ( struct net_device *netdev ) {
+       struct undi_nic *undinic = netdev->priv;
        struct s_PXENV_UNDI_ISR undi_isr;
-       struct pk_buff *pkb = NULL;
+       struct io_buffer *iobuf = NULL;
        size_t len;
        size_t frag_len;
        int rc;
 
-       /* Do nothing unless ISR has been triggered */
-       if ( ! undi_isr_triggered() )
-               return;
+       if ( ! undinic->isr_processing ) {
+               /* Do nothing unless ISR has been triggered */
+               if ( ! undinet_isr_triggered() )
+                       return;
 
-       /* See if this was our interrupt */
-       memset ( &undi_isr, 0, sizeof ( undi_isr ) );
-       undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_START;
-       if ( ( rc = undi_call ( undi, PXENV_UNDI_ISR, &undi_isr,
-                               sizeof ( undi_isr ) ) ) != 0 )
-               return;
-       if ( undi_isr.FuncFlag != PXENV_UNDI_ISR_OUT_OURS )
-               return;
-
-       /* Send EOI */
-       send_eoi ( undi->irq );
+               /* Start ISR processing */
+               undinic->isr_processing = 1;
+               undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
+       } else {
+               /* Continue ISR processing */
+               undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+       }
 
        /* Run through the ISR loop */
-       undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
        while ( 1 ) {
-               if ( ( rc = undi_call ( undi, PXENV_UNDI_ISR, &undi_isr,
-                                       sizeof ( undi_isr ) ) ) != 0 )
+               if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
+                                          sizeof ( undi_isr ) ) ) != 0 )
                        break;
                switch ( undi_isr.FuncFlag ) {
                case PXENV_UNDI_ISR_OUT_TRANSMIT:
@@ -436,43 +446,54 @@ static void undi_poll ( struct net_device *netdev ) {
                        /* Packet fragment received */
                        len = undi_isr.FrameLength;
                        frag_len = undi_isr.BufferLength;
-                       if ( ! pkb )
-                               pkb = alloc_pkb ( len );
-                       if ( ! pkb ) {
-                               DBGC ( undi, "UNDI %p could not allocate %zd "
-                                      "bytes for receive buffer\n",
-                                      undi, len );
-                               break;
+                       if ( ! iobuf )
+                               iobuf = alloc_iob ( len );
+                       if ( ! iobuf ) {
+                               DBGC ( undinic, "UNDINIC %p could not "
+                                      "allocate %zd bytes for RX buffer\n",
+                                      undinic, len );
+                               /* Fragment will be dropped */
+                               netdev_rx_err ( netdev, NULL, -ENOMEM );
+                               goto done;
                        }
-                       if ( frag_len > pkb_available ( pkb ) ) {
-                               DBGC ( undi, "UNDI %p fragment too large\n",
-                                      undi );
-                               frag_len = pkb_available ( pkb );
+                       if ( frag_len > iob_tailroom ( iobuf ) ) {
+                               DBGC ( undinic, "UNDINIC %p fragment too "
+                                      "large\n", undinic );
+                               frag_len = iob_tailroom ( iobuf );
                        }
-                       copy_from_real ( pkb_put ( pkb, frag_len ),
+                       copy_from_real ( iob_put ( iobuf, frag_len ),
                                         undi_isr.Frame.segment,
                                         undi_isr.Frame.offset, frag_len );
-                       if ( pkb_len ( pkb ) == len ) {
-                               netdev_rx ( netdev, pkb );
-                               pkb = NULL;
+                       if ( iob_len ( iobuf ) == len ) {
+                               /* Whole packet received; deliver it */
+                               netdev_rx ( netdev, iobuf );
+                               iobuf = NULL;
+                               /* Etherboot 5.4 fails to return all packets
+                                * under mild load; pretend it retriggered.
+                                */
+                               if ( undinic->hacks & UNDI_HACK_EB54 )
+                                       --last_trigger_count;
                        }
                        break;
                case PXENV_UNDI_ISR_OUT_DONE:
                        /* Processing complete */
+                       undinic->isr_processing = 0;
                        goto done;
                default:
                        /* Should never happen */
-                       DBGC ( undi, "UNDI %p ISR returned invalid FuncFlag "
-                              "%04x\n", undi, undi_isr.FuncFlag );
+                       DBGC ( undinic, "UNDINIC %p ISR returned invalid "
+                              "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
+                       undinic->isr_processing = 0;
                        goto done;
                }
                undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
        }
 
  done:
-       if ( pkb ) {
-               DBGC ( undi, "UNDI %p returned incomplete packet\n", undi );
-               netdev_rx ( netdev, pkb );
+       if ( iobuf ) {
+               DBGC ( undinic, "UNDINIC %p returned incomplete packet\n",
+                      undinic );
+               netdev_rx_err ( netdev, iobuf, -EINVAL );
        }
 }
 
@@ -482,37 +503,39 @@ static void undi_poll ( struct net_device *netdev ) {
  * @v netdev           Net device
  * @ret rc             Return status code
  */
-static int undi_open ( struct net_device *netdev ) {
-       struct undi_nic *undi = netdev->priv;
-       struct s_PXENV_UNDI_SET_STATION_ADDRESS set_address;
-       struct s_PXENV_UNDI_OPEN open;
+static int undinet_open ( struct net_device *netdev ) {
+       struct undi_nic *undinic = netdev->priv;
+       struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
+       struct s_PXENV_UNDI_OPEN undi_open;
        int rc;
 
        /* Hook interrupt service routine and enable interrupt */
-       undi_hook_isr ( undi->irq );
-       enable_irq ( undi->irq );
+       undinet_hook_isr ( undinic->irq );
+       enable_irq ( undinic->irq );
+       send_eoi ( undinic->irq );
 
        /* Set station address.  Required for some PXE stacks; will
         * spuriously fail on others.  Ignore failures.  We only ever
         * use it to set the MAC address to the card's permanent value
         * anyway.
         */
-       memcpy ( set_address.StationAddress, netdev->ll_addr,
-                sizeof ( set_address.StationAddress ) );
-       undi_call ( undi, PXENV_UNDI_SET_STATION_ADDRESS,
-                   &set_address, sizeof ( set_address ) );
+       memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
+                sizeof ( undi_set_address.StationAddress ) );
+       undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS,
+                      &undi_set_address, sizeof ( undi_set_address ) );
 
        /* Open NIC */
-       memset ( &open, 0, sizeof ( open ) );
-       open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST );
-       if ( ( rc = undi_call ( undi, PXENV_UNDI_OPEN, &open,
-                               sizeof ( open ) ) ) != 0 )
+       memset ( &undi_open, 0, sizeof ( undi_open ) );
+       undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST );
+       if ( ( rc = undinet_call ( undinic, PXENV_UNDI_OPEN, &undi_open,
+                                  sizeof ( undi_open ) ) ) != 0 )
                goto err;
 
+       DBGC ( undinic, "UNDINIC %p opened\n", undinic );
        return 0;
 
  err:
-       undi_close ( netdev );
+       undinet_close ( netdev );
        return rc;
 }
 
@@ -521,121 +544,190 @@ static int undi_open ( struct net_device *netdev ) {
  *
  * @v netdev           Net device
  */
-static void undi_close ( struct net_device *netdev ) {
-       struct undi_nic *undi = netdev->priv;
-       struct s_PXENV_UNDI_CLOSE close;
+static void undinet_close ( struct net_device *netdev ) {
+       struct undi_nic *undinic = netdev->priv;
+       struct s_PXENV_UNDI_ISR undi_isr;
+       struct s_PXENV_UNDI_CLOSE undi_close;
+       int rc;
+
+       /* Ensure ISR has exited cleanly */
+       while ( undinic->isr_processing ) {
+               undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+               if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
+                                          sizeof ( undi_isr ) ) ) != 0 )
+                       break;
+               switch ( undi_isr.FuncFlag ) {
+               case PXENV_UNDI_ISR_OUT_TRANSMIT:
+               case PXENV_UNDI_ISR_OUT_RECEIVE:
+                       /* Continue draining */
+                       break;
+               default:
+                       /* Stop processing */
+                       undinic->isr_processing = 0;
+                       break;
+               }
+       }
 
        /* Close NIC */
-       undi_call ( undi, PXENV_UNDI_CLOSE, &close, sizeof ( close ) );
+       undinet_call ( undinic, PXENV_UNDI_CLOSE, &undi_close,
+                      sizeof ( undi_close ) );
 
        /* Disable interrupt and unhook ISR */
-       disable_irq ( undi->irq );
-       undi_unhook_isr ( undi->irq );
+       disable_irq ( undinic->irq );
+       undinet_unhook_isr ( undinic->irq );
+
+       DBGC ( undinic, "UNDINIC %p closed\n", undinic );
 }
 
 /**
- * Probe PXE device
+ * Enable/disable interrupts
  *
- * @v pxe              PXE device
+ * @v netdev           Net device
+ * @v enable           Interrupts should be enabled
+ */
+static void undinet_irq ( struct net_device *netdev, int enable ) {
+       struct undi_nic *undinic = netdev->priv;
+
+       /* Cannot support interrupts yet */
+       DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
+              undinic, ( enable ? "enable" : "disable" ) );
+}
+
+/** UNDI network device operations */
+static struct net_device_operations undinet_operations = {
+       .open           = undinet_open,
+       .close          = undinet_close,
+       .transmit       = undinet_transmit,
+       .poll           = undinet_poll,
+       .irq            = undinet_irq,
+};
+
+/**
+ * Probe UNDI device
+ *
+ * @v undi             UNDI device
  * @ret rc             Return status code
  */
-int undi_probe ( struct pxe_device *pxe ) {
+int undinet_probe ( struct undi_device *undi ) {
        struct net_device *netdev;
-       struct undi_nic *undi;
+       struct undi_nic *undinic;
        struct s_PXENV_START_UNDI start_undi;
        struct s_PXENV_UNDI_STARTUP undi_startup;
        struct s_PXENV_UNDI_INITIALIZE undi_initialize;
        struct s_PXENV_UNDI_GET_INFORMATION undi_info;
+       struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
        struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
        struct s_PXENV_UNDI_CLEANUP undi_cleanup;
        struct s_PXENV_STOP_UNDI stop_undi;
        int rc;
 
        /* Allocate net device */
-       netdev = alloc_etherdev ( sizeof ( *undi ) );
+       netdev = alloc_etherdev ( sizeof ( *undinic ) );
        if ( ! netdev )
                return -ENOMEM;
-       undi = netdev->priv;
-       pxe_set_drvdata ( pxe, netdev );
-       memset ( undi, 0, sizeof ( *undi ) );
-       undi->entry = pxe->entry;
+       netdev_init ( netdev, &undinet_operations );
+       undinic = netdev->priv;
+       undi_set_drvdata ( undi, netdev );
+       netdev->dev = &undi->dev;
+       memset ( undinic, 0, sizeof ( *undinic ) );
+       undinet_entry_point = undi->entry;
+       DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
 
        /* Hook in UNDI stack */
-       memset ( &start_undi, 0, sizeof ( start_undi ) );
-       start_undi.AX = pxe->pci_busdevfn;
-       start_undi.BX = pxe->isapnp_csn;
-       start_undi.DX = pxe->isapnp_read_port;
-       start_undi.ES = BIOS_SEG;
-       start_undi.DI = find_pnp_bios();
-       if ( ( rc = undi_call ( undi, PXENV_START_UNDI, &start_undi,
-                               sizeof ( start_undi ) ) ) != 0 )
-               goto err_start_undi;
+       if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
+               memset ( &start_undi, 0, sizeof ( start_undi ) );
+               start_undi.AX = undi->pci_busdevfn;
+               start_undi.BX = undi->isapnp_csn;
+               start_undi.DX = undi->isapnp_read_port;
+               start_undi.ES = BIOS_SEG;
+               start_undi.DI = find_pnp_bios();
+               if ( ( rc = undinet_call ( undinic, PXENV_START_UNDI,
+                                          &start_undi,
+                                          sizeof ( start_undi ) ) ) != 0 )
+                       goto err_start_undi;
+       }
+       undi->flags |= UNDI_FL_STARTED;
 
        /* Bring up UNDI stack */
        memset ( &undi_startup, 0, sizeof ( undi_startup ) );
-       if ( ( rc = undi_call ( undi, PXENV_UNDI_STARTUP, &undi_startup,
-                               sizeof ( undi_startup ) ) ) != 0 )
+       if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP, &undi_startup,
+                                  sizeof ( undi_startup ) ) ) != 0 )
                goto err_undi_startup;
        memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
-       if ( ( rc = undi_call ( undi, PXENV_UNDI_INITIALIZE, &undi_initialize,
-                               sizeof ( undi_initialize ) ) ) != 0 )
+       if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE,
+                                  &undi_initialize,
+                                  sizeof ( undi_initialize ) ) ) != 0 )
                goto err_undi_initialize;
 
        /* Get device information */
        memset ( &undi_info, 0, sizeof ( undi_info ) );
-       if ( ( rc = undi_call ( undi, PXENV_UNDI_GET_INFORMATION, &undi_info,
-                               sizeof ( undi_info ) ) ) != 0 )
+       if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION,
+                                  &undi_info, sizeof ( undi_info ) ) ) != 0 )
                goto err_undi_get_information;
        memcpy ( netdev->ll_addr, undi_info.PermNodeAddress, ETH_ALEN );
-       undi->irq = undi_info.IntNumber;
-       if ( undi->irq > IRQ_MAX ) {
-               DBGC ( undi, "UNDI %p invalid IRQ %d\n", undi, undi->irq );
+       undinic->irq = undi_info.IntNumber;
+       if ( undinic->irq > IRQ_MAX ) {
+               DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
+                      undinic, undinic->irq );
                goto err_bad_irq;
        }
-       DBGC ( undi, "UNDI %p (%s) using IRQ %d\n",
-              undi, eth_ntoa ( netdev->ll_addr ), undi->irq );
-
-       /* Point to NIC specific routines */
-       netdev->open     = undi_open;
-       netdev->close    = undi_close;
-       netdev->transmit = undi_transmit;
-       netdev->poll     = undi_poll;
+       DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
+              undinic, eth_ntoa ( netdev->ll_addr ), undinic->irq );
+
+       /* Get interface information */
+       memset ( &undi_iface, 0, sizeof ( undi_iface ) );
+       if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO,
+                                  &undi_iface,
+                                  sizeof ( undi_iface ) ) ) != 0 )
+               goto err_undi_get_iface_info;
+       DBGC ( undinic, "UNDINIC %p has type %s and link speed %ld\n",
+              undinic, undi_iface.IfaceType, undi_iface.LinkSpeed );
+       if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
+                      sizeof ( undi_iface.IfaceType ) ) == 0 ) {
+               DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
+                      undinic );
+               undinic->hacks |= UNDI_HACK_EB54;
+       }
 
        /* Register network device */
        if ( ( rc = register_netdev ( netdev ) ) != 0 )
                goto err_register;
 
+       DBGC ( undinic, "UNDINIC %p added\n", undinic );
        return 0;
 
  err_register:
+ err_undi_get_iface_info:
  err_bad_irq:
  err_undi_get_information:
  err_undi_initialize:
        /* Shut down UNDI stack */
        memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
-       undi_call ( undi, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
-                   sizeof ( undi_shutdown ) );
+       undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
+                      sizeof ( undi_shutdown ) );
        memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
-       undi_call ( undi, PXENV_UNDI_CLEANUP, &undi_cleanup,
-                   sizeof ( undi_cleanup ) );
+       undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
+                      sizeof ( undi_cleanup ) );
  err_undi_startup:
        /* Unhook UNDI stack */
        memset ( &stop_undi, 0, sizeof ( stop_undi ) );
-       undi_call ( undi, PXENV_STOP_UNDI, &stop_undi, sizeof ( stop_undi ) );
+       undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
+                      sizeof ( stop_undi ) );
  err_start_undi:
-       free_netdev ( netdev );
-       pxe_set_drvdata ( pxe, NULL );
+       netdev_nullify ( netdev );
+       netdev_put ( netdev );
+       undi_set_drvdata ( undi, NULL );
        return rc;
 }
 
 /**
- * Remove PXE device
+ * Remove UNDI device
  *
- * @v pxe              PXE device
+ * @v undi             UNDI device
  */
-void undi_remove ( struct pxe_device *pxe ) {
-       struct net_device *netdev = pxe_get_drvdata ( pxe );
-       struct undi_nic *undi = netdev->priv;
+void undinet_remove ( struct undi_device *undi ) {
+       struct net_device *netdev = undi_get_drvdata ( undi );
+       struct undi_nic *undinic = netdev->priv;
        struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
        struct s_PXENV_UNDI_CLEANUP undi_cleanup;
        struct s_PXENV_STOP_UNDI stop_undi;
@@ -645,16 +737,24 @@ void undi_remove ( struct pxe_device *pxe ) {
 
        /* Shut down UNDI stack */
        memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
-       undi_call ( undi, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
-                   sizeof ( undi_shutdown ) );
+       undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
+                      sizeof ( undi_shutdown ) );
        memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
-       undi_call ( undi, PXENV_UNDI_CLEANUP, &undi_cleanup,
-                   sizeof ( undi_cleanup ) );
+       undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
+                      sizeof ( undi_cleanup ) );
 
        /* Unhook UNDI stack */
        memset ( &stop_undi, 0, sizeof ( stop_undi ) );
-       undi_call ( undi, PXENV_STOP_UNDI, &stop_undi, sizeof ( stop_undi ) );
+       undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
+                      sizeof ( stop_undi ) );
+       undi->flags &= ~UNDI_FL_STARTED;
+
+       /* Clear entry point */
+       memset ( &undinet_entry_point, 0, sizeof ( undinet_entry_point ) );
 
        /* Free network device */
-       free_netdev ( netdev );
+       netdev_nullify ( netdev );
+       netdev_put ( netdev );
+
+       DBGC ( undinic, "UNDINIC %p removed\n", undinic );
 }