Added PXE bus driver (with support only for using PXE structures found
authorMichael Brown <mcb30@etherboot.org>
Thu, 4 Jan 2007 19:38:34 +0000 (19:38 +0000)
committerMichael Brown <mcb30@etherboot.org>
Thu, 4 Jan 2007 19:38:34 +0000 (19:38 +0000)
lying about in memory, not for loading from ROM), and UNDI net driver.

src/arch/i386/drivers/bus/pxebus.c [new file with mode: 0644]
src/arch/i386/drivers/net/undi_net.c [new file with mode: 0644]
src/include/pxe.h

diff --git a/src/arch/i386/drivers/bus/pxebus.c b/src/arch/i386/drivers/bus/pxebus.c
new file mode 100644 (file)
index 0000000..66cb0fd
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <pxe.h>
+#include <realmode.h>
+#include <gpxe/device.h>
+
+/** @file
+ *
+ * PXE bus
+ *
+ */
+
+/**
+ * UNDI parameter block
+ *
+ * Used as the paramter block for all UNDI API calls.  Resides in base
+ * memory.
+ */
+static union u_PXENV_ANY __data16 ( pxe_params );
+#define pxe_params __use_data16 ( pxe_params )
+
+/** UNDI entry point */
+static SEGOFF16_t __data16 ( pxe_entry_point );
+#define pxe_entry_point __use_data16 ( pxe_entry_point )
+
+/**
+ * Issue PXE API call
+ *
+ * @v pxe              PXE device
+ * @v function         API call number
+ * @v params           PXE parameter block
+ * @v params_len       Length of PXE parameter block
+ * @ret rc             Return status code
+ */
+int pxe_call ( struct pxe_device *pxe, unsigned int function,
+              void *params, size_t params_len ) {
+       union u_PXENV_ANY *pxenv_any = params;
+       PXENV_EXIT_t exit;
+       int discard_b, discard_D;
+       int rc;
+
+       /* Copy parameter block and entry point */
+       assert ( params_len <= sizeof ( pxe_params ) );
+       memcpy ( &pxe_params, params, params_len );
+       pxe_entry_point = pxe->entry;
+
+       /* Call real-mode entry point.  This calling convention will
+        * work with both the !PXE and the PXENV+ entry points.
+        */
+       __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t"
+                                          "pushw %%di\n\t"
+                                          "pushw %%bx\n\t"
+                                          "lcall *%c3\n\t"
+                                          "addw $6, %%sp\n\t" )
+                              : "=a" ( exit ), "=b" ( discard_b ),
+                                "=D" ( discard_D )
+                              : "p" ( & __from_data16 ( pxe_entry_point ) ),
+                                "b" ( function ),
+                                "D" ( & __from_data16 ( pxe_params ) ) );
+
+       /* UNDI API calls may rudely change the status of A20 and not
+        * bother to restore it afterwards.  Intel is known to be
+        * guilty of this.
+        *
+        * Note that we will return to this point even if A20 gets
+        * screwed up by the UNDI driver, because Etherboot always
+        * resides in an even megabyte of RAM.
+        */     
+       gateA20_set();
+
+       /* Copy parameter block back */
+       memcpy ( params, &pxe_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;
+               /* Paranoia; don't return success for the combination
+                * of PXENV_EXIT_FAILURE but PXENV_STATUS_SUCCESS
+                */
+               if ( rc == 0 )
+                       rc = -EIO;
+       }
+
+       return rc;
+}
+
+/**
+ * Byte checksum
+ *
+ * @v data             Data to checksum
+ * @v len              Length of data
+ * @ret sum            Byte checksum
+ */
+static uint8_t checksum ( void *data, size_t len ) {
+       uint8_t *bytes = data;
+       unsigned int sum = 0;
+
+       while ( len-- )
+               sum += *(bytes++);
+
+       return sum;
+}
+
+/**
+ * Get PXE device information for an instantiated device
+ *
+ * @v pxe              PXE device
+ * @ret rc             Return status code
+ */
+static int pxedev_get_instance_info ( struct pxe_device *pxe ) {
+       struct s_PXENV pxenv;
+       struct s_PXE ppxe;
+       struct s_PXENV_UNDI_GET_INFORMATION undi_info;
+       int rc;
+
+       /* Determine entry point from PXENV+ structure */
+       DBGC ( pxe, "PXE %p has PXENV+ structure at %04x:%04x\n", pxe,
+             pxe->pxenv.segment, pxe->pxenv.offset );
+       copy_from_real ( &pxenv, pxe->pxenv.segment, pxe->pxenv.offset,
+                        sizeof ( pxenv ) );
+       if ( checksum ( &pxenv, sizeof ( pxenv ) ) != 0 ) {
+               DBGC ( pxe, "PXE %p bad PXENV+ checksum\n", pxe );
+               return -EINVAL;
+       }
+       pxe->entry = pxenv.RMEntry;
+
+       /* If API version is 2.1 or greater, use the !PXE structure instead */
+       if ( pxenv.Version >= 0x0201 ) {
+               pxe->ppxe = pxenv.PXEPtr;
+               DBGC ( pxe, "PXE %p has !PXE structure at %04x:%04x\n", pxe,
+                      pxe->ppxe.segment, pxe->ppxe.offset );
+               copy_from_real ( &ppxe, pxe->ppxe.segment, pxe->ppxe.offset,
+                                sizeof ( ppxe ) );
+               if ( checksum ( &pxenv, sizeof ( pxenv ) ) != 0 ) {
+                       DBGC ( pxe, "PXE %p bad !PXE checksum\n", pxe );
+                       return -EINVAL;
+               }
+               pxe->entry = ppxe.EntryPointSP;
+       }
+
+       DBGC ( pxe, "PXE %p using entry point at %04x:%04x\n", pxe,
+              pxe->entry.segment, pxe->entry.offset );
+
+       /* Get device information */
+       memset ( &undi_info, 0, sizeof ( undi_info ) );
+       if ( ( rc = pxe_call ( pxe, PXENV_UNDI_GET_INFORMATION, &undi_info,
+                              sizeof ( undi_info ) ) ) != 0 ) {
+               DBGC ( pxe, "PXE %p could not retrieve UNDI information: %s\n",
+                      pxe, strerror ( rc ) );
+               return rc;
+       }
+       memcpy ( pxe->hwaddr, undi_info.PermNodeAddress,
+                sizeof ( pxe->hwaddr ) );
+       pxe->irq = undi_info.IntNumber;
+       pxe->rom = undi_info.ROMAddress;
+
+       return 0;
+}
+
+/**
+ * Register PXE device
+ *
+ * @v pxe              PXE device
+ * @ret rc             Return status code
+ */
+static int register_pxedev ( struct pxe_device *pxe ) {
+       int rc;
+
+       DBGC ( pxe, "PXE %p registering\n", pxe );
+
+       /* Register as an UNDI driver */
+       if ( ( rc = undi_probe ( pxe ) ) != 0 )
+               return rc;
+
+       /* Add to device hierarchy and return */
+       list_add ( &pxe->dev.siblings, &pxe->dev.parent->children );
+       return 0;
+}
+
+/**
+ * Unregister a PXE device
+ *
+ * @v pxe              PXE device
+ *
+ */
+static void unregister_pxedev ( struct pxe_device *pxe ) {
+       undi_remove ( pxe );
+       list_del ( &pxe->dev.siblings );
+       DBGC ( pxe, "PXE %p unregistered\n", pxe );
+}
+
+static void pxebus_remove ( struct root_device *rootdev );
+
+/**
+ * Probe PXE root bus
+ *
+ * @v rootdev          PXE bus root device
+ *
+ * Scans the PXE bus for devices and registers all devices it can
+ * find.
+ */
+static int pxebus_probe ( struct root_device *rootdev ) {
+       struct pxe_device *pxe;
+       uint16_t signature;
+       uint16_t pxenv_segment;
+       uint16_t pxenv_offset;
+       int rc;
+
+       /* PXE installation check */
+       __asm__ __volatile__ ( REAL_CODE ( "stc\n\t"
+                                          "int $0x1a\n\t"
+                                          "jnc 1f\n\t"
+                                          "xorw %%ax, %%ax\n\t"
+                                          "\n1:\n\t"
+                                          "movw %%es, %%dx\n\t" )
+                              : "=a" ( signature ), "=b" ( pxenv_offset ),
+                                "=d" ( pxenv_segment )
+                              : "a" ( 0x5650 ) );
+       if ( signature != 0x564e ) {
+               DBG ( "No pixies found\n" );
+               return 0;
+       }
+
+       /* Allocate PXE device structure */
+       pxe = malloc ( sizeof ( *pxe ) );
+       if ( ! pxe ) {
+               rc = -ENOMEM;
+               goto err;
+       }
+       memset ( pxe, 0, sizeof ( *pxe ) );
+
+       /* Populate PXE device structure */
+       pxe->pxenv.segment = pxenv_segment;
+       pxe->pxenv.offset = pxenv_offset;
+       INIT_LIST_HEAD ( &pxe->dev.children );
+       pxe->dev.parent = &rootdev->dev;
+       if ( ( rc = pxedev_get_instance_info ( pxe ) ) != 0 )
+               goto err;
+
+       /* Register PXE device */
+       if ( ( rc = register_pxedev ( pxe ) ) != 0 )
+               goto err;
+
+       return 0;
+
+ err:
+       free ( pxe );
+       pxebus_remove ( rootdev );
+       return rc;
+}
+
+/**
+ * Remove PXE root bus
+ *
+ * @v rootdev          PXE bus root device
+ */
+static void pxebus_remove ( struct root_device *rootdev ) {
+       struct pxe_device *pxe;
+       struct pxe_device *tmp;
+
+       list_for_each_entry_safe ( pxe, tmp, &rootdev->dev.children,
+                                  dev.siblings ) {
+               unregister_pxedev ( pxe );
+               free ( pxe );
+       }
+}
+
+/** PXE bus root device driver */
+static struct root_driver pxe_root_driver = {
+       .probe = pxebus_probe,
+       .remove = pxebus_remove,
+};
+
+/** PXE bus root device */
+struct root_device pxe_root_device __root_device = {
+       .name = "PXE",
+       .driver = &pxe_root_driver,
+       .dev = {
+               .children = LIST_HEAD_INIT ( pxe_root_device.dev.children ),
+       },
+};
diff --git a/src/arch/i386/drivers/net/undi_net.c b/src/arch/i386/drivers/net/undi_net.c
new file mode 100644 (file)
index 0000000..f022bd8
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <string.h>
+#include <pxe.h>
+#include <realmode.h>
+#include <pic8259.h>
+#include <biosint.h>
+#include <gpxe/pkbuff.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ethernet.h>
+
+/** @file
+ *
+ * UNDI network device driver
+ *
+ */
+
+/*****************************************************************************
+ *
+ * UNDI interrupt service routine
+ *
+ *****************************************************************************
+ */
+
+/**
+ * UNDI interrupt service routine
+ *
+ * The UNDI ISR simply increments a counter (@c trigger_count) and
+ * exits.
+ */
+extern void undi_isr ( void );
+
+/** Vector for chaining to other interrupts handlers */
+static struct segoff __text16 ( undi_isr_chain );
+#define undi_isr_chain __use_text16 ( undi_isr_chain )
+
+/** IRQ trigger count */
+static volatile uint16_t __text16 ( trigger_count );
+#define trigger_count __use_text16 ( trigger_count )
+
+/**
+ * Hook UNDI interrupt service routine
+ *
+ * @v irq              IRQ number
+ */
+static void undi_hook_isr ( unsigned int irq ) {
+       __asm__ __volatile__ ( TEXT16_CODE ( "\nundi_isr:\n\t"
+                                            "incl %%cs:%c0\n\t"
+                                            "ljmp *%%cs:%c1\n\t" )
+                              : : "p" ( & __from_text16 ( trigger_count ) ),
+                                  "p" ( & __from_text16 ( undi_isr_chain ) ));
+
+       hook_bios_interrupt ( IRQ_INT ( irq ), ( ( unsigned int ) undi_isr ),
+                             &undi_isr_chain );
+
+}
+
+/**
+ * Unhook UNDI interrupt service routine
+ *
+ * @v irq              IRQ number
+ */
+static void undi_unhook_isr ( unsigned int irq ) {
+       unhook_bios_interrupt ( IRQ_INT ( irq ), ( ( unsigned int ) undi_isr ),
+                               &undi_isr_chain );
+}
+
+/**
+ * Test to see if UNDI ISR has been triggered
+ *
+ * @ret triggered      ISR has been triggered since last check
+ */
+static int undi_isr_triggered ( void ) {
+       static unsigned int last_trigger_count;
+       unsigned int this_trigger_count;
+
+       /* Read trigger_count.  Do this only once; it is volatile */
+       this_trigger_count = trigger_count;
+
+       if ( this_trigger_count == last_trigger_count ) {
+               /* Not triggered */
+               return 0;
+       } else {
+               /* Triggered */
+               last_trigger_count = this_trigger_count;
+               return 1;
+       }
+}
+
+/*****************************************************************************
+ *
+ * UNDI network device interface
+ *
+ *****************************************************************************
+ */
+
+/** 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 )
+
+/**
+ * Transmit packet
+ *
+ * @v netdev           Network device
+ * @v pkb              Packet buffer
+ * @ret rc             Return status code
+ */
+static int undi_transmit ( struct net_device *netdev, struct pk_buff *pkb ) {
+       struct pxe_device *pxe = netdev->priv;
+       struct s_PXENV_UNDI_TRANSMIT undi_transmit;
+       size_t len = pkb_len ( pkb );
+       int rc;
+
+       /* Copy packet to UNDI packet buffer */
+       if ( len > sizeof ( undi_pkb ) )
+               len = sizeof ( undi_pkb );
+       memcpy ( &undi_pkb, pkb->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 ) );
+       undi_transmit.TBD.segment = rm_ds;
+       undi_transmit.TBD.offset
+               = ( ( unsigned ) & __from_data16 ( undi_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 ) );
+
+       /* Issue PXE API call */
+       if ( ( rc = pxe_call ( pxe, PXENV_UNDI_TRANSMIT, &undi_transmit,
+                              sizeof ( undi_transmit ) ) ) != 0 ) {
+               DBG ( "UNDI_TRANSMIT failed: %s\n", strerror ( rc ) );
+       }
+
+       /* Free packet buffer and return */
+       free_pkb ( pkb );
+       return rc;
+}
+
+/** 
+ * Poll for received packets
+ *
+ * @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
+ * 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!
+ *
+ * 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.
+ */
+static void undi_poll ( struct net_device *netdev ) {
+       struct pxe_device *pxe = netdev->priv;
+       struct s_PXENV_UNDI_ISR undi_isr;
+       struct pk_buff *pkb = NULL;
+       size_t len;
+       size_t frag_len;
+       int rc;
+
+       /* Do nothing unless ISR has been triggered */
+       if ( ! undi_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 = pxe_call ( pxe, PXENV_UNDI_ISR, &undi_isr,
+                              sizeof ( undi_isr ) ) ) != 0 ) {
+               DBG ( "UNDI_ISR (START) failed: %s\n", strerror ( rc ) );
+               return;
+       }
+       if ( undi_isr.FuncFlag != PXENV_UNDI_ISR_OUT_OURS )
+               return;
+
+       /* Send EOI */
+       send_eoi ( pxe->irq );
+
+       /* Run through the ISR loop */
+       undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
+       while ( 1 ) {
+               if ( ( rc = pxe_call ( pxe, PXENV_UNDI_ISR, &undi_isr,
+                                      sizeof ( undi_isr ) ) ) != 0 ) {
+                       DBG ( "UNDI_ISR (PROCESS/GET_NEXT) failed: %s\n",
+                             strerror ( rc ) );
+                       break;
+               }
+               switch ( undi_isr.FuncFlag ) {
+               case PXENV_UNDI_ISR_OUT_TRANSMIT:
+                       /* We don't care about transmit completions */
+                       break;
+               case PXENV_UNDI_ISR_OUT_RECEIVE:
+                       /* Packet fragment received */
+                       len = undi_isr.FrameLength;
+                       frag_len = undi_isr.BufferLength;
+                       if ( ! pkb )
+                               pkb = alloc_pkb ( len );
+                       if ( ! pkb ) {
+                               DBG ( "UNDI could not allocate %zd bytes for "
+                                     "receive buffer\n", len );
+                               break;
+                       }
+                       if ( frag_len > pkb_available ( pkb ) ) {
+                               DBG ( "UNDI fragment too large\n" );
+                               frag_len = pkb_available ( pkb );
+                       }
+                       copy_from_real ( pkb_put ( pkb, frag_len ),
+                                        undi_isr.Frame.segment,
+                                        undi_isr.Frame.offset, frag_len );
+                       if ( pkb_len ( pkb ) == len ) {
+                               netdev_rx ( netdev, pkb );
+                               pkb = NULL;
+                       }
+                       break;
+               case PXENV_UNDI_ISR_OUT_DONE:
+                       /* Processing complete */
+                       goto done;
+               default:
+                       /* Should never happen */
+                       DBG ( "UNDI ISR returned invalid FuncFlag %04x\n",
+                             undi_isr.FuncFlag );
+                       goto done;
+               }
+               undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+       }
+
+ done:
+       if ( pkb ) {
+               DBG ( "UNDI returned incomplete packet\n" );
+               netdev_rx ( netdev, pkb );
+       }
+}
+
+/**
+ * Open NIC
+ *
+ * @v netdev           Net device
+ * @ret rc             Return status code
+ */
+static int undi_open ( struct net_device *netdev ) {
+       struct pxe_device *pxe = netdev->priv;
+       struct s_PXENV_UNDI_SET_STATION_ADDRESS set_address;
+       struct s_PXENV_UNDI_OPEN open;
+       int rc;
+
+       /* Hook interrupt service routine */
+       undi_hook_isr ( pxe->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 ) );
+       if ( ( rc = pxe_call ( pxe, PXENV_UNDI_SET_STATION_ADDRESS,
+                              &set_address, sizeof ( set_address ) ) ) != 0 ){
+               DBG ( "UNDI_SET_STATION_ADDRESS failed: %s\n",
+                     strerror ( rc ) );
+       }
+
+       /* Open NIC */
+       memset ( &open, 0, sizeof ( open ) );
+       open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST );
+       if ( ( rc = pxe_call ( pxe, PXENV_UNDI_OPEN, &open,
+                              sizeof ( open ) ) ) != 0 ) {
+               DBG ( "UNDI_OPEN failed: %s\n", strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Close NIC
+ *
+ * @v netdev           Net device
+ */
+static void undi_close ( struct net_device *netdev ) {
+       struct pxe_device *pxe = netdev->priv;
+       struct s_PXENV_UNDI_CLOSE close;
+       int rc;
+
+       /* Close NIC */
+       if ( ( rc = pxe_call ( pxe, PXENV_UNDI_CLOSE, &close,
+                              sizeof ( close ) ) ) != 0 ) {
+               DBG ( "UNDI_CLOSE failed: %s\n", strerror ( rc ) );
+       }
+
+       /* Unhook ISR */
+       undi_unhook_isr ( pxe->irq );
+}
+
+/**
+ * Probe PXE device
+ *
+ * @v pxe              PXE device
+ * @ret rc             Return status code
+ */
+int undi_probe ( struct pxe_device *pxe ) {
+       struct net_device *netdev;
+       int rc;
+
+       /* Allocate net device */
+       netdev = alloc_etherdev ( 0 );
+       if ( ! netdev ) {
+               rc = -ENOMEM;
+               goto err;
+       }
+       netdev->priv = pxe;
+       pxe_set_drvdata ( pxe, netdev );
+
+       /* Fill in NIC information */
+       memcpy ( netdev->ll_addr, pxe->hwaddr, ETH_ALEN );
+
+       /* Point to NIC specific routines */
+       netdev->open     = undi_open;
+       netdev->close    = undi_close;
+       netdev->transmit = undi_transmit;
+       netdev->poll     = undi_poll;
+
+       /* Register network device */
+       if ( ( rc = register_netdev ( netdev ) ) != 0 )
+               goto err;
+
+       return 0;
+
+ err:
+       free_netdev ( netdev );
+       return rc;
+}
+
+/**
+ * Remove PXE device
+ *
+ * @v pxe              PXE device
+ */
+void undi_remove ( struct pxe_device *pxe ) {
+       struct net_device *netdev = pxe_get_drvdata ( pxe );
+
+       unregister_netdev ( netdev );
+       free_netdev ( netdev );
+}
index 93d950c..497f20c 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "pxe_types.h"
 #include "pxe_api.h"
+#include <gpxe/device.h>
 
 /* Parameter block for pxenv_unknown() */
 struct s_PXENV_UNKNOWN {
@@ -61,38 +62,55 @@ union u_PXENV_ANY {
 
 typedef union u_PXENV_ANY PXENV_ANY_t;
 
-/* PXE stack status indicator.  See pxe_export.c for further
- * explanation.
- */
-typedef enum {
-       CAN_UNLOAD = 0,
-       MIDWAY,
-       READY
-} pxe_stack_state_t;
+/** A PXE device */
+struct pxe_device {
+       /** Generic device */
+       struct device dev;
+       /** Driver-private data
+        *
+        * Use pxe_set_drvdata() and pxe_get_drvdata() to access this
+        * field.
+        */
+       void *priv;
 
-#define ENSURE_CAN_UNLOAD(structure) if ( ! ensure_pxe_state(CAN_UNLOAD) ) { \
-                       structure->Status = PXENV_STATUS_UNDI_INVALID_STATE; \
-                       return PXENV_EXIT_FAILURE; }
-#define ENSURE_MIDWAY(structure) if ( ! ensure_pxe_state(MIDWAY) ) { \
-                       structure->Status = PXENV_STATUS_UNDI_INVALID_STATE; \
-                       return PXENV_EXIT_FAILURE; }
-#define ENSURE_READY(structure) if ( ! ensure_pxe_state(READY) ) { \
-                       structure->Status = PXENV_STATUS_UNDI_INVALID_STATE; \
-                       return PXENV_EXIT_FAILURE; }
+       /** PXENV+ structure address */
+       SEGOFF16_t pxenv;
+       /** !PXE structure address */
+       SEGOFF16_t ppxe;
+       /** Entry point */
+       SEGOFF16_t entry;
+       /** MAC address */
+       MAC_ADDR_t hwaddr;
+       /** Assigned IRQ number */
+       UINT16_t irq;
+       /** ROM address */
+       SEGSEL_t rom;
+};
 
-/* Data structures installed as part of a PXE stack.  Architectures
- * will have extra information to append to the end of this.
+/**
+ * Set PXE driver-private data
+ *
+ * @v pxe              PXE device
+ * @v priv             Private data
  */
-#define PXE_TFTP_MAGIC_COOKIE ( ( 'P'<<24 ) | ( 'x'<<16 ) | ( 'T'<<8 ) | 'f' )
-typedef struct pxe_stack {
-       struct s_PXE            pxe     __attribute__ ((aligned(16)));
-       struct s_PXENV          pxenv   __attribute__ ((aligned(16)));
-       pxe_stack_state_t       state;
-} pxe_stack_t;
+static inline void pxe_set_drvdata ( struct pxe_device *pxe, void *priv ) {
+       pxe->priv = priv;
+}
 
-extern int ensure_pxe_state ( pxe_stack_state_t wanted );
+/**
+ * Get PXE driver-private data
+ *
+ * @v pxe              PXE device
+ * @ret priv           Private data
+ */
+static inline void * pxe_get_drvdata ( struct pxe_device *pxe ) {
+       return pxe->priv;
+}
 
-extern pxe_stack_t *pxe_stack;
+extern int pxe_call ( struct pxe_device *pxe, unsigned int function,
+                     void *params, size_t params_len );
+extern int undi_probe ( struct pxe_device *pxe );
+extern void undi_remove ( struct pxe_device *pxe );
 
 extern struct net_device *pxe_netdev;