added gPXE compatible eepro100. not tested. it just compiles.
authorUdayan Kumar <udayan.kumar@gmail.com>
Mon, 23 Jul 2007 15:07:45 +0000 (11:07 -0400)
committerUdayan Kumar <udayan.kumar@gmail.com>
Sat, 18 Aug 2007 23:32:38 +0000 (19:32 -0400)
src/drivers/net/eepro100.c

index f7d888c..29f7f0c 100644 (file)
-/*
- * eepro100.c -- This file implements the eepro100 driver for etherboot.
- *
- *
- * Copyright (C) AW Computer Systems.
- * written by R.E.Wolff -- R.E.Wolff@BitWizard.nl
- *
- *
- * AW Computer Systems is contributing to the free software community
- * by paying for this driver and then putting the result under GPL.
- *
- * If you need a Linux device driver, please contact BitWizard for a
- * quote.
- *
- *
- * 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, or (at
- * your option) 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.
- *
- *
- *              date       version  by   what
- *  Written:    May 29 1997  V0.10  REW  Initial revision.
- * changes:     May 31 1997  V0.90  REW  Works!
- *              Jun 1  1997  V0.91  REW  Cleanup
- *              Jun 2  1997  V0.92  REW  Add some code documentation
- *              Jul 25 1997  V1.00  REW  Tested by AW to work in a PROM
- *                                       Cleanup for publication
- *              Dez 11 2004  V1.10  Kiszka  Add RX ring buffer support
- *
- * This is the etherboot intel etherexpress Pro/100B driver.
- *
- * It was written from scratch, with Donald Beckers eepro100.c kernel
- * driver as a guideline. Mostly the 82557 related definitions and the
- * lower level routines have been cut-and-pasted into this source.
- *
- * The driver was finished before Intel got the NDA out of the closet.
- * I still don't have the docs.
- *
- *
- * Datasheet is now published and available from 
- * ftp://download.intel.com/design/network/manuals/8255X_OpenSDM.pdf
- *    - Michael Brown
- * */
+/* 
+   eepro100.c - gPXE driver for the eepro100 8255x series. 
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <io.h>
+#include <errno.h>
+#include <timer.h>
+#include <byteswap.h>
+#include <gpxe/pci.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ethernet.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/spi_bit.h>
+#include <gpxe/threewire.h>
+#include <gpxe/nvo.h>
 
-/* Philosophy of this driver.
- *
- * Probing:
- *
- * Using the pci.c functions of the Etherboot code, the 82557 chip is detected.
- * It is verified that the BIOS initialized everything properly and if
- * something is missing it is done now.
- *
- *
- * Initialization:
- *
- *
- * The chip is then initialized to "know" its ethernet address, and to
- * start recieving packets. The Linux driver has a whole transmit and
- * recieve ring of buffers. This is neat if you need high performance:
- * you can write the buffers asynchronously to the chip reading the
- * buffers and transmitting them over the network.  Performance is NOT
- * an issue here. We can boot a 400k kernel in about two
- * seconds. (Theory: 0.4 seconds). Booting a system is going to take
- * about half a minute anyway, so getting 10 times closer to the
- * theoretical limit is going to make a difference of a few percent. */
-/* Not totally true: busy networks can cause packet drops due to RX
- * buffer overflows. Fixed in V1.10 of this driver. [Kiszka] */
-/*
- *
- * Transmitting and recieving.
- *
- * We have only one transmit descriptor. It has two buffer descriptors:
- * one for the header, and the other for the data.
- * We have multiple receive buffers (currently: 4). The chip is told to
- * receive packets and suspend itself once it ran on the last free buffer.
- * The recieve (poll) routine simply looks at the current recieve buffer,
- * picks the packet if any, and releases this buffer again (classic ring
- * buffer concept). This helps to avoid packet drops on busy networks.
- *
- * Caveats:
- *
- * The Etherboot framework moves the code to the 48k segment from
- * 0x94000 to 0xa0000. There is just a little room between the end of
- * this driver and the 0xa0000 address. If you compile in too many
- * features, this will overflow.
- * The number under "hex" in the output of size that scrolls by while
- * compiling should be less than 8000. Maybe even the stack is up there,
- * so that you need even more headroom.
- */
 
-/* The etherboot authors seem to dislike the argument ordering in
- * outb macros that Linux uses. I disklike the confusion that this
- * has caused even more.... This file uses the Linux argument ordering.  */
-/* Sorry not us. It's inherited code from FreeBSD. [The authors] */
+#define TX_RING_SIZE 4
+#define NUM_RX_DESC  4
+#define RX_BUF_SIZE 1528
+#define CONFIG_DATA_SIZE 22
 
-#include "etherboot.h"
-#include "nic.h"
-#include <gpxe/ethernet.h>
-#include <gpxe/pci.h>
-#include "timer.h"
+/* Elements of the dump_statistics block. This block must be lword aligned. */
+struct speedo_stats {
+        u32 tx_good_frames;
+        u32 tx_coll16_errs;
+        u32 tx_late_colls;
+        u32 tx_underruns;
+        u32 tx_lost_carrier;
+        u32 tx_deferred;
+        u32 tx_one_colls;
+        u32 tx_multi_colls;
+        u32 tx_total_colls;
+        u32 rx_good_frames;
+        u32 rx_crc_errs;
+        u32 rx_align_errs;
+        u32 rx_resource_errs;
+        u32 rx_overrun_errs;
+        u32 rx_colls_errs;
+        u32 rx_runt_errs;
+        u32 done_marker;
+};
+
+struct speedo_tx {
+       volatile s16 status;
+       s16 command;
+       u32 link;          /* void * */
+       u32 tx_desc_addr;  /* (almost) Always points to the tx_buf_addr element. */
+       s32 count;         /* # of TBD (=2), Tx start thresh., etc. */
+       /* This constitutes two "TBD" entries: hdr and data */
+       u32 tx_buf_addr0;  /* void *, header of frame to be transmitted.  */
+       s32 tx_buf_size0;  /* Length of Tx hdr. */
+       u32 tx_buf_addr1;  /* void *, data to be transmitted.  */
+       s32 tx_buf_size1;  /* Length of Tx data. */
+};
 
-static int ioaddr;
+struct speedo_rx {
+       volatile s16 status;
+       s16 command;
+       u32 link;                 /* struct RxFD * */
+       u32 rx_buf_addr;          /* void * */
+       u16 count;
+       u16 size;
+       char packet[RX_BUF_SIZE];
+};
+
+struct speedo_private {
+       unsigned short ioaddr;
+       unsigned short tx_cur;
+       unsigned short tx_dirty;
+       unsigned short rx_cur;
+       struct speedo_tx txfd[TX_RING_SIZE];
+       struct speedo_rx rxfd[NUM_RX_DESC];
+
+       /* netdev_tx_complete needs pointer to the iobuf of the data so as to free 
+        * it from the memory.
+        */
+       struct io_buffer *tx_iobuf[TX_RING_SIZE];
+       struct speedo_stats lstats;
+       struct spi_bit_basher spibit;
+       struct spi_device eeprom;
+       struct nvo_block nvo;
+};
 
 enum speedo_offsets {
   SCBStatus = 0, SCBCmd = 2,      /* Rx/Command Unit command and status. */
@@ -132,13 +107,13 @@ enum SCBCmdBits {
        RxResumeNoResources=0x0007,
 };
 
-static int do_eeprom_cmd(int cmd, int cmd_len);
-void hd(void *where, int n);
-
-/***********************************************************************/
-/*                       I82557 related defines                        */
-/***********************************************************************/
+static const char i82558_config_cmd[CONFIG_DATA_SIZE] = {
+       22, 0x08, 0, 1,  0, 0, 0x22, 0x03,  1, /* 1=Use MII  0=Use AUI */
+       0, 0x2E, 0,  0x60, 0x08, 0x88,
+       0x68, 0, 0x40, 0xf2, 0x84,              /* Disable FC */
+       0x31, 0x05, };
 
+unsigned short eeprom[16];
 /* Serial EEPROM section.
    A "bit" grungy, but we work our way through bit-by-bit :->. */
 /*  EEPROM_Ctrl bits. */
@@ -153,6 +128,7 @@ void hd(void *where, int n);
 /* The EEPROM commands include the alway-set leading bit. */
 #define EE_READ_CMD     6
 
+
 /* The SCB accepts the following controls for the Tx and Rx units: */
 #define  CU_START       0x0010
 #define  CU_RESUME      0x0020
@@ -189,6 +165,37 @@ enum commands {
   CmdTxFlex = 0x0008,       /* Use "Flexible mode" for CmdTx command. */
 };
 
+/* Selected elements of the Tx/RxFD.status word. */
+enum RxFD_bits {
+       RxComplete=0x8000, RxOK=0x2000,
+       RxErrCRC=0x0800, RxErrAlign=0x0400, RxErrTooBig=0x0200, RxErrSymbol=0x0010,
+       RxEth2Type=0x0020, RxNoMatch=0x0004, RxNoIAMatch=0x0002,
+       TxUnderrun=0x1000,  StatusComplete=0x8000,
+};
+
+static int congenb = 0;         /* Enable congestion control in the DP83840. */
+static int txfifo = 8;          /* Tx FIFO threshold in 4 byte units, 0-15 */
+static int rxfifo = 8;          /* Rx FIFO threshold, default 32 bytes. */
+static int txdmacount = 0;      /* Tx DMA burst length, 0-127, default 0. */
+static int rxdmacount = 0;      /* Rx DMA length, 0 means no preemption. */
+
+
+/*  Function Prototypes: */
+static int speedo_open (struct net_device *netdev);
+static int speedo_transmit (struct net_device *netdev, struct io_buffer *iobuf);
+static void speedo_poll (struct net_device *netdev);
+static void speedo_disable ( struct net_device *netdev); 
+
+/** speedo net device operations */
+static struct net_device_operations speedo_operations = {
+        .open           = speedo_open,
+        .close          = speedo_disable,
+        .transmit       = speedo_transmit,
+        .poll           = speedo_poll,
+//     .irq            = speedo_irq,
+};
+
 /* How to wait for the command unit to accept a command.
    Typically this takes 0 ticks. */
 static inline void wait_for_cmd_done(int cmd_ioaddr)
@@ -206,102 +213,23 @@ static inline void wait_for_cmd_done(int cmd_ioaddr)
   printf("Command %2.2x was not immediately accepted, %d ticks!\n",
       delayed_cmd, wait);
 }
-
-/* Elements of the dump_statistics block. This block must be lword aligned. */
-static struct speedo_stats {
-        u32 tx_good_frames;
-        u32 tx_coll16_errs;
-        u32 tx_late_colls;
-        u32 tx_underruns;
-        u32 tx_lost_carrier;
-        u32 tx_deferred;
-        u32 tx_one_colls;
-        u32 tx_multi_colls;
-        u32 tx_total_colls;
-        u32 rx_good_frames;
-        u32 rx_crc_errs;
-        u32 rx_align_errs;
-        u32 rx_resource_errs;
-        u32 rx_overrun_errs;
-        u32 rx_colls_errs;
-        u32 rx_runt_errs;
-        u32 done_marker;
-} lstats;
-
-/* A speedo3 TX buffer descriptor with two buffers... */
-static struct TxFD {
-       volatile s16 status;
-       s16 command;
-       u32 link;          /* void * */
-       u32 tx_desc_addr;  /* (almost) Always points to the tx_buf_addr element. */
-       s32 count;         /* # of TBD (=2), Tx start thresh., etc. */
-       /* This constitutes two "TBD" entries: hdr and data */
-       u32 tx_buf_addr0;  /* void *, header of frame to be transmitted.  */
-       s32 tx_buf_size0;  /* Length of Tx hdr. */
-       u32 tx_buf_addr1;  /* void *, data to be transmitted.  */
-       s32 tx_buf_size1;  /* Length of Tx data. */
-} txfd;
-
-struct RxFD {               /* Receive frame descriptor. */
-       volatile s16 status;
-       s16 command;
-       u32 link;                 /* struct RxFD * */
-       u32 rx_buf_addr;          /* void * */
-       u16 count;
-       u16 size;
-       char packet[1518];
-};
-
-static struct nic_operations eepro100_operations;
-
-#define RXFD_COUNT 4
-struct {
-       struct RxFD rxfds[RXFD_COUNT];
-} eepro100_bufs __shared;
-#define rxfds eepro100_bufs.rxfds
-static unsigned int rxfd = 0;
-
-static int congenb = 0;         /* Enable congestion control in the DP83840. */
-static int txfifo = 8;          /* Tx FIFO threshold in 4 byte units, 0-15 */
-static int rxfifo = 8;          /* Rx FIFO threshold, default 32 bytes. */
-static int txdmacount = 0;      /* Tx DMA burst length, 0-127, default 0. */
-static int rxdmacount = 0;      /* Rx DMA length, 0 means no preemption. */
-
-/* I don't understand a byte in this structure. It was copied from the
- * Linux kernel initialization for the eepro100. -- REW */
-static struct ConfCmd {
-  s16 status;
-  s16 command;
-  u32 link;
-  unsigned char data[22];
-} confcmd = {
-  0, 0, 0, /* filled in later */
-  {22, 0x08, 0, 0,  0, 0x80, 0x32, 0x03,  1, /* 1=Use MII  0=Use AUI */
-   0, 0x2E, 0,  0x60, 0,
-   0xf2, 0x48,   0, 0x40, 0xf2, 0x80,        /* 0x40=Force full-duplex */
-   0x3f, 0x05, }
-};
-
-/***********************************************************************/
-/*                       Locally used functions                        */
-/***********************************************************************/
-
 /* Support function: mdio_write
  *
  * This probably writes to the "physical media interface chip".
  * -- REW
  */
 
-static int mdio_write(int phy_id, int location, int value)
+static int mdio_write(struct net_device *netdev, int phy_id, int location, int value)
 {
+       struct speedo_private *sp = netdev->priv;
        int val, boguscnt = 64*4;         /* <64 usec. to complete, typ 27 ticks */
 
        outl(0x04000000 | (location<<16) | (phy_id<<21) | value,
-            ioaddr + SCBCtrlMDI);
+            sp->ioaddr + SCBCtrlMDI);
        do {
                udelay(16);
 
-               val = inl(ioaddr + SCBCtrlMDI);
+               val = inl(sp->ioaddr + SCBCtrlMDI);
                if (--boguscnt < 0) {
                        printf(" mdio_write() timed out with val = %X.\n", val);
                        break;
@@ -315,14 +243,15 @@ static int mdio_write(int phy_id, int location, int value)
  * This probably reads a register in the "physical media interface chip".
  * -- REW
  */
-static int mdio_read(int phy_id, int location)
+static int mdio_read(struct net_device *netdev,int phy_id, int location)
 {
+       struct speedo_private *sp = netdev->priv;
        int val, boguscnt = 64*4;               /* <64 usec. to complete, typ 27 ticks */
-       outl(0x08000000 | (location<<16) | (phy_id<<21), ioaddr + SCBCtrlMDI);
+       outl(0x08000000 | (location<<16) | (phy_id<<21), sp->ioaddr + SCBCtrlMDI);
        do {
                udelay(16);
 
-               val = inl(ioaddr + SCBCtrlMDI);
+               val = inl(sp->ioaddr + SCBCtrlMDI);
 
                if (--boguscnt < 0) {
                        printf( " mdio_read() timed out with val = %X.\n", val);
@@ -331,7 +260,6 @@ static int mdio_read(int phy_id, int location)
        } while (! (val & 0x10000000));
        return val & 0xffff;
 }
-
 /* The fixes for the code were kindly provided by Dragan Stancevic
    <visitor@valinux.com> to strictly follow Intel specifications of EEPROM
    access timing.
@@ -339,10 +267,11 @@ static int mdio_read(int phy_id, int location)
    interval for serial EEPROM.  However, it looks like that there is an
    additional requirement dictating larger udelay's in the code below.
    2000/05/24  SAW */
-static int do_eeprom_cmd(int cmd, int cmd_len)
+static int do_eeprom_cmd(struct net_device *netdev,int cmd, int cmd_len)
 {
+       struct speedo_private *sp = netdev->priv;
        unsigned retval = 0;
-       long ee_addr = ioaddr + SCBeeprom;
+       long ee_addr = sp->ioaddr + SCBeeprom;
 
        outw(EE_ENB, ee_addr); udelay(2);
        outw(EE_ENB | EE_SHIFT_CLK, ee_addr); udelay(2);
@@ -361,203 +290,6 @@ static int do_eeprom_cmd(int cmd, int cmd_len)
        return retval;
 }
 
-#if 0
-static inline void whereami (const char *str)
-{
-  printf ("%s\n", str);
-  sleep (2);
-}
-#else
-#define whereami(s)
-#endif
-
-static void eepro100_irq(struct nic *nic __unused, irq_action_t action)
-{
-       uint16_t enabled_mask = ( SCBMaskCmdDone | SCBMaskCmdIdle |
-                                 SCBMaskEarlyRx | SCBMaskFlowCtl );
-
-       switch ( action ) {
-       case DISABLE :
-               outw(SCBMaskAll, ioaddr + SCBCmd);
-               break;
-       case ENABLE :
-               outw(enabled_mask, ioaddr + SCBCmd);
-               break;
-       case FORCE :
-               outw(enabled_mask | SCBTriggerIntr, ioaddr + SCBCmd);
-               break;
-       }
-}
-
-/* function: eepro100_transmit
- * This transmits a packet.
- *
- * Arguments: char d[6]:          destination ethernet address.
- *            unsigned short t:   ethernet protocol type.
- *            unsigned short s:   size of the data-part of the packet.
- *            char *p:            the data for the packet.
- * returns:   void.
- */
-
-static void eepro100_transmit(struct nic *nic, const char *d, unsigned int t, unsigned int s, const char *p)
-{
-       struct eth_hdr {
-               unsigned char dst_addr[ETH_ALEN];
-               unsigned char src_addr[ETH_ALEN];
-               unsigned short type;
-       } hdr;
-       unsigned short status;
-       int s1, s2;
-
-       status = inw(ioaddr + SCBStatus);
-       /* Acknowledge all of the current interrupt sources ASAP. */
-       outw(status & 0xfc00, ioaddr + SCBStatus);
-
-#ifdef DEBUG
-       printf ("transmitting type %hX packet (%d bytes). status = %hX, cmd=%hX\n",
-               t, s, status, inw (ioaddr + SCBCmd));
-#endif
-
-       memcpy (&hdr.dst_addr, d, ETH_ALEN);
-       memcpy (&hdr.src_addr, nic->node_addr, ETH_ALEN);
-
-       hdr.type = htons (t);
-
-       txfd.status = 0;
-       txfd.command = CmdSuspend | CmdTx | CmdTxFlex;
-       txfd.link   = virt_to_bus (&txfd);
-       txfd.count   = 0x02208000;
-       txfd.tx_desc_addr = virt_to_bus(&txfd.tx_buf_addr0);
-
-       txfd.tx_buf_addr0 = virt_to_bus (&hdr);
-       txfd.tx_buf_size0 = sizeof (hdr);
-
-       txfd.tx_buf_addr1 = virt_to_bus (p);
-       txfd.tx_buf_size1 = s;
-
-#ifdef DEBUG
-       printf ("txfd: \n");
-       hd (&txfd, sizeof (txfd));
-#endif
-
-       outl(virt_to_bus(&txfd), ioaddr + SCBPointer);
-       outb(CU_START, ioaddr + SCBCmd);
-       wait_for_cmd_done(ioaddr + SCBCmd);
-
-       s1 = inw (ioaddr + SCBStatus);
-       load_timer2(10*TICKS_PER_MS);           /* timeout 10 ms for transmit */
-       while (!txfd.status && timer2_running())
-               /* Wait */;
-       s2 = inw (ioaddr + SCBStatus);
-
-#ifdef DEBUG
-       printf ("s1 = %hX, s2 = %hX.\n", s1, s2);
-#endif
-}
-
-/*
- * Sometimes the receiver stops making progress.  This routine knows how to
- * get it going again, without losing packets or being otherwise nasty like
- * a chip reset would be.  Previously the driver had a whole sequence
- * of if RxSuspended, if it's no buffers do one thing, if it's no resources,
- * do another, etc.  But those things don't really matter.  Separate logic
- * in the ISR provides for allocating buffers--the other half of operation
- * is just making sure the receiver is active.  speedo_rx_soft_reset does that.
- * This problem with the old, more involved algorithm is shown up under
- * ping floods on the order of 60K packets/second on a 100Mbps fdx network.
- */
-static void
-speedo_rx_soft_reset(void)
-{
-       int i;
-
-
-#ifdef DEBUG
-       printf("reset\n");
-#endif
-       wait_for_cmd_done(ioaddr + SCBCmd);
-       /*
-        * Put the hardware into a known state.
-        */
-       outb(RX_ABORT, ioaddr + SCBCmd);
-
-       for (i = 0; i < RXFD_COUNT; i++) {
-               rxfds[i].status      = 0;
-               rxfds[i].rx_buf_addr = 0xffffffff;
-               rxfds[i].count       = 0;
-               rxfds[i].size        = 1528;
-       }
-
-       wait_for_cmd_done(ioaddr + SCBCmd);
-
-       outl(virt_to_bus(&rxfds[rxfd]), ioaddr + SCBPointer);
-       outb(RX_START, ioaddr + SCBCmd);
-}
-
-/* function: eepro100_poll / eth_poll
- * This receives a packet from the network.
- *
- * Arguments: none
- *
- * returns:   1 if a packet was received.
- *            0 if no packet was received.
- * side effects:
- *            returns the packet in the array nic->packet.
- *            returns the length of the packet in nic->packetlen.
- */
-
-static int eepro100_poll(struct nic *nic, int retrieve)
-{
-       if (rxfds[rxfd].status) {
-               if (!retrieve)
-                       return 1;
-#ifdef DEBUG
-               printf("Got a packet: Len = %d, rxfd = %d.\n",
-                      rxfds[rxfd].count & 0x3fff, rxfd);
-#endif
-               /* First save the data from the rxfd */
-               nic->packetlen = rxfds[rxfd].count & 0x3fff;
-               memcpy(nic->packet, rxfds[rxfd].packet, nic->packetlen);
-
-               rxfds[rxfd].status      = 0;
-               rxfds[rxfd].command     = 0xc000;
-               rxfds[rxfd].rx_buf_addr = 0xFFFFFFFF;
-               rxfds[rxfd].count       = 0;
-               rxfds[rxfd].size        = 1528;
-               rxfds[(rxfd-1) % RXFD_COUNT].command = 0x0000;
-               rxfd = (rxfd+1) % RXFD_COUNT;
-
-#ifdef DEBUG
-               hd (nic->packet, 0x30);
-#endif
-
-               /* Acknowledge all conceivable interrupts */
-               outw(0xff00, ioaddr + SCBStatus);
-
-               return 1;
-       }
-
-       /*
-        * The chip may have suspended reception for various reasons.
-        * Check for that, and re-prime it should this be the case.
-        */
-       switch ((inw(ioaddr + SCBStatus) >> 2) & 0xf) {
-               case 0:  /* Idle */
-                       break;
-               case 1:  /* Suspended */
-               case 2:  /* No resources (RxFDs) */
-               case 9:  /* Suspended with no more RBDs */
-               case 10: /* No resources due to no RBDs */
-               case 12: /* Ready with no RBDs */
-                       speedo_rx_soft_reset();
-                       break;
-               default:
-                       /* reserved values */
-                       break;
-       }
-       return 0;
-}
-
 /* function: eepro100_disable
  * resets the card. This is used to allow Etherboot or Linux
  * to probe the card again from a "virginal" state....
@@ -565,13 +297,14 @@ static int eepro100_poll(struct nic *nic, int retrieve)
  *
  * returns:   void.
  */
-static void eepro100_disable ( struct nic *nic __unused ) {
+static void speedo_disable ( struct net_device *netdev) {
+       struct speedo_private *sp = netdev->priv;
 /* from eepro100_reset */
-       outl(0, ioaddr + SCBPort);
+       outl(0, sp->ioaddr + SCBPort);
 /* from eepro100_disable */
        /* See if this PartialReset solves the problem with interfering with
           kernel operation after Etherboot hands over. - Ken 20001102 */
-       outl(2, ioaddr + SCBPort);
+       outl(2, sp->ioaddr + SCBPort);
 
        /* The following is from the Intel e100 driver.
         * This hopefully solves the problem with hanging hard DOS images. */
@@ -583,41 +316,49 @@ static void eepro100_disable ( struct nic *nic __unused ) {
        {
                u16 intr_status;
                /* Disable interrupts on our PCI board by setting the mask bit */
-               outw(INT_MASK, ioaddr + SCBCmd);
-               intr_status = inw(ioaddr + SCBStatus);
+               outw(INT_MASK, sp->ioaddr + SCBCmd);
+               intr_status = inw(sp->ioaddr + SCBStatus);
                /* ack and clear intrs */
-               outw(intr_status, ioaddr + SCBStatus);
-               inw(ioaddr + SCBStatus);
+               outw(intr_status, sp->ioaddr + SCBStatus);
+               inw(sp->ioaddr + SCBStatus);
        }
 }
 
-/* exported function: eepro100_probe / eth_probe
- * initializes a card
+
+/**
+ * Probe PCI device
  *
- * side effects:
- *            leaves the ioaddress of the 82557 chip in the variable ioaddr.
- *            leaves the 82557 initialized, and ready to recieve packets.
+ * @v pci      PCI device
+ * @v id       PCI ID
+ * @ret rc     Return status code
  */
-
-static int eepro100_probe ( struct nic *nic, struct pci_device *p ) {
-
-       unsigned short sum = 0;
-       int i;
+static int speedo_probe (struct pci_device *pci,
+                      const struct pci_device_id *id __unused) {
+       struct net_device *netdev;
+       struct speedo_private *sp = NULL;
+       int rc;
        int read_cmd, ee_size;
-       int options;
-       int rx_mode;
-
-       /* we cache only the first few words of the EEPROM data
-          be careful not to access beyond this array */
-       unsigned short eeprom[16];
+       int i, sum;
 
-       if (p->ioaddr == 0)
-               return 0;
-       pci_fill_nic ( nic, p );
-       adjust_pci_device(p);
-       ioaddr = nic->ioaddr;
-
-       if ((do_eeprom_cmd(EE_READ_CMD << 24, 27) & 0xffe0000)
+       /* Allocate net device 
+        */
+       netdev = alloc_etherdev (sizeof (*sp));
+       if (! netdev) 
+               return -ENOMEM;
+
+       netdev_init (netdev, &speedo_operations);
+       sp = netdev->priv;
+       pci_set_drvdata (pci, netdev);
+       netdev->dev = &pci->dev;
+       memset (sp, 0, sizeof (*sp));
+       sp->ioaddr = pci->ioaddr;
+
+       adjust_pci_device (pci);
+
+       //natsemi_reset (netdev);
+       
+       /* read the MAC Address */
+       if ((do_eeprom_cmd(netdev,EE_READ_CMD << 24, 27) & 0xffe0000)
                == 0xffe0000) {
                ee_size = 0x100;
                read_cmd = EE_READ_CMD << 24;
@@ -627,172 +368,383 @@ static int eepro100_probe ( struct nic *nic, struct pci_device *p ) {
        }
 
        for (i = 0, sum = 0; i < ee_size; i++) {
-               unsigned short value = do_eeprom_cmd(read_cmd | (i << 16), 27);
+               unsigned short value = do_eeprom_cmd(netdev,read_cmd | (i << 16), 27);
                if (i < (int)(sizeof(eeprom)/sizeof(eeprom[0])))
                        eeprom[i] = value;
                sum += value;
        }
 
        for (i=0;i<ETH_ALEN;i++) {
-               nic->node_addr[i] =  (eeprom[i/2] >> (8*(i&1))) & 0xff;
+               netdev->ll_addr[i] =  (eeprom[i/2] >> (8*(i&1))) & 0xff;
        }
 
-       DBG ( "Ethernet addr: %s\n", eth_ntoa ( nic->node_addr ) );
-
-       if (sum != 0xBABA)
-               printf("eepro100: Invalid EEPROM checksum %#hX, "
-                      "check settings before activating this device!\n", sum);
-       outl(0, ioaddr + SCBPort);
-       udelay (10000);
-       whereami ("Got eeprom.");
-
-       /* Base = 0, disable all interrupts  */
-       outl(0, ioaddr + SCBPointer);
-       outw(INT_MASK | RX_ADDR_LOAD, ioaddr + SCBCmd);
-       wait_for_cmd_done(ioaddr + SCBCmd);
-       whereami ("set rx base addr.");
-
-       outl(virt_to_bus(&lstats), ioaddr + SCBPointer);
-       outb(CU_STATSADDR, ioaddr + SCBCmd);
-       wait_for_cmd_done(ioaddr + SCBCmd);
-       whereami ("set stats addr.");
-
-       /* INIT RX stuff. */
-       for (i = 0; i < RXFD_COUNT; i++) {
-               rxfds[i].status      = 0x0000;
-               rxfds[i].command     = 0x0000;
-               rxfds[i].rx_buf_addr = 0xFFFFFFFF;
-               rxfds[i].count       = 0;
-               rxfds[i].size        = 1528;
-               rxfds[i].link        = virt_to_bus(&rxfds[i+1]);
-       }
 
-       rxfds[RXFD_COUNT-1].status  = 0x0000;
-       rxfds[RXFD_COUNT-1].command = 0xC000;
-       rxfds[RXFD_COUNT-1].link    = virt_to_bus(&rxfds[0]);
+       if ((rc = register_netdev (netdev)) != 0)
+               goto err_register_netdev;
 
-       outl(virt_to_bus(&rxfds[0]), ioaddr + SCBPointer);
-       outb(RX_START, ioaddr + SCBCmd);
-       wait_for_cmd_done(ioaddr + SCBCmd);
+       return 0;
 
-       whereami ("started RX process.");
+err_register_netdev:
 
-       /* INIT TX stuff. */
+       speedo_disable (netdev);
+       netdev_put (netdev);
+       return rc;
+}
 
-       /* Base = 0 */
-       outl(0, ioaddr + SCBPointer);
-       outb(CU_CMD_BASE, ioaddr + SCBCmd);
-       wait_for_cmd_done(ioaddr + SCBCmd);
+/**
+ * Open NIC
+ *
+ * @v netdev           Net device
+ * @ret rc             Return status code
+ */
+static int speedo_open (struct net_device *netdev) { 
+       struct speedo_private *sp = netdev->priv; 
+       int i; 
+       int options = 0x00;
+       int rx_mode=0;
+       /* Set MAC address in NIC */
+       /*TODO
+       for (i = 0 ; i < ETH_ALEN ; i+=2) {
+               outl (i, np->ioaddr + RxFilterAddr);
+               outw (netdev->ll_addr[i] + (netdev->ll_addr[i + 1] << 8),
+                      np->ioaddr + RxFilterData);
+       }
+       */
+       /* Setup RX ring
+        */
+       /*TODO check what is required */
+       outl(0, sp->ioaddr + SCBPointer);
+       outw(INT_MASK | RX_ADDR_LOAD, sp->ioaddr + SCBCmd);
+       wait_for_cmd_done(sp->ioaddr + SCBCmd);
+
+       outl(virt_to_bus(&sp->lstats), sp->ioaddr + SCBPointer);
+       outb(CU_STATSADDR, sp->ioaddr + SCBCmd);
+       wait_for_cmd_done(sp->ioaddr + SCBCmd);
+
+
+       sp->rx_cur = 0;
+       for (i = 0 ; i < NUM_RX_DESC ; i++) {
+               sp->rxfd[i].status      = 0x0000;
+               sp->rxfd[i].command     = 0x0000;
+               sp->rxfd[i].rx_buf_addr = 0xFFFFFFFF;
+               sp->rxfd[i].count       = 0;
+               sp->rxfd[i].size        = RX_BUF_SIZE;
+               sp->rxfd[i].link        = virt_to_bus(&sp->rxfd[i+1]);
+       }
 
-       whereami ("set TX base addr.");
+       sp->rxfd[NUM_RX_DESC -1].status  = 0x0000;
+       sp->rxfd[NUM_RX_DESC -1].command = 0xC000;
+       sp->rxfd[NUM_RX_DESC -1].link    = virt_to_bus(&sp->rxfd[0]);
 
-       txfd.command      = (CmdIASetup);
-       txfd.status       = 0x0000;
-       txfd.link         = virt_to_bus (&confcmd);
 
-       {
-               char *t = (char *)&txfd.tx_desc_addr;
+       outl(virt_to_bus(&sp->rxfd[0]), sp->ioaddr + SCBPointer);
+       outb(RX_START, sp->ioaddr + SCBCmd);
+       wait_for_cmd_done(sp->ioaddr + SCBCmd);
 
-               for (i=0;i<ETH_ALEN;i++)
-                       t[i] = nic->node_addr[i];
+       /* Setup Tx Ring 
+        */
+       /* Base = 0 */
+       outl(0, sp->ioaddr + SCBPointer);
+       outb(CU_CMD_BASE, sp->ioaddr + SCBCmd);
+       wait_for_cmd_done(sp->ioaddr + SCBCmd);
+       sp->tx_cur = 0;
+       sp->tx_dirty = 0;
+       for (i = 0 ; i < TX_RING_SIZE ; i++) {
+               sp->txfd[i].link   = virt_to_bus ((i + 1 < TX_RING_SIZE) ? &
+                                                  sp->txfd[i + 1] : &sp->txfd[0]);
+               sp->txfd[i].command = (CmdSuspend);
+               sp->txfd[i].status = 0;
+               sp->txfd[i].tx_desc_addr = virt_to_bus(&sp->txfd[i].tx_buf_addr0);
        }
 
-#ifdef DEBUG
-       printf ("Setup_eaddr:\n");
-       hd (&txfd, 0x20);
-#endif
-       /*      options = 0x40; */ /* 10mbps half duplex... */
-       options = 0x00;            /* Autosense */
-
-#ifdef PROMISC
-       rx_mode = 3;
-#elif ALLMULTI
-       rx_mode = 1;
-#else
-       rx_mode = 0;
-#endif
-
-       if (   ((eeprom[6]>>8) & 0x3f) == DP83840
+       
+       /* Setting up the RX mode */
+       /* Set or clear the multicast filter for this adaptor.
+          This is very ugly with Intel chips -- we usually have to execute an
+          entire configuration command, plus process a multicast command.
+          This is complicated.  We must put a large configuration command and
+          an arbitrarily-sized multicast command in the transmit list.
+          To minimize the disruption -- the previous command might have already
+          loaded the link -- we convert the current command block, normally a Tx
+          command, into a no-op and link it to the new command.
+       */
+       
+               if (   ((eeprom[6]>>8) & 0x3f) == DP83840
               || ((eeprom[6]>>8) & 0x3f) == DP83840A) {
-               int mdi_reg23 = mdio_read(eeprom[6] & 0x1f, 23) | 0x0422;
+               int mdi_reg23 = mdio_read(netdev,eeprom[6] & 0x1f, 23) | 0x0422;
                if (congenb)
                        mdi_reg23 |= 0x0100;
-               printf("  DP83840 specific setup, setting register 23 to %hX.\n",
+               DBG("  DP83840 specific setup, setting register 23 to %hX.\n",
                       mdi_reg23);
-               mdio_write(eeprom[6] & 0x1f, 23, mdi_reg23);
+               mdio_write(netdev,eeprom[6] & 0x1f, 23, mdi_reg23);
        }
-       whereami ("Done DP8340 special setup.");
+       DBG ("Done DP8340 special setup.\n");
        if (options != 0) {
-               mdio_write(eeprom[6] & 0x1f, 0,
+               mdio_write(netdev,eeprom[6] & 0x1f, 0,
                           ((options & 0x20) ? 0x2000 : 0) |    /* 100mbps? */
                           ((options & 0x10) ? 0x0100 : 0)); /* Full duplex? */
-               whereami ("set mdio_register.");
+               DBG ("set mdio_register.\n");
        }
 
-       confcmd.command  = CmdSuspend | CmdConfigure;
-       confcmd.status   = 0x0000;
-       confcmd.link     = virt_to_bus (&txfd);
-       confcmd.data[1]  = (txfifo << 4) | rxfifo;
-       confcmd.data[4]  = rxdmacount;
-       confcmd.data[5]  = txdmacount + 0x80;
-       confcmd.data[15] = (rx_mode & 2) ? 0x49: 0x48;
-       confcmd.data[19] = (options & 0x10) ? 0xC0 : 0x80;
-       confcmd.data[21] = (rx_mode & 1) ? 0x0D: 0x05;
-
-       outl(virt_to_bus(&txfd), ioaddr + SCBPointer);
-       outb(CU_START, ioaddr + SCBCmd);
-       wait_for_cmd_done(ioaddr + SCBCmd);
-
-       whereami ("started TX thingy (config, iasetup).");
-
-       load_timer2(10*TICKS_PER_MS);
-       while (!txfd.status && timer2_running())
-               /* Wait */;
+       
+       sp->tx_cur++;
+       sp->tx_dirty++;
+       sp->txfd[0].status = (CmdSuspend | CmdConfigure);
+       uint8_t * config_cmd_data;
+       config_cmd_data = (void *)&sp->txfd[0].tx_desc_addr;
+       /* Construct a full CmdConfig frame. */
+       memcpy(config_cmd_data, i82558_config_cmd, CONFIG_DATA_SIZE);   
+       config_cmd_data[1]  = (txfifo << 4) | rxfifo;
+       config_cmd_data[4]  = rxdmacount;
+       config_cmd_data[5]  = txdmacount + 0x80;
+       config_cmd_data[15] = (rx_mode & 2) ? 0x49: 0x48;
+       config_cmd_data[19] = (options & 0x10) ? 0xC0 : 0x80;
+       config_cmd_data[21] = (rx_mode & 1) ? 0x0D: 0x05;
+
+       outl(virt_to_bus(&sp->txfd[0]), sp->ioaddr + SCBPointer);
+       outb(CU_START, sp->ioaddr + SCBCmd);
+       wait_for_cmd_done(sp->ioaddr + SCBCmd);
 
        /* Read the status register once to disgard stale data */
-       mdio_read(eeprom[6] & 0x1f, 1);
+       mdio_read(netdev,eeprom[6] & 0x1f, 1);
        /* Check to see if the network cable is plugged in.
         * This allows for faster failure if there is nothing
         * we can do.
         */
-       if (!(mdio_read(eeprom[6] & 0x1f, 1) & (1 << 2))) {
+       if (!(mdio_read(netdev,eeprom[6] & 0x1f, 1) & (1 << 2))) {
                printf("Valid link not established\n");
-               eepro100_disable(nic);
-               return 0;
+               speedo_disable(netdev);
+               return -1;
        }
-       nic->nic_op     = &eepro100_operations;
-       return 1;
+
+
+       
+
+       return 0;
+                      
 }
 
-/*********************************************************************/
 
-#ifdef DEBUG
+/** 
+ * Transmit packet
+ *
+ * @v netdev   Network device
+ * @v iobuf    I/O buffer
+ * @ret rc     Return status code
+ */
+static int speedo_transmit (struct net_device *netdev, struct io_buffer *iobuf)
+{
+       struct speedo_private *sp = netdev->priv;
+       unsigned short status;
+
+       status = inw(sp->ioaddr + SCBStatus);
+       /* Acknowledge all of the current interrupt sources ASAP. */
+       outw(status & 0xfc00, sp->ioaddr + SCBStatus);
+
+       if ((sp->tx_cur + 1)% TX_RING_SIZE == sp->tx_dirty) {
+               DBG ("TX overflow\n");
+               return -ENOBUFS;
+       }
+
+       /* Used by netdev_tx_complete ()
+        */
+       sp->tx_iobuf[sp->tx_cur] = iobuf;
 
-/* Hexdump a number of bytes from memory... */
-void hd (void *where, int n)
+       /* Pad and align packet has not been used because its not required 
+        * by the hardware.
+        *      iob_pad (iobuf, ETH_ZLEN); 
+        * can be used to achieve it, if required
+        */
+
+       /* Add the packet to TX ring
+        */
+
+       sp->txfd[sp->tx_cur].status =  0;
+       sp->txfd[sp->tx_cur].command = CmdSuspend | CmdTx | CmdTxFlex;
+       sp->txfd[sp->tx_cur].count =  0x02208000;
+       sp->txfd[sp->tx_cur].tx_buf_addr0 = virt_to_bus (iobuf->data);
+       sp->txfd[sp->tx_cur].tx_buf_size0 = iob_len(iobuf);
+
+       DBG ("TX id %d at %#08lx + %#08x\n", sp->tx_cur,
+            virt_to_bus (&iobuf->data), iob_len (iobuf));
+
+
+       /* start the transmitter 
+        *
+        * Removing suspend from the command of previous packet.  
+        * As linx driver does
+        */
+       sp->txfd[((sp->tx_cur-1) < 0 ? TX_RING_SIZE -1 : sp->tx_cur -1)].command 
+                                                               &= ~CmdSuspend; 
+       /* We want the time window between clearing suspend flag on the previous
+          command and resuming CU to be as small as possible.
+          Interrupts in between are very undesired.  --SAW */
+       outb(CUResume, sp->ioaddr + SCBCmd);
+
+       /* increment the circular buffer pointer to the next buffer location
+        */
+       sp->tx_cur = (sp->tx_cur + 1) % TX_RING_SIZE;
+
+       return 0;
+}
+
+
+
+/*
+ * Sometimes the receiver stops making progress.  This routine knows how to
+ * get it going again, without losing packets or being otherwise nasty like
+ * a chip reset would be.  Previously the driver had a whole sequence
+ * of if RxSuspended, if it's no buffers do one thing, if it's no resources,
+ * do another, etc.  But those things don't really matter.  Separate logic
+ * in the ISR provides for allocating buffers--the other half of operation
+ * is just making sure the receiver is active.  speedo_rx_soft_reset does that.
+ * This problem with the old, more involved algorithm is shown up under
+ * ping floods on the order of 60K packets/second on a 100Mbps fdx network.
+ */
+static void
+speedo_rx_soft_reset(struct net_device *netdev)
 {
+       struct speedo_private *sp = netdev->priv;
        int i;
 
-       while (n > 0) {
-               printf ("%X ", where);
-               for (i=0;i < ( (n>16)?16:n);i++)
-                       printf (" %hhX", ((char *)where)[i]);
-               printf ("\n");
-               n -= 16;
-               where += 16;
+
+       DBG("reset\n");
+       wait_for_cmd_done(sp->ioaddr + SCBCmd);
+       /*
+        * Put the hardware into a known state.
+        */
+       outb(RX_ABORT, sp->ioaddr + SCBCmd);
+
+       for (i = 0; i < NUM_RX_DESC; i++) {
+               sp->rxfd[i].status      = 0;
+               sp->rxfd[i].rx_buf_addr = 0xffffffff;
+               sp->rxfd[i].count       = 0;
+               sp->rxfd[i].size        = RX_BUF_SIZE;
        }
+
+       wait_for_cmd_done(sp->ioaddr + SCBCmd);
+
+       outl(virt_to_bus(&sp->rxfd[sp->rx_cur]), sp->ioaddr + SCBPointer);
+       outb(RX_START, sp->ioaddr + SCBCmd);
 }
-#endif
 
-static struct nic_operations eepro100_operations = {
-       .connect        = dummy_connect,
-       .poll           = eepro100_poll,
-       .transmit       = eepro100_transmit,
-       .irq            = eepro100_irq,
 
-};
 
-static struct pci_device_id eepro100_nics[] = {
+
+
+/** 
+ * Poll for received packets
+ *
+ * @v netdev   Network device
+ */
+static void speedo_poll (struct net_device *netdev)
+{
+       struct speedo_private *sp = netdev->priv;
+       uint16_t tx_status;
+       unsigned int rx_status;
+       unsigned int intr_status;
+       unsigned int rx_len;
+       struct io_buffer *rx_iob;
+       int i;
+       
+       /* read the interrupt register TODO
+        */
+       intr_status = inw (sp->ioaddr + SCBStatus);
+
+       //if (!intr_status)
+       //      goto end;
+
+        DBG ("eepro/speedo speedo_poll: intr_status = %#08x\n", intr_status);
+
+       /* Check status of transmitted packets
+        */
+       i = sp->tx_dirty;
+       while (i != sp->tx_cur) {
+               tx_status = sp->txfd[sp->tx_dirty].status;
+
+               DBG ("tx_dirty = %d tx_cur=%d tx_status=%#08x\n",
+                    sp->tx_dirty, sp->tx_cur, tx_status);
+               
+               if (( tx_status & StatusComplete ) == 0) 
+                       break;
+
+               netdev_tx_complete (netdev, sp->tx_iobuf[sp->tx_dirty]);
+               DBG ("Success transmitting packet\n");
+
+               sp->tx_dirty = (sp->tx_dirty + 1) % TX_RING_SIZE;
+               i = (i + 1) % TX_RING_SIZE;
+       }
+       
+       /* Process received packets 
+        */
+       rx_status = sp->rxfd[sp->rx_cur].status; 
+       while (rx_status) {
+               rx_len = sp->rxfd[sp->rx_cur].count & 0x3fff;
+
+                DBG ("Received packet, rx_curr = %d, rx_status = %#08x, rx_len = %d\n",
+                     sp->rx_cur, rx_status, rx_len);
+               /* If unable allocate space for this packet,
+                *  try again next poll
+                */
+               rx_iob = alloc_iob (rx_len);
+               if (! rx_iob) 
+                       break;
+               memcpy (iob_put (rx_iob, rx_len), 
+                       sp->rxfd[sp->rx_cur].packet, rx_len);
+               /* Add this packet to the receive queue. 
+                */
+               netdev_rx (netdev, rx_iob);
+               
+               sp->rxfd[sp->rx_cur].status = 0;
+               sp->rxfd[sp->rx_cur].command = 0xc000;
+               sp->rxfd[sp->rx_cur].rx_buf_addr = 0xFFFFFFFF;
+               sp->rxfd[sp->rx_cur].count = 0;
+               sp->rxfd[sp->rx_cur].size = RX_BUF_SIZE;
+               sp->rxfd[(sp->rx_cur-1)% NUM_RX_DESC].command = 0;
+               sp->rx_cur = (sp->rx_cur + 1) % NUM_RX_DESC;
+               rx_status = sp->rxfd[sp->rx_cur].status; 
+               /* Acknowledge all interrupts */
+               outw(0xff00,sp->ioaddr + SCBStatus);
+
+       }
+
+       /*
+        * The chip may have suspended reception for various reasons.
+        * Check for that, and re-prime it should this be the case.
+        */
+       switch ((inw(sp->ioaddr + SCBStatus) >> 2) & 0xf) {
+               case 0:  /* Idle */
+                       break;
+               case 1:  /* Suspended */
+               case 2:  /* No resources (RxFDs) */
+               case 9:  /* Suspended with no more RBDs */
+               case 10: /* No resources due to no RBDs */
+               case 12: /* Ready with no RBDs */
+                       speedo_rx_soft_reset(netdev);
+                       break;
+               default:
+                       /* reserved values */
+                       break;
+       }
+
+}                              
+
+
+/**
+ * Remove PCI device
+ *
+ * @v pci       PCI device
+ */
+static void speedo_remove (struct pci_device *pci) {
+        struct net_device *netdev = pci_get_drvdata (pci);
+
+        unregister_netdev (netdev);
+        speedo_disable (netdev);
+        netdev_put (netdev);
+}
+
+
+static struct pci_device_id speedo_nics[] = {
 PCI_ROM(0x8086, 0x1029, "id1029",        "Intel EtherExpressPro100 ID1029"),
 PCI_ROM(0x8086, 0x1030, "id1030",        "Intel EtherExpressPro100 ID1030"),
 PCI_ROM(0x8086, 0x1031, "82801cam",      "Intel 82801CAM (ICH3) Chipset Ethernet Controller"),
@@ -824,12 +776,9 @@ PCI_ROM(0x8086, 0x5200, "eepro100-5200", "Intel EtherExpress PRO/100 Intelligent
 PCI_ROM(0x8086, 0x5201, "eepro100-5201", "Intel EtherExpress PRO/100 Intelligent Server"),
 };
 
-/* Cards with device ids 0x1030 to 0x103F, 0x2449, 0x2459 or 0x245D might need
- * a workaround for hardware bug on 10 mbit half duplex (see linux driver eepro100.c)
- * 2003/03/17 gbaum */
-
-
-PCI_DRIVER ( eepro100_driver, eepro100_nics, PCI_NO_CLASS );
-
-DRIVER ( "EEPRO100", nic_driver, pci_driver, eepro100_driver,
-        eepro100_probe, eepro100_disable );
+struct pci_driver epro100_driver __pci_driver = {
+       .ids = speedo_nics,
+       .id_count = (sizeof (speedo_nics) / sizeof (speedo_nics[0])),
+       .probe = speedo_probe,
+       .remove = speedo_remove,
+};