patch-1.3.71 linux/drivers/net/eexpress.c

Next file: linux/drivers/net/eth82586.h
Previous file: linux/drivers/net/arcnet.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.70/linux/drivers/net/eexpress.c linux/drivers/net/eexpress.c
@@ -1,39 +1,105 @@
-/* eexpress.c: Intel EtherExpress device driver for Linux. */
 /*
-	Written 1993 by Donald Becker.
-	Copyright 1993 United States Government as represented by the Director,
-	National Security Agency.  This software may only be used and distributed
-	according to the terms of the GNU Public License as modified by SRC,
-	incorporated herein by reference.
-
-	The author may be reached as becker@super.org or
-	C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715
-
-	Things remaining to do:
-	Check that the 586 and ASIC are reset/unreset at the right times.
-	Check tx and rx buffer setup.
-	The current Tx is single-buffer-only.
-	Move the theory of operation and memory map documentation.
-	Rework the board error reset
-	The statistics need to be updated correctly.
-
-        Modularized by Pauline Middelink <middelin@polyware.iaf.nl>
-        Changed to support io= irq= by Alan Cox <Alan.Cox@linux.org>
-*/
-
-static const char *version =
-	"eexpress.c:v0.07 1/19/94 Donald Becker (becker@super.org)\n";
-
-/*
-  Sources:
-	This driver wouldn't have been written with the availability of the
-	Crynwr driver source code.	It provided a known-working implementation
-	that filled in the gaping holes of the Intel documentation.  Three cheers
-	for Russ Nelson.
-
-	Intel Microcommunications Databook, Vol. 1, 1990. It provides just enough
-	info that the casual reader might think that it documents the i82586.
-*/
+ * eexpress2.c: Intel EtherExpress device driver for Linux
+ *
+ * Original version written 1993 by Donald Becker
+ * Modularized by Pauline Middelink <middelin@polyware.iaf.nl>
+ * Changed to support io= irq= by Alan Cox <Alan.Cox@linux.org>
+ * Reworked 1995 by John Sullivan <js10039@cam.ac.uk>
+ * 
+ *  31jan96 Philip Blundell <pjb27@cam.ac.uk>
+ *     - Tidy up
+ *     - Some debugging.  Now works with 1.3 kernels.
+ *
+ *     Still to do:
+ *     - rationalise debugging
+ *     - fix detect/autoprobe and module routines
+ *     - test under high load, try to chase CU lockups
+ *     - look at RAM size check
+ *
+ * ToDo:
+ *   Move private globals into net_local structure
+ *   Multicast/Promiscuous mode handling
+ *   Put back debug reporting?
+ *   More documentation
+ *   Some worry about whether statistics are reported accurately
+ *
+ */
+
+/*
+ * The original EtherExpress driver was just about useable, but
+ * suffered from a long startup delay, a hard limit of 16k memory
+ * usage on the card (EtherExpress 16s have either 32k or 64k),
+ * and random locks under load. The last was particularly annoying
+ * and made running eXceed/W preferable to Linux/XFree. After hacking
+ * through the driver for a couple of days, I had fixed most of the
+ * card handling errors, at the expense of turning the code into
+ * a complete jungle, but still hadn't tracked down the lock-ups.
+ * I had hoped these would be an IP bug, but failed to reproduce them
+ * under other drivers, so decided to start from scratch and rewrite
+ * the driver cleanly. And here it is.
+ *
+ * It's still not quite there, but self-corrects a lot more problems.
+ * the 'CU wedged, resetting...' message shouldn't happen at all, but
+ * at least we recover. It still locks occasionally, any ideas welcome.
+ *
+ * The original startup delay experienced by some people was due to the
+ * first ARP request for the address of the default router getting lost.
+ * (mostly the reply we were getting back was arriving before our
+ * hardware address was set up, or before the configuration sequence
+ * had told the card NOT to strip of the frame header). If you a long
+ * startup delay, you may have lost this ARP request/reply, although
+ * the original cause has been fixed. However, it is more likely that
+ * you've just locked under this version.
+ *
+ * The main changes are in the 586 initialization procedure (which was
+ * just broken before - the EExp is a strange beasty and needs careful
+ * handling) the receive buffer handling (we now use a non-terminating
+ * circular list of buffers, which stops the card giving us out-of-
+ * resources errors), and the transmit code. The driver is also more
+ * structured, and I have tried to keep the kernel interface separate
+ * from the hardware interface (although some routines naturally want
+ * to do both).
+ *
+ * John Sullivan
+ *
+ * 18/5/95:
+ *
+ * The lock-ups seem to happen when you access card memory after a 586
+ * reset. This happens only 1 in 12 resets, on a random basis, and
+ * completely locks the machine. As far as I can see there is no
+ * workaround possible - the only thing to be done is make sure we
+ * never reset the card *after* booting the kernel - once at probe time
+ * must be sufficient, and we'll just have to put up with that failing
+ * occasionally (or buy a new NIC). By the way, this looks like a 
+ * definate card bug, since Intel's own driver for DOS does exactly the
+ * same.
+ */
+
+/*
+ * Sources:
+ *
+ * The original eexpress.c by Donald Becker
+ *   Sources: the Crynwr EtherExpress driver source.
+ *            the Intel Microcommunications Databook Vol.1 1990
+ *
+ * wavelan.c and i82586.h
+ *   This was invaluable for the complete '586 configuration details
+ *   and command format.
+ *
+ * The Crynwr sources (again)
+ *   Not as useful as the Wavelan driver, but then I had eexpress.c to
+ *   go off.
+ *
+ * The Intel EtherExpress 16 ethernet card
+ *   Provided the only reason I want to see a working etherexpress driver.
+ *   A lot of fixes came from just observing how the card (mis)behaves when
+ *   you prod it.
+ *
+ */
+
+static char version[] = 
+"eexpress.c: v0.07 1/19/94 Donald Becker <becker@super.org>\n"
+"            v0.10 4th May 1995 John Sullivan <js10039@cam.ac.uk>\n";
 
 #include <linux/module.h>
 
@@ -57,978 +123,1108 @@
 #include <linux/skbuff.h>
 #include <linux/malloc.h>
 
-/* use 0 for production, 1 for verification, 2..7 for debug */
+/*
+ * Not actually used yet - may be implemented when the driver has
+ * been debugged!
+ *
+ * Debug Level		Driver Status
+ *	0		Final release
+ *	1		Beta test
+ *	2
+ *	3
+ * 	4		Report timeouts & 586 errors (normal debug level)
+ *	5		Report all major events
+ *	6		Dump sent/received packet contents
+ *	7		Report function entry/exit
+ */
+
+#undef NET_DEBUG
+
 #ifndef NET_DEBUG
-#define NET_DEBUG 2
+#define NET_DEBUG 4
 #endif
 static unsigned int net_debug = NET_DEBUG;
 
-/*
-  			Details of the i82586.
-
-   You'll really need the databook to understand the details of this part,
-   but the outline is that the i82586 has two separate processing units.
-
-   The Rx unit uses a list of frame descriptors and a list of data buffer
-   descriptors.  We use full-sized (1518 byte) data buffers, so there is
-   a one-to-one pairing of frame descriptors to buffer descriptors.
+#undef F_DEB
 
-   The Tx ("command") unit executes a list of commands that look like:
-		Status word		Written by the 82586 when the command is done.
-		Command word	Command in lower 3 bits, post-command action in upper 3
-		Link word		The address of the next command.
-		Parameters		(as needed).
+#include "eth82586.h"
 
-	Some definitions related to the Command Word are:
+/*
+ * Private data declarations
  */
-#define CMD_EOL		0x8000			/* The last command of the list, stop. */
-#define CMD_SUSP	0x4000			/* Suspend after doing cmd. */
-#define CMD_INTR	0x2000			/* Interrupt after doing cmd. */
-
-enum commands {
-	CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMulticastList = 3,
-	CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7};
 
-/* Information that need to be kept for each board. */
-struct net_local {
+struct net_local 
+{
 	struct enet_statistics stats;
-	int last_restart;
-	short rx_head;
-	short rx_tail;
-	short tx_head;
-	short tx_cmd_link;
-	short tx_reap;
+	unsigned long init_time;        /* jiffies when eexp_hw_init586 called */
+	unsigned short rx_first;        /* first rx buf, same as RX_BUF_START */
+	unsigned short rx_last;         /* last rx buf */
+	unsigned short tx_head;         /* next free tx buf */
+	unsigned short tx_reap;         /* first in-use tx buf */
+	unsigned short tx_tail;         /* previous tx buf to tx_head */
+	unsigned short tx_link;         /* last known-executing tx buf */
+	unsigned short last_tx_restart; /* set to tx_link when we restart the CU */
 };
 
-/*
-  		Details of the EtherExpress Implementation
-  The EtherExpress takes an unusual approach to host access to packet buffer
-  memory.  The host can use either the Dataport, with independent
-  autoincrementing read and write pointers, or it can I/O map 32 bytes of the
-  memory using the "Shadow Memory Pointer" (SMB) as follows:
-			ioaddr						Normal EtherExpress registers
-			ioaddr+0x4000...0x400f		Buffer Memory at SMB...SMB+15
-			ioaddr+0x8000...0x800f		Buffer Memory at SMB+16...SMB+31
-			ioaddr+0xC000...0xC007		"" SMB+16...SMB+23 (hardware flaw?)
-			ioaddr+0xC008...0xC00f		Buffer Memory at 0x0008...0x000f
-  The last I/O map set is useful if you put the i82586 System Command Block
-  (the command mailbox) exactly at 0x0008.  (There seems to be some
-  undocumented init structure at 0x0000-7, so I had to use the Crywnr memory
-  setup verbatim for those four words anyway.)
-
-  A problem with using either one of these mechanisms is that you must run
-  single-threaded, or the interrupt handler must restore a changed value of
-  the read, write, or SMB pointers.
-
-  Unlike the Crynwr driver, my driver mostly ignores the I/O mapped "feature"
-  and relies heavily on the dataport for buffer memory access.  To minimize
-  switching, the read_pointer is dedicated to the Rx interrupt handler, and
-  the write_pointer is used by the send_packet() routine (it's carefully saved
-  and restored when it's needed by the interrupt handler).
-  */
-
-/* Offsets from the base I/O address. */
-#define DATAPORT	0	/* Data Transfer Register. */
-#define WRITE_PTR	2	/* Write Address Pointer. */
-#define READ_PTR	4	/* Read Address Pointer. */
-#define SIGNAL_CA	6	/* Frob the 82586 Channel Attention line. */
-#define SET_IRQ		7	/* IRQ Select. */
-#define SHADOW_PTR	8	/* Shadow Memory Bank Pointer. */
-#define MEM_Ctrl	11
-#define MEM_Page_Ctrl	12
-#define Config		13
-#define EEPROM_Ctrl		14
-#define ID_PORT		15
-
-#define EEXPRESS_IO_EXTENT 16
-
-/*	EEPROM_Ctrl bits. */
-
-#define EE_SHIFT_CLK	0x01	/* EEPROM shift clock. */
-#define EE_CS			0x02	/* EEPROM chip select. */
-#define EE_DATA_WRITE	0x04	/* EEPROM chip data in. */
-#define EE_DATA_READ	0x08	/* EEPROM chip data out. */
-#define EE_CTRL_BITS	(EE_SHIFT_CLK | EE_CS | EE_DATA_WRITE | EE_DATA_READ)
-#define ASIC_RESET		0x40
-#define _586_RESET		0x80
-
-/* Offsets to elements of the System Control Block structure. */
-#define SCB_STATUS	0xc008
-#define SCB_CMD		0xc00A
-#define	 CUC_START	 0x0100
-#define	 CUC_RESUME	 0x0200
-#define	 CUC_SUSPEND 0x0300
-#define	 RX_START	 0x0010
-#define	 RX_RESUME	 0x0020
-#define	 RX_SUSPEND	 0x0030
-#define SCB_CBL		0xc00C	/* Command BLock offset. */
-#define SCB_RFA		0xc00E	/* Rx Frame Area offset. */
-
-/*
-  What follows in 'init_words[]' is the "program" that is downloaded to the
-  82586 memory.	 It's mostly tables and command blocks, and starts at the
-  reset address 0xfffff6.
-
-  Even with the additional "don't care" values, doing it this way takes less
-  program space than initializing the individual tables, and I feel it's much
-  cleaner.
-
-  The databook is particularly useless for the first two structures; they are
-  completely undocumented.  I had to use the Crynwr driver as an example.
-
-   The memory setup is as follows:
-   */
-
-#define CONFIG_CMD	0x0018
-#define SET_SA_CMD	0x0024
-#define SA_OFFSET	0x002A
-#define IDLELOOP	0x30
-#define TDR_CMD		0x38
-#define TDR_TIME	0x3C
-#define DUMP_CMD	0x40
-#define DIAG_CMD	0x48
-#define SET_MC_CMD	0x4E
-#define DUMP_DATA	0x56	/* A 170 byte buffer for dump and Set-MC into. */
-
-#define TX_BUF_START	0x0100
-#define NUM_TX_BUFS 	4
-#define TX_BUF_SIZE		0x0680	/* packet+header+TBD+extra (1518+14+20+16) */
-#define TX_BUF_END		0x2000
-
-#define RX_BUF_START	0x2000
-#define RX_BUF_SIZE 	(0x640)	/* packet+header+RBD+extra */
-#define RX_BUF_END		0x4000
-
-/*
-  That's it: only 86 bytes to set up the beast, including every extra
-  command available.  The 170 byte buffer at DUMP_DATA is shared between the
-  Dump command (called only by the diagnostic program) and the SetMulticastList
-  command.
-
-  To complete the memory setup you only have to write the station address at
-  SA_OFFSET and create the Tx & Rx buffer lists.
-
-  The Tx command chain and buffer list is setup as follows:
-  A Tx command table, with the data buffer pointing to...
-  A Tx data buffer descriptor.  The packet is in a single buffer, rather than
-     chaining together several smaller buffers.
-  A NoOp command, which initially points to itself,
-  And the packet data.
-
-  A transmit is done by filling in the Tx command table and data buffer,
-  re-writing the NoOp command, and finally changing the offset of the last
-  command to point to the current Tx command.  When the Tx command is finished,
-  it jumps to the NoOp, when it loops until the next Tx command changes the
-  "link offset" in the NoOp.  This way the 82586 never has to go through the
-  slow restart sequence.
-
-  The Rx buffer list is set up in the obvious ring structure.  We have enough
-  memory (and low enough interrupt latency) that we can avoid the complicated
-  Rx buffer linked lists by alway associating a full-size Rx data buffer with
-  each Rx data frame.
-
-  I current use four transmit buffers starting at TX_BUF_START (0x0100), and
-  use the rest of memory, from RX_BUF_START to RX_BUF_END, for Rx buffers.
-
-  */
-
-static short init_words[] = {
-	0x0000,					/* Set bus size to 16 bits. */
-	0x0000,0x0000,			/* Set control mailbox (SCB) addr. */
-	0,0,					/* pad to 0x000000. */
-	0x0001,					/* Status word that's cleared when init is done. */
-	0x0008,0,0,				/* SCB offset, (skip, skip) */
-
-	0,0xf000|RX_START|CUC_START,	/* SCB status and cmd. */
-	CONFIG_CMD,				/* Command list pointer, points to Configure. */
-	RX_BUF_START,				/* Rx block list. */
-	0,0,0,0,				/* Error count: CRC, align, buffer, overrun. */
-
-	/* 0x0018: Configure command.  Change to put MAC data with packet. */
-	0, CmdConfigure,		/* Status, command.		*/
-	SET_SA_CMD,				/* Next command is Set Station Addr. */
-	0x0804,					/* "4" bytes of config data, 8 byte FIFO. */
-	0x2e40,					/* Magic values, including MAC data location. */
-	0,						/* Unused pad word. */
-
-	/* 0x0024: Setup station address command. */
-	0, CmdSASetup,
-	SET_MC_CMD,				/* Next command. */
-	0xaa00,0xb000,0x0bad,	/* Station address (to be filled in) */
-
-	/* 0x0030: NOP, looping back to itself.	 Point to first Tx buffer to Tx. */
-	0, CmdNOp, IDLELOOP, 0 /* pad */,
-
-	/* 0x0038: A unused Time-Domain Reflectometer command. */
-	0, CmdTDR, IDLELOOP, 0,
-
-	/* 0x0040: An unused Dump State command. */
-	0, CmdDump, IDLELOOP, DUMP_DATA,
-
-	/* 0x0048: An unused Diagnose command. */
-	0, CmdDiagnose, IDLELOOP,
-
-	/* 0x004E: An empty set-multicast-list command. */
-#ifdef initial_text_tx
-	0, CmdMulticastList, DUMP_DATA, 0,
-#else
-	0, CmdMulticastList, IDLELOOP, 0,
-#endif
+unsigned short start_code[] = {
+	0x0000,                 /* SCP: set bus to 16 bits */
+	0x0000,0x0000,          /* junk */
+	0x0000,0x0000,          /* address of ISCP (lo,hi) */
+
+	0x0001,                 /* ISCP: busy - cleared after reset */
+	0x0008,0x0000,0x0000,   /* offsett,address (lo,hi) of SCB */
+
+	0x0000,0x0000,          /* SCB: status, commands */
+	0x0000,0x0000,          /* links to first command block, first receive descriptor */
+	0x0000,0x0000,          /* CRC error, alignment error counts */
+	0x0000,0x0000,          /* out of resources, overrun error counts */
+
+	0x0000,0x0000,          /* pad */
+	0x0000,0x0000,
+
+	0x0000,Cmd_Config,      /* startup configure sequence, at 0x0020 */
+	0x0032,                 /* link to next command */
+	0x080c,                 /* 12 bytes follow : fifo threshold=8 */
+	0x2e40,                 /* don't rx bad frames : SRDY/ARDY => ext. sync. : preamble len=8
+	                         * take addresses from data buffers : 6 bytes/address */
+	0x6000,                 /* default backoff method & priority : interframe spacing = 0x60 */
+	0xf200,                 /* slot time=0x200 : max collision retry = 0xf */
+	0x0000,                 /* no HDLC : normal CRC : enable broadcast : disable promiscuous/multicast modes */
+	0x003c,                 /* minimum frame length = 60 octets) */
+
+	0x0000,Cmd_INT|Cmd_SetAddr,
+	0x003e,                 /* link to next command */
+	0x0000,0x0000,0x0000,   /* hardware address placed here, 0x0038 */
+	0x0000,Cmd_END|Cmd_Nop, /* end of configure sequence */
+	0x003e,
 
-	/* 0x0056: A continuous transmit command, only here for testing. */
-	0, CmdTx, DUMP_DATA, DUMP_DATA+8, 0x83ff, -1, DUMP_DATA, 0,
-};
+	0x0000
 
-/* Index to functions, as function prototypes. */
+};
 
-extern int express_probe(struct device *dev);	/* Called from Space.c */
+#define CONF_LINK 0x0020
+#define CONF_HW_ADDR 0x0038
 
-static int	eexp_probe1(struct device *dev, short ioaddr);
-static int	eexp_open(struct device *dev);
-static int	eexp_send_packet(struct sk_buff *skb, struct device *dev);
-static void	eexp_interrupt(int irq, void *dev_id, struct pt_regs *regs);
-static void eexp_rx(struct device *dev);
-static int	eexp_close(struct device *dev);
-static struct enet_statistics *eexp_get_stats(struct device *dev);
-static void set_multicast_list(struct device *dev);
-
-static int read_eeprom(int ioaddr, int location);
-static void hardware_send_packet(struct device *dev, void *buf, short length);
-static void init_82586_mem(struct device *dev);
-static void init_rx_bufs(struct device *dev);
-
-
-/* Check for a network adaptor of this type, and return '0' iff one exists.
-   If dev->base_addr == 0, probe all likely locations.
-   If dev->base_addr == 1, always return failure.
-   If dev->base_addr == 2, (detachable devices only) allocate space for the
-   device and return success.
-   */
-int
-express_probe(struct device *dev)
-{
-	/* Don't probe all settable addresses, 0x[23][0-7]0, just common ones. */
-	int *port, ports[] = {0x300, 0x270, 0x320, 0x340, 0};
-	int base_addr = dev->base_addr;
-
-	if (base_addr > 0x1ff)	/* Check a single specified location. */
-		return eexp_probe1(dev, base_addr);
-	else if (base_addr > 0)
-		return ENXIO;		/* Don't probe at all. */
+/* maps irq number to EtherExpress magic value */
+static char irqrmap[] = { 0,0,1,2,3,4,0,0,0,1,5,6,0,0,0,0 };
 
-	for (port = &ports[0]; *port; port++) {
-		short id_addr = *port + ID_PORT;
-		unsigned short sum = 0;
-		int i;
-#ifdef notdef
-		for (i = 16; i > 0; i--)
-			sum += inb(id_addr);
-		printk("EtherExpress ID checksum is %04x.\n", sum);
-#else
-		for (i = 4; i > 0; i--) {
-			short id_val = inb(id_addr);
-			sum |= (id_val >> 4) << ((id_val & 3) << 2);
-		}
-#endif
-		if (sum == 0xbaba
-			&& eexp_probe1(dev, *port) == 0)
-			return 0;
-	}
-
-	return ENODEV;
-}
-
-int eexp_probe1(struct device *dev, short ioaddr)
-{
-	unsigned short station_addr[3];
-	int i;
+static unsigned char started=0;
 
-	printk("%s: EtherExpress at %#x,", dev->name, ioaddr);
+/*
+ * Prototypes for Linux interface
+ */
 
-	/* The station address is stored !backwards! in the EEPROM, reverse
-	   after reading.  (Hmmm, a little brain-damage there at Intel, eh?) */
-	station_addr[0] = read_eeprom(ioaddr, 2);
-	station_addr[1] = read_eeprom(ioaddr, 3);
-	station_addr[2] = read_eeprom(ioaddr, 4);
-
-	/* Check the first three octets of the S.A. for the manufacturer's code. */
-	if (station_addr[2] != 0x00aa || (station_addr[1] & 0xff00) != 0x0000) {
-		printk(" rejected (invalid address %04x%04x%04x).\n",
-			   station_addr[2], station_addr[1], station_addr[0]);
-		return ENODEV;
-	}
+extern int                  express_probe(struct device *dev);
+static int                     eexp_open (struct device *dev);
+static int                     eexp_close(struct device *dev);
+static struct enet_statistics *eexp_stats(struct device *dev);
+static int                     eexp_xmit (struct sk_buff *buf, struct device *dev);
 
-	/* We've committed to using the board, and can start filling in *dev. */
-	request_region(ioaddr, EEXPRESS_IO_EXTENT, "eexpress");
-	dev->base_addr = ioaddr;
+static void                    eexp_irq  (int irq, void *dev_addr, struct pt_regs *regs);
 
-	for (i = 0; i < 6; i++) {
-		dev->dev_addr[i] = ((unsigned char*)station_addr)[5-i];
-		printk(" %02x", dev->dev_addr[i]);
-	}
-
-	/* There is no reason for the driver to care, but I print out the
-	   interface to minimize bogus bug reports. */
-	{
-		char irqmap[] = {0, 9, 3, 4, 5, 10, 11, 0};
-		const char *ifmap[] = {"AUI", "BNC", "10baseT"};
-		enum iftype {AUI=0, BNC=1, TP=2};
-		unsigned short setupval = read_eeprom(ioaddr, 0);
+/*
+ * Prototypes for hardware access functions
+ */
 
-		dev->irq = irqmap[setupval >> 13];
-		dev->if_port = (setupval & 0x1000) == 0 ? AUI :
-			read_eeprom(ioaddr, 5) & 0x1 ? TP : BNC;
-		printk(", IRQ %d, Interface %s.\n", dev->irq, ifmap[dev->if_port]);
-		/* Release the IRQ line so that it can be shared if we don't use the
-		   ethercard. */
-		outb(0x00, ioaddr + SET_IRQ);
-	}
+static void           eexp_hw_rx        (struct device *dev);
+static void           eexp_hw_tx        (struct device *dev, unsigned short *buf, unsigned short len);
+static int            eexp_hw_probe     (struct device *dev,unsigned short ioaddr);
+static unsigned short eexp_hw_readeeprom(unsigned short ioaddr, unsigned char location);
+
+static unsigned short eexp_hw_lasttxstat(struct device *dev);
+static void           eexp_hw_txrestart (struct device *dev);
+
+static void           eexp_hw_txinit    (struct device *dev);
+static void           eexp_hw_rxinit    (struct device *dev);
+static void           eexp_hw_rxmap     (struct device *dev,unsigned short rx_buf);
 
-	/* It's now OK to leave the board in reset, pending the open(). */
-	outb(ASIC_RESET, ioaddr + EEPROM_Ctrl);
+static void           eexp_hw_init586   (struct device *dev);
+static void           eexp_hw_ASICrst   (struct device *dev);
 
-	if ((dev->mem_start & 0xf) > 0)
-		net_debug = dev->mem_start & 7;
+/*
+ * Linux interface
+ */
 
-	if (net_debug)
-		printk(version);
+/*
+ * checks for presence of EtherExpress card
+ */
 
-	/* Initialize the device structure. */
-	dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
-	if (dev->priv == NULL)
-		return -ENOMEM;
-	memset(dev->priv, 0, sizeof(struct net_local));
+int express_probe(struct device *dev)
+{
+	unsigned short *port,ports[] = { 0x0300,0x0270,0x0320,0x0340,0 };
+	unsigned short ioaddr = dev->base_addr;
 
-	dev->open		= eexp_open;
-	dev->stop		= eexp_close;
-	dev->hard_start_xmit = eexp_send_packet;
-	dev->get_stats	= eexp_get_stats;
-	dev->set_multicast_list = &set_multicast_list;
+	if (ioaddr&0xfe00)
+		return eexp_hw_probe(dev,ioaddr);
+	else if (ioaddr)
+		return ENXIO;
 
-	/* Fill in the fields of the device structure with ethernet-generic values. */
-	
-	ether_setup(dev);
-	
-	dev->flags&=~IFF_MULTICAST;
-		
-	return 0;
+	for ( port=&ports[0] ; *port ; port++ ) 
+	{
+		unsigned short sum = 0;
+		int i;
+		for ( i=0 ; i<4 ; i++ ) 
+		{
+			unsigned short t;
+			t = inb(*port + ID_PORT);
+			sum |= (t>>4) << ((t & 0x03)<<2);
+		}
+		if (sum==0xbaba && !eexp_hw_probe(dev,*port)) 
+			return 0;
+	}
+	return ENODEV;
 }
 
-
-/* Reverse IRQ map: the value to put in the SET_IRQ reg. for IRQ<index>. */
-static char irqrmap[]={0,0,1,2,3,4,0,0,0,1,5,6,0,0,0,0};
+/*
+ * open and initialize the adapter, ready for use
+ */
 
-static int
-eexp_open(struct device *dev)
+static int eexp_open(struct device *dev)
 {
-	int ioaddr = dev->base_addr;
+	int irq = dev->irq;
+	unsigned short ioaddr = dev->base_addr;
+
+#if NET_DEBUG > 6
+	printk("%s: eexp_open()\n", dev->name);
+#endif
 
-	if (dev->irq == 0  ||  irqrmap[dev->irq] == 0)
+	if (!irq || !irqrmap[irq]) 
 		return -ENXIO;
 
-	if (irq2dev_map[dev->irq] != 0
-		/* This is always true, but avoid the false IRQ. */
-		|| (irq2dev_map[dev->irq] = dev) == 0
-		|| request_irq(dev->irq, &eexp_interrupt, 0, "EExpress", NULL)) {
+	if (irq2dev_map[irq] ||
+	      /* more consistent, surely? */
+	   ((irq2dev_map[irq]=dev),0) ||
+	     request_irq(irq,&eexp_irq,0,"EExpress",NULL)) 
 		return -EAGAIN;
-	}
-
-	/* Initialize the 82586 memory and start it. */
-	init_82586_mem(dev);
-
-	/* Enable the interrupt line. */
-	outb(irqrmap[dev->irq] | 0x08, ioaddr + SET_IRQ);
 
+	request_region(ioaddr,16,"EExpress");
 	dev->tbusy = 0;
 	dev->interrupt = 0;
+	eexp_hw_init586(dev);
 	dev->start = 1;
-	MOD_INC_USE_COUNT;
 	return 0;
 }
 
-static int
-eexp_send_packet(struct sk_buff *skb, struct device *dev)
+/*
+ * close and disable the interface, leaving
+ * the 586 in reset
+ */
+static int eexp_close(struct device *dev)
+{
+	unsigned short ioaddr = dev->base_addr;
+	int irq = dev->irq;
+
+	dev->tbusy = 1; 
+	dev->start = 0;
+  
+	outb(SIRQ_dis|irqrmap[irq],ioaddr+SET_IRQ);
+	started = 0;
+	outw(SCB_CUsuspend|SCB_RUsuspend,ioaddr+SCB_CMD);
+	outb(0,ioaddr+SIGNAL_CA);
+	free_irq(irq,NULL);
+	irq2dev_map[irq] = NULL;
+	outb(i586_RST,ioaddr+EEPROM_Ctrl);
+	release_region(ioaddr,16);
+	return 0;
+}
+
+/*
+ * Return interface stats
+ */
+
+static struct enet_statistics *eexp_stats(struct device *dev)
+{
+	struct net_local *lp = (struct net_local *)dev->priv;
+
+	/* 
+	 * Hmmm, this looks a little too easy... The card maintains
+	 * some stats in the SCB, and I'm not convinced we're
+	 * incrementing the most sensible statistics when the card
+	 * returns an error (esp. slow DMA, out-of-resources)
+	 */
+	return &lp->stats;
+}
+
+/*
+ * Called to transmit a packet, or to allow us to right ourselves
+ * if the kernel thinks we've died.
+ */
+
+static int eexp_xmit(struct sk_buff *buf, struct device *dev)
 {
 	struct net_local *lp = (struct net_local *)dev->priv;
-	int ioaddr = dev->base_addr;
+	unsigned short ioaddr = dev->base_addr;
+
+#if NET_DEBUG > 6
+	printk("%s: eexp_xmit()\n", dev->name);
+#endif
 
-	if (dev->tbusy) {
-		/* If we get here, some higher level has decided we are broken.
-		   There should really be a "kick me" function call instead. */
-		int tickssofar = jiffies - dev->trans_start;
-		if (tickssofar < 5)
-			return 1;
-		if (net_debug > 1)
-			printk("%s: transmit timed out, %s?  ", dev->name,
-				   inw(ioaddr+SCB_STATUS) & 0x8000 ? "IRQ conflict" :
-				   "network cable problem");
-		lp->stats.tx_errors++;
-		/* Try to restart the adaptor. */
-		if (lp->last_restart == lp->stats.tx_packets) {
-			if (net_debug > 1) printk("Resetting board.\n");
-			/* Completely reset the adaptor. */
-			init_82586_mem(dev);
-		} else {
-			/* Issue the channel attention signal and hope it "gets better". */
-			if (net_debug > 1) printk("Kicking board.\n");
-			outw(0xf000|CUC_START|RX_START, ioaddr + SCB_CMD);
-			outb(0, ioaddr + SIGNAL_CA);
-			lp->last_restart = lp->stats.tx_packets;
+	outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+	if (dev->tbusy) 
+	{
+		/* This will happen, but hopefully not as often as when
+		 * tbusy==0. If it happens too much, we probably ought
+		 * to think about unwedging ourselves...
+		 */
+		if (test_bit(0,(void *)&started)) 
+		{
+			if ((jiffies - dev->trans_start)>5) 
+			{
+				if (lp->tx_link==lp->last_tx_restart) 
+				{
+					unsigned short boguscount=200,rsst;
+					printk("%s: Retransmit timed out, status %04x, resetting...\n",
+						dev->name,inw(ioaddr+SCB_STATUS));
+					eexp_hw_txinit(dev);
+					lp->last_tx_restart = 0;
+					outw(lp->tx_link,ioaddr+SCB_CBL);
+					outw(0,ioaddr+SCB_STATUS);
+					outw(SCB_CUstart,ioaddr+SCB_CMD);
+					outb(0,ioaddr+SIGNAL_CA);
+					while (!SCB_complete(rsst=inw(ioaddr+SCB_STATUS))) 
+					{
+						if (!--boguscount) 
+						{
+							boguscount=200;
+							printk("%s: Reset timed out status %04x, retrying...\n",
+								dev->name,rsst);
+							outw(lp->tx_link,ioaddr+SCB_CBL);
+							outw(0,ioaddr+SCB_STATUS);
+							outw(SCB_CUstart,ioaddr+SCB_CMD);
+							outb(0,ioaddr+SIGNAL_CA);
+						}
+					}
+					dev->tbusy = 0;
+					mark_bh(NET_BH);
+				}
+				else
+				{
+					unsigned short status = inw(ioaddr+SCB_STATUS);
+					if (SCB_CUdead(status)) 
+					{
+						unsigned short txstatus = eexp_hw_lasttxstat(dev);
+						printk("%s: Transmit timed out, CU not active status %04x %04x, restarting...\n",
+							dev->name, status, txstatus);
+						eexp_hw_txrestart(dev);
+					}
+					else
+					{
+						unsigned short txstatus = eexp_hw_lasttxstat(dev);
+						if (dev->tbusy && !txstatus) 
+						{
+							printk("%s: CU wedged, status %04x %04x, resetting...\n",
+								dev->name,status,txstatus);
+							eexp_hw_init586(dev); 
+							dev->tbusy = 0;
+							mark_bh(NET_BH);
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+			if ((jiffies-lp->init_time)>10)
+			{
+				unsigned short status = inw(ioaddr+SCB_STATUS);
+				printk("%s: i82586 startup timed out, status %04x, resetting...\n",
+					dev->name, status);
+				eexp_hw_init586(dev);
+				dev->tbusy = 0;
+				mark_bh(NET_BH);
+			}
 		}
-		dev->tbusy=0;
-		dev->trans_start = jiffies;
 	}
 
-	/* If some higher layer thinks we've missed an tx-done interrupt
-	   we are passed NULL. Caution: dev_tint() handles the cli()/sti()
-	   itself. */
-	if (skb == NULL) {
+	if (buf==NULL) 
+	{
+		unsigned short status = inw(ioaddr+SCB_STATUS);
+		unsigned short txstatus = eexp_hw_lasttxstat(dev);
+		if (SCB_CUdead(status)) 
+		{
+			printk("%s: CU has died! status %04x %04x, attempting to restart...\n",
+				dev->name, status, txstatus);
+			lp->stats.tx_errors++;
+			eexp_hw_txrestart(dev);
+		}
 		dev_tint(dev);
+		outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
 		return 0;
 	}
 
-	/* Block a timer-based transmit from overlapping. */
-	if (set_bit(0, (void*)&dev->tbusy) != 0)
-		printk("%s: Transmitter access conflict.\n", dev->name);
-	else {
-		short length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN;
-		unsigned char *buf = skb->data;
-
-		/* Disable the 82586's input to the interrupt line. */
-		outb(irqrmap[dev->irq], ioaddr + SET_IRQ);
-		hardware_send_packet(dev, buf, length);
-		dev->trans_start = jiffies;
-		/* Enable the 82586 interrupt input. */
-		outb(0x08 | irqrmap[dev->irq], ioaddr + SET_IRQ);
+	if (set_bit(0,(void *)&dev->tbusy)) 
+	{
+/*    printk("%s: Transmitter busy or access conflict\n",dev->name); */
+		lp->stats.tx_dropped++;
 	}
+	else
+	{
+		unsigned short length = (ETH_ZLEN < buf->len) ? buf->len : ETH_ZLEN;
+		unsigned short *data = (unsigned short *)buf->data;
 
-	dev_kfree_skb (skb, FREE_WRITE);
-
-	/* You might need to clean up and record Tx statistics here. */
-	lp->stats.tx_aborted_errors++;
-
+		outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+		eexp_hw_tx(dev,data,length);
+		outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
+	}
+	dev_kfree_skb(buf, FREE_WRITE);
+	outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
 	return 0;
 }
-
-/*	The typical workload of the driver:
-	Handle the network interface interrupts. */
-static void
-eexp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+
+/*
+ * Handle an EtherExpress interrupt
+ * If we've finished initializing, start the RU and CU up.
+ * If we've already started, reap tx buffers, handle any received packets,
+ * check to make sure we've not become wedged.
+ */
+
+static void eexp_irq(int irq, void *dev_info, struct pt_regs *regs)
 {
-	struct device *dev = (struct device *)(irq2dev_map[irq]);
+	struct device *dev = irq2dev_map[irq];
 	struct net_local *lp;
-	int ioaddr, status, boguscount = 0;
-	short ack_cmd;
+	unsigned short ioaddr,status,ack_cmd;
+	unsigned short old_rp,old_wp;
 
-	if (dev == NULL) {
-		printk ("net_interrupt(): irq %d for unknown device.\n", irq);
+	if (dev==NULL) 
+	{
+		printk("net_interrupt(): irq %d for unknown device caught by EExpress\n",irq);
 		return;
 	}
-	dev->interrupt = 1;
-	
-	ioaddr = dev->base_addr;
-	lp = (struct net_local *)dev->priv;
-	
-	status = inw(ioaddr + SCB_STATUS);
-	
-    if (net_debug > 4) {
-		printk("%s: EExp interrupt, status %4.4x.\n", dev->name, status);
-    }
-
-	/* Disable the 82586's input to the interrupt line. */
-	outb(irqrmap[dev->irq], ioaddr + SET_IRQ);
-
-	/* Reap the Tx packet buffers. */
-	while (lp->tx_reap != lp->tx_head) { 	/* if (status & 0x8000) */
-		unsigned short tx_status;
-		outw(lp->tx_reap, ioaddr + READ_PTR);
-		tx_status = inw(ioaddr);
-		if (tx_status == 0) {
-			if (net_debug > 5)  printk("Couldn't reap %#x.\n", lp->tx_reap);
-			break;
-		}
-		if (tx_status & 0x2000) {
-			lp->stats.tx_packets++;
-			lp->stats.collisions += tx_status & 0xf;
-			dev->tbusy = 0;
-			mark_bh(NET_BH);	/* Inform upper layers. */
-		} else {
-			lp->stats.tx_errors++;
-			if (tx_status & 0x0600)  lp->stats.tx_carrier_errors++;
-			if (tx_status & 0x0100)  lp->stats.tx_fifo_errors++;
-			if (!(tx_status & 0x0040))  lp->stats.tx_heartbeat_errors++;
-			if (tx_status & 0x0020)  lp->stats.tx_aborted_errors++;
-		}
-		if (net_debug > 5)
-			printk("Reaped %x, Tx status %04x.\n" , lp->tx_reap, tx_status);
-		lp->tx_reap += TX_BUF_SIZE;
-		if (lp->tx_reap > TX_BUF_END - TX_BUF_SIZE)
-			lp->tx_reap = TX_BUF_START;
-		if (++boguscount > 4)
-			break;
-	}
 
-	if (status & 0x4000) { /* Packet received. */
-		if (net_debug > 5)
-			printk("Received packet, rx_head %04x.\n", lp->rx_head);
-		eexp_rx(dev);
-	}
+#if NET_DEBUG > 6
+	printk("%s: interrupt\n", dev->name);
+#endif
 
-	/* Acknowledge the interrupt sources. */
-	ack_cmd = status & 0xf000;
+	dev->interrupt = 1; /* should this be reset on exit? */
+  
+	lp = (struct net_local *)dev->priv;
+	ioaddr = dev->base_addr;
+
+	outb(SIRQ_dis|irqrmap[irq],ioaddr+SET_IRQ);
+	old_rp = inw(ioaddr+READ_PTR);
+	old_wp = inw(ioaddr+WRITE_PTR);
+	status = inw(ioaddr+SCB_STATUS);
+	ack_cmd = SCB_ack(status);
 
-	if ((status & 0x0700) != 0x0200  &&  dev->start) {
-		short saved_write_ptr = inw(ioaddr + WRITE_PTR);
-		if (net_debug > 1)
-			printk("%s: Command unit stopped, status %04x, restarting.\n",
-				   dev->name, status);
-		/* If this ever occurs we must re-write the idle loop, reset
-		   the Tx list, and do a complete restart of the command unit. */
-		outw(IDLELOOP, ioaddr + WRITE_PTR);
-		outw(0, ioaddr);
-		outw(CmdNOp, ioaddr);
-		outw(IDLELOOP, ioaddr);
-		outw(IDLELOOP, SCB_CBL);
-		lp->tx_cmd_link = IDLELOOP + 4;
-		lp->tx_head = lp->tx_reap = TX_BUF_START;
-		/* Restore the saved write pointer. */
-		outw(saved_write_ptr, ioaddr + WRITE_PTR);
-		ack_cmd |= CUC_START;
+	if (started==0 && SCB_complete(status)) 
+	{
+		if (SCB_CUstat(status)==2) 
+			while (SCB_CUstat(inw(ioaddr+SCB_STATUS))==2);
+		started=1;
+		outw(lp->tx_link,ioaddr+SCB_CBL);
+		outw(RX_BUF_START,ioaddr+SCB_RFA);
+		ack_cmd |= SCB_CUstart | SCB_RUstart;
+	}
+	else if (started) 
+	{
+		unsigned short txstatus;
+		txstatus = eexp_hw_lasttxstat(dev);
+	}
+  
+	if (SCB_rxdframe(status)) 
+	{
+		eexp_hw_rx(dev);
 	}
 
-	if ((status & 0x0070) != 0x0040  &&  dev->start) {
-		short saved_write_ptr = inw(ioaddr + WRITE_PTR);
-		/* The Rx unit is not ready, it must be hung.  Restart the receiver by
-		   initializing the rx buffers, and issuing an Rx start command. */
+	if ((started&2)!=0 && SCB_RUstat(status)!=4) 
+	{
+		printk("%s: RU stopped status %04x, restarting...\n",
+			dev->name,status);
 		lp->stats.rx_errors++;
-		if (net_debug > 1) {
-			int cur_rxbuf = RX_BUF_START;
-			printk("%s: Rx unit stopped status %04x rx head %04x tail %04x.\n",
-				   dev->name, status, lp->rx_head, lp->rx_tail);
-			while (cur_rxbuf <= RX_BUF_END - RX_BUF_SIZE) {
-				int i;
-				printk("  Rx buf at %04x:", cur_rxbuf);
-				outw(cur_rxbuf, ioaddr + READ_PTR);
-				for (i = 0; i < 0x20; i += 2)
-					printk(" %04x", inw(ioaddr));
-				printk(".\n");
-				cur_rxbuf += RX_BUF_SIZE;
+		eexp_hw_rxinit(dev);
+		outw(RX_BUF_START,ioaddr+SCB_RFA);
+		ack_cmd |= SCB_RUstart;
+	} 
+	else if (started==1 && SCB_RUstat(status)==4) 
+		started|=2;
+
+	outw(ack_cmd,ioaddr+SCB_CMD);
+	outb(0,ioaddr+SIGNAL_CA);
+	outw(old_rp,ioaddr+READ_PTR);
+	outw(old_wp,ioaddr+WRITE_PTR);
+	outb(SIRQ_en|irqrmap[irq],ioaddr+SET_IRQ);
+	return;
+}
+
+/*
+ * Hardware access functions
+ */
+
+/*
+ * Check all the receive buffers, and hand any received packets
+ * to the upper levels. Basic sanity check on each frame
+ * descriptor
+ */
+ 
+static void eexp_hw_rx(struct device *dev)
+{
+	struct net_local *lp = (struct net_local *)dev->priv;
+	unsigned short ioaddr = dev->base_addr;
+	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+	unsigned short old_rp = inw(ioaddr+READ_PTR);
+	unsigned short rx_block = lp->rx_first;
+	unsigned short boguscount = NUM_RX_BUFS;
+
+#if NET_DEBUG > 6
+	printk("%s: eexp_hw_rx()\n", dev->name);
+#endif
+
+	while (outw(rx_block,ioaddr+READ_PTR),boguscount--) 
+	{
+		unsigned short status = inw(ioaddr);
+		unsigned short rfd_cmd = inw(ioaddr);
+		unsigned short rx_next = inw(ioaddr);
+		unsigned short pbuf = inw(ioaddr);
+		unsigned short pkt_len;
+
+		if (FD_Done(status)) 
+		{
+			outw(pbuf,ioaddr+READ_PTR);
+			pkt_len = inw(ioaddr);
+
+			if (rfd_cmd!=0x0000 || pbuf!=rx_block+0x16
+				|| (pkt_len & 0xc000)!=0xc000) 
+			{
+				printk("%s: Rx frame at %04x corrupted, status %04x, cmd %04x, "
+					"next %04x, pbuf %04x, len %04x\n",dev->name,rx_block,
+					status,rfd_cmd,rx_next,pbuf,pkt_len);
+				eexp_hw_rxmap(dev,rx_block);
+				boguscount++;
+				continue;
+			}
+			else if (!FD_OK(status)) 
+			{
+				lp->stats.rx_errors++;
+				if (FD_CRC(status)) 
+					lp->stats.rx_crc_errors++;
+				if (FD_Align(status))
+					lp->stats.rx_frame_errors++;
+				if (FD_Resrc(status))
+					lp->stats.rx_fifo_errors++;
+				if (FD_DMA(status))
+					lp->stats.rx_over_errors++;
+				if (FD_Short(status))
+					lp->stats.rx_length_errors++;
 			}
+			else
+			{
+				struct sk_buff *skb;
+				pkt_len &= 0x3fff;
+				skb = dev_alloc_skb(pkt_len+16);
+				if (skb == NULL) 
+				{
+					printk("%s: Memory squeeze, dropping packet\n",dev->name);
+					lp->stats.rx_dropped++;
+					break;
+				}
+				skb->dev = dev;
+				skb_reserve(skb, 2);
+				outw(pbuf+10,ioaddr+READ_PTR);
+				insw(ioaddr,skb_put(skb,pkt_len),(pkt_len+1)>>1);
+				skb->protocol = eth_type_trans(skb,dev);
+				netif_rx(skb);
+				lp->stats.rx_packets++;
+			}
+			outw(rx_block,ioaddr+WRITE_PTR);
+			outw(0x0000,ioaddr);
+			outw(0x0000,ioaddr);
 		}
-		init_rx_bufs(dev);
-		outw(RX_BUF_START, SCB_RFA);
-		outw(saved_write_ptr, ioaddr + WRITE_PTR);
-		ack_cmd |= RX_START;
+		rx_block = rx_next;
 	}
+	outw(old_rp,ioaddr+READ_PTR);
+	outw(old_wp,ioaddr+WRITE_PTR);
+}
 
-	outw(ack_cmd, ioaddr + SCB_CMD);
-	outb(0, ioaddr + SIGNAL_CA);
+/*
+ * Hand a packet to the card for transmission
+ * If we get here, we MUST have already checked
+ * to make sure there is room in the transmit
+ * buffer region
+ */
 
-    if (net_debug > 5) {
-		printk("%s: EExp exiting interrupt, status %4.4x.\n", dev->name,
-			   inw(ioaddr + SCB_CMD));
-    }
-	/* Enable the 82586's input to the interrupt line. */
-	outb(irqrmap[dev->irq] | 0x08, ioaddr + SET_IRQ);
-	return;
+static void eexp_hw_tx(struct device *dev, unsigned short *buf, unsigned short len)
+{
+	struct net_local *lp = (struct net_local *)dev->priv;
+	unsigned short ioaddr = dev->base_addr;
+	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+
+	outw(lp->tx_head,ioaddr+WRITE_PTR);
+	outw(0x0000,ioaddr);
+	outw(Cmd_INT|Cmd_Xmit,ioaddr);
+	outw(lp->tx_head+0x08,ioaddr);
+	outw(lp->tx_head+0x0e,ioaddr);
+	outw(0x0000,ioaddr);
+	outw(0x0000,ioaddr);
+	outw(lp->tx_head+0x08,ioaddr);
+	outw(0x8000|len,ioaddr);
+	outw(-1,ioaddr);
+	outw(lp->tx_head+0x16,ioaddr);
+	outw(0,ioaddr);
+	outsw(ioaddr,buf,(len+1)>>1);
+	outw(lp->tx_tail+0x0c,ioaddr+WRITE_PTR);
+	outw(lp->tx_head,ioaddr);
+	dev->trans_start = jiffies;
+	lp->tx_tail = lp->tx_head;
+	if (lp->tx_head==TX_BUF_START+((NUM_TX_BUFS-1)*TX_BUF_SIZE)) 
+		lp->tx_head = TX_BUF_START;
+	else 
+		lp->tx_head += TX_BUF_SIZE;
+	if (lp->tx_head != lp->tx_reap) 
+		dev->tbusy = 0;
+	outw(old_wp,ioaddr+WRITE_PTR);
 }
 
-static int
-eexp_close(struct device *dev)
+/*
+ * Sanity check the suspected EtherExpress card
+ * Read hardware address, reset card, size memory and
+ * initialize buffer memory pointers. These should
+ * probably be held in dev->priv, incase someone has 2
+ * differently configured cards in their box (Arghhh!)
+ */
+
+static int eexp_hw_probe(struct device *dev, unsigned short ioaddr)
 {
-	int ioaddr = dev->base_addr;
+	unsigned short hw_addr[3];
+	int i;
+	unsigned char *chw_addr = (unsigned char *)hw_addr;
 
-	dev->tbusy = 1;
-	dev->start = 0;
+	printk("%s: EtherExpress at %#x, ",dev->name,ioaddr);
 
-	/* Flush the Tx and disable Rx. */
-	outw(RX_SUSPEND | CUC_SUSPEND, ioaddr + SCB_CMD);
-	outb(0, ioaddr + SIGNAL_CA);
+	hw_addr[0] = eexp_hw_readeeprom(ioaddr,2);
+	hw_addr[1] = eexp_hw_readeeprom(ioaddr,3);
+	hw_addr[2] = eexp_hw_readeeprom(ioaddr,4);
 
-	/* Disable the physical interrupt line. */
-	outb(0, ioaddr + SET_IRQ);
+	if (hw_addr[2]!=0x00aa || ((hw_addr[1] & 0xff00)!=0x0000)) 
+	{
+		printk("rejected: invalid address %04x%04x%04x\n",
+			hw_addr[2],hw_addr[1],hw_addr[0]);
+		return ENODEV;
+	}
 
-	free_irq(dev->irq, NULL);
+	dev->base_addr = ioaddr;
+	for ( i=0 ; i<6 ; i++ ) 
+		dev->dev_addr[i] = chw_addr[5-i];
 
-	irq2dev_map[dev->irq] = 0;
+	{
+		char irqmap[]={0, 9, 3, 4, 5, 10, 11, 0};
+		char *ifmap[]={"AUI", "BNC", "10baseT"};
+		enum iftype {AUI=0, BNC=1, TP=2};
+		unsigned short setupval = eexp_hw_readeeprom(ioaddr,0);
 
-	/* Update the statistics here. */
+		dev->irq = irqmap[setupval>>13];
+		dev->if_port = !(setupval & 0x1000) ? AUI :
+			eexp_hw_readeeprom(ioaddr,5) & 0x1 ? TP : BNC;
 
-	MOD_DEC_USE_COUNT;
-	return 0;
-}
+		printk("IRQ %d, Interface %s, ",dev->irq,ifmap[dev->if_port]);
 
-/* Get the current statistics.	This may be called with the card open or
-   closed. */
-static struct enet_statistics *
-eexp_get_stats(struct device *dev)
-{
-	struct net_local *lp = (struct net_local *)dev->priv;
+		outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+		outb(0,ioaddr+SET_IRQ);
+	}
 
-	/* ToDo: decide if there are any useful statistics from the SCB. */
+	eexp_hw_ASICrst(dev);
 
-	return &lp->stats;
-}
+	{
+		unsigned short i586mso = 0x023e;
+		unsigned short old_wp,old_rp,old_a0,old_a1;
+		unsigned short a0_0,a1_0,a0_1,a1_1;
+
+		old_wp = inw(ioaddr+WRITE_PTR);
+		old_rp = inw(ioaddr+READ_PTR);
+		outw(0x8000+i586mso,ioaddr+READ_PTR);
+		old_a1 = inw(ioaddr);
+		outw(i586mso,ioaddr+READ_PTR);
+		old_a0 = inw(ioaddr);
+		outw(i586mso,ioaddr+WRITE_PTR);
+		outw(0x55aa,ioaddr);
+		outw(i586mso,ioaddr+READ_PTR);
+		a0_0 = inw(ioaddr);
+		outw(0x8000+i586mso,ioaddr+WRITE_PTR);
+		outw(0x5a5a,ioaddr);
+		outw(0x8000+i586mso,ioaddr+READ_PTR);
+		a1_0 = inw(ioaddr);
+		outw(i586mso,ioaddr+READ_PTR);
+		a0_1 = inw(ioaddr);
+		outw(i586mso,ioaddr+WRITE_PTR);
+		outw(0x1234,ioaddr);
+		outw(0x8000+i586mso,ioaddr+READ_PTR);
+		a1_1 = inw(ioaddr);
+
+		if ((a0_0 != a0_1) || (a1_0 != a1_1) ||
+			(a1_0 != 0x5a5a) || (a0_0 != 0x55aa)) 
+		{
+			printk("32k\n");
+			RX_BUF_END = 0x7ff6;
+		}
+		else
+		{
+			printk("64k\n");
+			NUM_TX_BUFS = 8;
+			RX_BUF_START = TX_BUF_START + (NUM_TX_BUFS*TX_BUF_SIZE);
+			RX_BUF_END = 0xfff6;
+		}
 
-/* Set or clear the multicast filter for this adaptor.
- */
-static void
-set_multicast_list(struct device *dev)
-{
-/* This doesn't work yet */
-#if 0
-	short ioaddr = dev->base_addr;
-	if (num_addrs < 0) {
-		/* Not written yet, this requires expanding the init_words config
-		   cmd. */
-	} else if (num_addrs > 0) {
-		/* Fill in the SET_MC_CMD with the number of address bytes, followed
-		   by the list of multicast addresses to be accepted. */
-		outw(SET_MC_CMD + 6, ioaddr + WRITE_PTR);
-		outw(num_addrs * 6, ioaddr);
-		outsw(ioaddr, addrs, num_addrs*3);		/* 3 = addr len in words */
-		/* We must trigger a whole 586 reset due to a bug. */
-	} else {
-		/* Not written yet, this requires expanding the init_words config
-		   cmd. */
-		outw(99, ioaddr);		/* Disable promiscuous mode, use normal mode */
+		outw(0x8000+i586mso,ioaddr+WRITE_PTR);
+		outw(old_a1,ioaddr);
+		outw(i586mso,ioaddr+WRITE_PTR);
+		outw(old_a0,ioaddr);
+		outw(old_wp,ioaddr+WRITE_PTR);
+		outw(old_rp,ioaddr+READ_PTR);
 	}
-#endif
-}
-
-/* The horrible routine to read a word from the serial EEPROM. */
+  
+	if (net_debug) 
+		printk(version);
 
-/* The delay between EEPROM clock transitions. */
-#define eeprom_delay()	{ int _i = 40; while (--_i > 0) { __SLOW_DOWN_IO; }}
-#define EE_READ_CMD (6 << 6)
+	dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
+	memset(dev->priv, 0, sizeof(struct net_local));
+	dev->open = eexp_open;
+	dev->stop = eexp_close;
+	dev->hard_start_xmit = eexp_xmit;
+	dev->get_stats = eexp_stats;
+	ether_setup(dev);
+	return 0;
+}
 
-int
-read_eeprom(int ioaddr, int location)
+/*
+ *	Read a word from eeprom location (0-63?)
+ */
+static unsigned short eexp_hw_readeeprom(unsigned short ioaddr, unsigned char location)
 {
+	unsigned short cmd = 0x180|(location&0x7f);
+	unsigned short rval = 0,wval = EC_CS|i586_RST;
 	int i;
-	unsigned short retval = 0;
-	short ee_addr = ioaddr + EEPROM_Ctrl;
-	int read_cmd = location | EE_READ_CMD;
-	short ctrl_val = EE_CS | _586_RESET;
-	
-	outb(ctrl_val, ee_addr);
-	
-	/* Shift the read command bits out. */
-	for (i = 8; i >= 0; i--) {
-		short outval = (read_cmd & (1 << i)) ? ctrl_val | EE_DATA_WRITE
-			: ctrl_val;
-		outb(outval, ee_addr);
-		outb(outval | EE_SHIFT_CLK, ee_addr);	/* EEPROM clock tick. */
+ 
+	outb(EC_CS|i586_RST,ioaddr+EEPROM_Ctrl);
+	for ( i=0x100 ; i ; i>>=1 ) 
+	{
+		if (cmd&i) 
+			wval |= EC_Wr;
+		else 
+			wval &= ~EC_Wr;
+
+		outb(wval,ioaddr+EEPROM_Ctrl);
+		outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
 		eeprom_delay();
-		outb(outval, ee_addr);	/* Finish EEPROM a clock tick. */
+		outb(wval,ioaddr+EEPROM_Ctrl);
+		eeprom_delay();
+	}	
+	wval &= ~EC_Wr;
+	outb(wval,ioaddr+EEPROM_Ctrl);
+	for ( i=0x8000 ; i ; i>>=1 ) 
+	{
+		outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
+		eeprom_delay();
+		if (inb(ioaddr+EEPROM_Ctrl)&EC_Rd) 
+			rval |= i;
+		outb(wval,ioaddr+EEPROM_Ctrl);
 		eeprom_delay();
 	}
-	outb(ctrl_val, ee_addr);
-	
-	for (i = 16; i > 0; i--) {
-		outb(ctrl_val | EE_SHIFT_CLK, ee_addr);	 eeprom_delay();
-		retval = (retval << 1) | ((inb(ee_addr) & EE_DATA_READ) ? 1 : 0);
-		outb(ctrl_val, ee_addr);  eeprom_delay();
-	}
-
-	/* Terminate the EEPROM access. */
-	ctrl_val &= ~EE_CS;
-	outb(ctrl_val | EE_SHIFT_CLK, ee_addr);
+	wval &= ~EC_CS;
+	outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
 	eeprom_delay();
-	outb(ctrl_val, ee_addr);
+	outb(wval,ioaddr+EEPROM_Ctrl);
 	eeprom_delay();
-	return retval;
+	return rval;
 }
 
-static void
-init_82586_mem(struct device *dev)
+/*
+ * Reap tx buffers and return last transmit status.
+ * if ==0 then either:
+ *    a) we're not transmitting anything, so why are we here?
+ *    b) we've died.
+ * otherwise, Stat_Busy(return) means we've still got some packets
+ * to transmit, Stat_Done(return) means our buffers should be empty
+ * again
+ */
+
+static unsigned short eexp_hw_lasttxstat(struct device *dev)
 {
 	struct net_local *lp = (struct net_local *)dev->priv;
-	short ioaddr = dev->base_addr;
+	unsigned short ioaddr = dev->base_addr;
+	unsigned short old_rp = inw(ioaddr+READ_PTR);
+	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+	unsigned short tx_block = lp->tx_reap;
+	unsigned short status;
+  
+	if (!test_bit(0,(void *)&dev->tbusy) && lp->tx_head==lp->tx_reap) 
+		return 0x0000;
 
-	/* Enable loopback to protect the wire while starting up.
-	   This is Superstition From Crynwr. */
-	outb(inb(ioaddr + Config) | 0x02, ioaddr + Config);
-
-	/* Hold the 586 in reset during the memory initialization. */
-	outb(_586_RESET, ioaddr + EEPROM_Ctrl);
-
-	/* Place the write pointer at 0xfff6 (address-aliased to 0xfffff6). */
-	outw(0xfff6, ioaddr + WRITE_PTR);
-	outsw(ioaddr, init_words, sizeof(init_words)>>1);
-
-	/* Fill in the station address. */
-	outw(SA_OFFSET, ioaddr + WRITE_PTR);
-	outsw(ioaddr, dev->dev_addr, 3);
-
-	/* The Tx-block list is written as needed.  We just set up the values. */
-#ifdef initial_text_tx
-	lp->tx_cmd_link = DUMP_DATA + 4;
-#else
-	lp->tx_cmd_link = IDLELOOP + 4;
-#endif
-	lp->tx_head = lp->tx_reap = TX_BUF_START;
+	do
+	{
+		outw(tx_block,ioaddr+READ_PTR);
+		status = inw(ioaddr);
+		if (!Stat_Done(status)) 
+		{
+			lp->tx_link = tx_block;
+			outw(old_rp,ioaddr+READ_PTR);
+			outw(old_wp,ioaddr+WRITE_PTR);
+			return status;
+		}
+		else 
+		{
+			lp->last_tx_restart = 0;
+			lp->stats.collisions += Stat_NoColl(status);
+			if (!Stat_OK(status)) 
+			{
+				if (Stat_Abort(status)) 
+					lp->stats.tx_aborted_errors++;
+				if (Stat_TNoCar(status) || Stat_TNoCTS(status)) 
+					lp->stats.tx_carrier_errors++;
+				if (Stat_TNoDMA(status)) 
+					lp->stats.tx_fifo_errors++;
+			}
+			else
+				lp->stats.tx_packets++;
+		}
+		if (tx_block == TX_BUF_START+((NUM_TX_BUFS-1)*TX_BUF_SIZE)) 
+			lp->tx_reap = tx_block = TX_BUF_START;
+		else
+			lp->tx_reap = tx_block += TX_BUF_SIZE;
+		dev->tbusy = 0;
+		mark_bh(NET_BH);
+	}
+	while (lp->tx_reap != lp->tx_head);
 
-	init_rx_bufs(dev);
+	lp->tx_link = lp->tx_tail + 0x08;
+	outw(old_rp,ioaddr+READ_PTR);
+	outw(old_wp,ioaddr+WRITE_PTR);
 
-	/* Start the 586 by releasing the reset line. */
-	outb(0x00, ioaddr + EEPROM_Ctrl);
+	return status;
+}
 
-	/* This was time consuming to track down: you need to give two channel
-	   attention signals to reliably start up the i82586. */
-	outb(0, ioaddr + SIGNAL_CA);
+/* 
+ * This should never happen. It is called when some higher
+ * routine detects the CU has stopped, to try and restart
+ * it from the last packet we knew we were working on,
+ * or the idle loop if we had finished for the time.
+ */
+
+static void eexp_hw_txrestart(struct device *dev)
+{
+	struct net_local *lp = (struct net_local *)dev->priv;
+	unsigned short ioaddr = dev->base_addr;
+  
+	lp->last_tx_restart = lp->tx_link;
+	outw(lp->tx_link,ioaddr+SCB_CBL);
+	outw(SCB_CUstart,ioaddr+SCB_CMD);
+	outw(0,ioaddr+SCB_STATUS);
+	outb(0,ioaddr+SIGNAL_CA);
 
 	{
-		int boguscnt = 50;
-		while (inw(ioaddr + SCB_STATUS) == 0)
-			if (--boguscnt == 0) {
-				printk("%s: i82586 initialization timed out with status %04x, cmd %04x.\n",
-					   dev->name, inw(ioaddr + SCB_STATUS), inw(ioaddr + SCB_CMD));
-				break;
+		unsigned short boguscount=50,failcount=5;
+		while (!inw(ioaddr+SCB_STATUS)) 
+		{
+			if (!--boguscount) 
+			{
+				if (--failcount) 
+				{
+					printk("%s: CU start timed out, status %04x, cmd %04x\n",
+						dev->name, inw(ioaddr+SCB_STATUS), inw(ioaddr+SCB_CMD));
+					outw(lp->tx_link,ioaddr+SCB_CBL);
+					outw(0,ioaddr+SCB_STATUS);
+					outw(SCB_CUstart,ioaddr+SCB_CMD);
+					outb(0,ioaddr+SIGNAL_CA);
+					boguscount = 100;
+				}
+				else
+				{
+					printk("%s: Failed to restart CU, resetting board...\n",dev->name);
+					eexp_hw_init586(dev);
+					dev->tbusy = 0;
+					mark_bh(NET_BH);
+					return;
+				}
 			}
-		/* Issue channel-attn -- the 82586 won't start without it. */
-		outb(0, ioaddr + SIGNAL_CA);
+		}
 	}
-
-	/* Disable loopback. */
-	outb(inb(ioaddr + Config) & ~0x02, ioaddr + Config);
-	if (net_debug > 4)
-		printk("%s: Initialized 82586, status %04x.\n", dev->name,
-			   inw(ioaddr + SCB_STATUS));
-	return;
 }
 
-/* Initialize the Rx-block list. */
-static void init_rx_bufs(struct device *dev)
+/*
+ * Writes down the list of transmit buffers into card
+ * memory. Initial seperate, repeated transmits link
+ * them into a circular list, such that the CU can
+ * be constantly active, and unlink them as we reap
+ * transmitted packet buffers, so the CU doesn't loop
+ * and endlessly transmit packets. (Try hacking the driver
+ * to send continuous broadcast messages, say ARP requests
+ * on a subnet with Windows boxes running on Novell and
+ * LAN Workplace with EMM386. Amusing to watch them all die
+ * horribly leaving the Linux boxes up!)
+ */
+
+static void eexp_hw_txinit(struct device *dev)
 {
 	struct net_local *lp = (struct net_local *)dev->priv;
-	short ioaddr = dev->base_addr;
+	unsigned short ioaddr = dev->base_addr;
+	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+	unsigned short tx_block = TX_BUF_START;
+	unsigned short curtbuf;
 
-	int cur_rxbuf = lp->rx_head = RX_BUF_START;
-	
-	/* Initialize each Rx frame + data buffer. */
-	do {	/* While there is room for one more. */
-		outw(cur_rxbuf, ioaddr + WRITE_PTR);
-		outw(0x0000, ioaddr); 				/* Status */
-		outw(0x0000, ioaddr);				/* Command */
-		outw(cur_rxbuf + RX_BUF_SIZE, ioaddr); /* Link */
-		outw(cur_rxbuf + 22, ioaddr);		/* Buffer offset */
-		outw(0xFeed, ioaddr); 				/* Pad for dest addr. */
-		outw(0xF00d, ioaddr);
-		outw(0xF001, ioaddr);
-		outw(0x0505, ioaddr); 				/* Pad for source addr. */
-		outw(0x2424, ioaddr);
-		outw(0x6565, ioaddr);
-		outw(0xdeaf, ioaddr);				/* Pad for protocol. */
-
-		outw(0x0000, ioaddr);				/* Buffer: Actual count */
-		outw(-1, ioaddr);					/* Buffer: Next (none). */
-		outw(cur_rxbuf + 0x20, ioaddr);		/* Buffer: Address low */
-		outw(0x0000, ioaddr);
-		/* Finally, the number of bytes in the buffer. */
-		outw(0x8000 + RX_BUF_SIZE-0x20, ioaddr);
-		
-		lp->rx_tail = cur_rxbuf;
-		cur_rxbuf += RX_BUF_SIZE;
-	} while (cur_rxbuf <= RX_BUF_END - RX_BUF_SIZE);
-	
-	/* Terminate the list by setting the EOL bit, and wrap the pointer to make
-	   the list a ring. */
-	outw(lp->rx_tail + 2, ioaddr + WRITE_PTR);
-	outw(0xC000, ioaddr);					/* Command, mark as last. */
-	outw(lp->rx_head, ioaddr);				/* Link */
+	for ( curtbuf=0 ; curtbuf<NUM_TX_BUFS ; curtbuf++ ) 
+	{
+		outw(tx_block,ioaddr+WRITE_PTR);
+		outw(0x0000,ioaddr);
+		outw(Cmd_INT|Cmd_Xmit,ioaddr);
+		outw(tx_block+0x08,ioaddr);
+		outw(tx_block+0x0e,ioaddr);
+		outw(0x0000,ioaddr);
+		outw(0x0000,ioaddr);
+		outw(tx_block+0x08,ioaddr);
+		outw(0x8000,ioaddr);
+		outw(-1,ioaddr);
+		outw(tx_block+0x16,ioaddr);
+		outw(0x0000,ioaddr);
+		tx_block += TX_BUF_SIZE;
+	}
+	lp->tx_head = TX_BUF_START;
+	lp->tx_reap = TX_BUF_START;
+	lp->tx_tail = tx_block - TX_BUF_SIZE;
+	lp->tx_link = lp->tx_tail + 0x08;
+	RX_BUF_START = tx_block;
+	outw(old_wp,ioaddr+WRITE_PTR);
 }
 
-static void
-hardware_send_packet(struct device *dev, void *buf, short length)
+/* is this a standard test pattern, or dbecker randomness? */
+
+unsigned short rx_words[] = 
+{
+	0xfeed,0xf00d,0xf001,0x0505,0x2424,0x6565,0xdeaf
+};
+
+/*
+ * Write the circular list of receive buffer descriptors to
+ * card memory. Note, we no longer mark the end of the list,
+ * so if all the buffers fill up, the 82586 will loop until
+ * we free one. This may sound dodgy, but it works, and
+ * it makes the error detection in the interrupt handler
+ * a lot simpler.
+ */
+
+static void eexp_hw_rxinit(struct device *dev)
 {
 	struct net_local *lp = (struct net_local *)dev->priv;
-	short ioaddr = dev->base_addr;
-	short tx_block = lp->tx_head;
+	unsigned short ioaddr = dev->base_addr;
+	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+	unsigned short rx_block = RX_BUF_START;
+
+	NUM_RX_BUFS = 0;
+	lp->rx_first = rx_block;
+	do 
+	{
+		NUM_RX_BUFS++;
+		outw(rx_block,ioaddr+WRITE_PTR);
+		outw(0x0000,ioaddr);
+		outw(0x0000,ioaddr);
+		outw(rx_block+RX_BUF_SIZE,ioaddr);
+		outw(rx_block+0x16,ioaddr);
+		outsw(ioaddr, rx_words, sizeof(rx_words)>>1);
+		outw(0x8000,ioaddr);
+		outw(-1,ioaddr);
+		outw(rx_block+0x20,ioaddr);
+		outw(0x0000,ioaddr);
+		outw(0x8000|(RX_BUF_SIZE-0x20),ioaddr);
+		lp->rx_last = rx_block;
+		rx_block += RX_BUF_SIZE;
+	} while (rx_block <= RX_BUF_END-RX_BUF_SIZE);
 
-	/* Set the write pointer to the Tx block, and put out the header. */
-	outw(tx_block, ioaddr + WRITE_PTR);
-	outw(0x0000, ioaddr);		/* Tx status */
-	outw(CMD_INTR|CmdTx, ioaddr);		/* Tx command */
-	outw(tx_block+16, ioaddr);	/* Next command is a NoOp. */
-	outw(tx_block+8, ioaddr);	/* Data Buffer offset. */
-
-	/* Output the data buffer descriptor. */
-	outw(length | 0x8000, ioaddr); /* Byte count parameter. */
-	outw(-1, ioaddr);			/* No next data buffer. */
-	outw(tx_block+22, ioaddr);	/* Buffer follows the NoOp command. */
-	outw(0x0000, ioaddr);		/* Buffer address high bits (always zero). */
-
-	/* Output the Loop-back NoOp command. */
-	outw(0x0000, ioaddr);		/* Tx status */
-	outw(CmdNOp, ioaddr);		/* Tx command */
-	outw(tx_block+16, ioaddr);	/* Next is myself. */
-
-	/* Output the packet using the write pointer.
-	   Hmmm, it feels a little like a 3c501! */
-	outsw(ioaddr + DATAPORT, buf, (length + 1) >> 1);
-
-	/* Set the old command link pointing to this send packet. */
-	outw(lp->tx_cmd_link, ioaddr + WRITE_PTR);
-	outw(tx_block, ioaddr);
-	lp->tx_cmd_link = tx_block + 20;
-
-	/* Set the next free tx region. */
-	lp->tx_head = tx_block + TX_BUF_SIZE;
-	if (lp->tx_head > TX_BUF_END - TX_BUF_SIZE)
-		lp->tx_head = TX_BUF_START;
+	outw(lp->rx_last+4,ioaddr+WRITE_PTR);
+	outw(lp->rx_first,ioaddr);
 
-    if (net_debug > 4) {
-		printk("%s: EExp @%x send length = %d, tx_block %3x, next %3x, "
-			   "reap %4x status %4.4x.\n", dev->name, ioaddr, length,
-			   tx_block, lp->tx_head, lp->tx_reap, inw(ioaddr + SCB_STATUS));
-    }
+	outw(old_wp,ioaddr+WRITE_PTR);
+}
 
-	if (lp->tx_head != lp->tx_reap)
-		dev->tbusy = 0;
+/*
+ * This really ought not to be necessary now. Repairs a single
+ * damaged receive buffer. If buffer memory is getting bashed
+ * enough to call this, we probably have bigger problems that can
+ * be fixed here.
+ */
+static void eexp_hw_rxmap(struct device *dev, unsigned short rx_buf)
+{
+	struct net_local *lp = (struct net_local *)dev->priv;
+	unsigned short ioaddr = dev->base_addr;
+	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+
+	outw(rx_buf,ioaddr+WRITE_PTR);
+	outw(0x0000,ioaddr);
+	outw(0x0000,ioaddr);
+	outw((rx_buf==lp->rx_last)?lp->rx_first:(rx_buf+RX_BUF_SIZE),ioaddr);
+	outw(rx_buf+0x16,ioaddr);
+	outsw(ioaddr, rx_words, sizeof(rx_words)>>1);
+	outw(0x8000,ioaddr);
+	outw(-1,ioaddr);
+	outw(rx_buf+0x20,ioaddr);
+	outw(0x0000,ioaddr);
+	outw(0x8000|(RX_BUF_SIZE-0x20),ioaddr);
+	outw(old_wp,ioaddr+WRITE_PTR);
 }
 
-static void
-eexp_rx(struct device *dev)
+/*
+ * Reset the 586, fill memory (including calls to
+ * eexp_hw_[(rx)(tx)]init()) unreset, and start
+ * the configuration sequence. We don't wait for this
+ * to finish, but allow the interrupt handler to start
+ * the CU and RU for us. We can't start the receive/
+ * transmission system up before we know that the
+ * hardware is configured correctly
+ */
+static void eexp_hw_init586(struct device *dev)
 {
 	struct net_local *lp = (struct net_local *)dev->priv;
-	short ioaddr = dev->base_addr;
-	short saved_write_ptr = inw(ioaddr + WRITE_PTR);
-	short rx_head = lp->rx_head;
-	short rx_tail = lp->rx_tail;
-	short boguscount = 10;
-	short frame_status;
-
-	/* Set the read pointer to the Rx frame. */
-	outw(rx_head, ioaddr + READ_PTR);
-	while ((frame_status = inw(ioaddr)) < 0) {		/* Command complete */
-		short rfd_cmd = inw(ioaddr);
-		short next_rx_frame = inw(ioaddr);
-		short data_buffer_addr = inw(ioaddr);
-		short pkt_len;
-		
-		/* Set the read pointer the data buffer. */
-		outw(data_buffer_addr, ioaddr + READ_PTR);
-		pkt_len = inw(ioaddr);
-
-		if (rfd_cmd != 0  ||  data_buffer_addr != rx_head + 22
-			||  (pkt_len & 0xC000) != 0xC000) {
-			printk("%s: Rx frame at %#x corrupted, status %04x cmd %04x"
-				   "next %04x data-buf @%04x %04x.\n", dev->name, rx_head,
-				   frame_status, rfd_cmd, next_rx_frame, data_buffer_addr,
-				   pkt_len);
-		} else if ((frame_status & 0x2000) == 0) {
-			/* Frame Rxed, but with error. */
-			lp->stats.rx_errors++;
-			if (frame_status & 0x0800) lp->stats.rx_crc_errors++;
-			if (frame_status & 0x0400) lp->stats.rx_frame_errors++;
-			if (frame_status & 0x0200) lp->stats.rx_fifo_errors++;
-			if (frame_status & 0x0100) lp->stats.rx_over_errors++;
-			if (frame_status & 0x0080) lp->stats.rx_length_errors++;
-		} else {
-			/* Malloc up new buffer. */
-			struct sk_buff *skb;
-
-			pkt_len &= 0x3fff;
-			skb = dev_alloc_skb(pkt_len+2);
-			if (skb == NULL) {
-				printk("%s: Memory squeeze, dropping packet.\n", dev->name);
-				lp->stats.rx_dropped++;
-				break;
-			}
-			skb->dev = dev;
-			skb_reserve(skb,2);
+	unsigned short ioaddr = dev->base_addr;
 
-			outw(data_buffer_addr + 10, ioaddr + READ_PTR);
+	started = 0;
+	set_loopback;
 
-			insw(ioaddr, skb_put(skb,pkt_len), (pkt_len + 1) >> 1);
-		
-			skb->protocol=eth_type_trans(skb,dev);
-			netif_rx(skb);
-			lp->stats.rx_packets++;
-		}
+	outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+	outb(i586_RST,ioaddr+EEPROM_Ctrl);
 
-		/* Clear the status word and set End-of-List on the rx frame. */
-		outw(rx_head, ioaddr + WRITE_PTR);
-		outw(0x0000, ioaddr);
-		outw(0xC000, ioaddr);
-#ifndef final_version
-		if (next_rx_frame != rx_head + RX_BUF_SIZE
-			&& next_rx_frame != RX_BUF_START) {
-			printk("%s: Rx next frame at %#x is %#x instead of %#x.\n", dev->name,
-				   rx_head, next_rx_frame, rx_head + RX_BUF_SIZE);
-			next_rx_frame = rx_head + RX_BUF_SIZE;
-			if (next_rx_frame >= RX_BUF_END - RX_BUF_SIZE)
-				next_rx_frame = RX_BUF_START;
+	{
+		unsigned short wcnt;
+		wcnt = 0;
+		outw(0,ioaddr+WRITE_PTR);
+		while ((wcnt+=2) != RX_BUF_END+12) 
+			outw(0,ioaddr);
+	} 
+  
+	outw(RX_BUF_END,ioaddr+WRITE_PTR);
+	outsw(ioaddr, start_code, sizeof(start_code)>>1);
+	outw(CONF_HW_ADDR,ioaddr+WRITE_PTR);
+	outsw(ioaddr,dev->dev_addr,3);
+	eexp_hw_txinit(dev);
+	eexp_hw_rxinit(dev);
+	outw(0,ioaddr+WRITE_PTR);
+	outw(1,ioaddr);
+	outb(0,ioaddr+EEPROM_Ctrl);
+	outw(0,ioaddr+SCB_CMD);
+	outb(0,ioaddr+SIGNAL_CA);
+	{
+		unsigned short rboguscount=50,rfailcount=5;
+		while (outw(0,ioaddr+READ_PTR),inw(ioaddr)) 
+		{
+			if (!--rboguscount) 
+			{
+				printk("%s: i82586 reset timed out, kicking...\n",
+					dev->name);
+				outw(0,ioaddr+SCB_CMD);
+				outb(0,ioaddr+SIGNAL_CA);
+				rboguscount = 100;
+				if (!--rfailcount) 
+				{
+					printk("%s: i82586 not responding, giving up.\n",
+						dev->name);
+					return;
+				}
+			}
 		}
-#endif
-		outw(rx_tail+2, ioaddr + WRITE_PTR);
-		outw(0x0000, ioaddr);	/* Clear the end-of-list on the prev. RFD. */
+	}
 
-#ifndef final_version
-		outw(rx_tail+4, ioaddr + READ_PTR);
-		if (inw(ioaddr) != rx_head) {
-			printk("%s: Rx buf link mismatch, at %04x link %04x instead of %04x.\n",
-				   dev->name, rx_tail, (outw(rx_tail+4, ioaddr + READ_PTR),inw(ioaddr)),
-				   rx_head);
-			outw(rx_head, ioaddr);
+	outw(CONF_LINK,ioaddr+SCB_CBL);
+	outw(0,ioaddr+SCB_STATUS);
+	outw(0xf000|SCB_CUstart,ioaddr+SCB_CMD);
+	outb(0,ioaddr+SIGNAL_CA);
+	{
+		unsigned short iboguscount=50,ifailcount=5;
+		while (!inw(ioaddr+SCB_STATUS)) 
+		{
+			if (!--iboguscount) 
+			{
+				if (--ifailcount) 
+				{
+					printk("%s: i82586 initialization timed out, status %04x, cmd %04x\n",
+						dev->name, inw(ioaddr+SCB_STATUS), inw(ioaddr+SCB_CMD));
+					outw(CONF_LINK,ioaddr+SCB_CBL);
+					outw(0,ioaddr+SCB_STATUS);
+					outw(0xf000|SCB_CUstart,ioaddr+SCB_CMD);
+					outb(0,ioaddr+SIGNAL_CA);
+					iboguscount = 100;
+				}
+				else 
+				{
+					printk("%s: Failed to initialize i82586, giving up.\n",dev->name);
+					return;
+				}
+			}
 		}
-#endif
+	}
+  
+	outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
+	clear_loopback;
+	lp->init_time = jiffies;
+	if (started) 
+		printk("%s: Uh? We haven't started yet\n",dev->name);
+	return;
+}
+
+/* 
+ * completely reset the EtherExpress hardware. We will most likely get
+ * an interrupt during this whether we want one or not. It is best,
+ * therefore, to call this while we don't have a request_irq() on.
+ */
 
-		rx_tail = rx_head;
-		rx_head = next_rx_frame;
-		if (--boguscount == 0)
-			break;
-		outw(rx_head, ioaddr + READ_PTR);
+static void eexp_hw_ASICrst(struct device *dev)
+{
+	unsigned short ioaddr = dev->base_addr;
+	unsigned short wrval = 0x0001,succount=0,boguscount=500;
+
+	outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+
+	set_loopback;  /* yet more paranoia - since we're resetting the ASIC
+			* that controls this function, how can it possibly work?
+                        */
+	started = 0;
+	outb(ASIC_RST|i586_RST,ioaddr+EEPROM_Ctrl);
+	while (succount<20) 
+	{
+		if (wrval == 0xffff) 
+			wrval = 0x0001;
+		outw(0,ioaddr+WRITE_PTR);
+		outw(wrval,ioaddr);
+		outw(0,ioaddr+READ_PTR);
+		if (wrval++ == inw(ioaddr)) 
+			succount++;
+		else 
+		{
+			succount = 0;
+			if (!boguscount--) 
+			{
+				boguscount = 500;
+				printk("%s: Having problems resetting EtherExpress ASIC, continuing...\n",
+					dev->name);
+				wrval = 0x0001;
+				outb(ASIC_RST|i586_RST,ioaddr+EEPROM_Ctrl);
+			}
+		}
 	}
-	
-	lp->rx_head = rx_head;
-	lp->rx_tail = rx_tail;
-	
-	/* Restore the original write pointer. */
-	outw(saved_write_ptr, ioaddr + WRITE_PTR);
+	outb(i586_RST,ioaddr+EEPROM_Ctrl);
 }
-
+
+/*
+ * MODULE stuff
+ */
+
 #ifdef MODULE
-static char devicename[9] = { 0, };
-static struct device dev_eexpress = {
-	devicename, /* device name is inserted by linux/drivers/net/net_init.c */
-	0, 0, 0, 0,
-	0, 0,
-	0, 0, 0, NULL, express_probe };
-	
-
-static int irq=0x300;
-static int io=0;	
-
-int
-init_module(void)
-{
-	if (io == 0)
-		printk("eexpress: You should not use auto-probing with insmod!\n");
-	dev_eexpress.base_addr=io;
-	dev_eexpress.irq=irq;
-	if (register_netdev(&dev_eexpress) != 0)
+
+static struct device dev_eexpress = 
+{
+	"EExpress", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, express_probe 
+};
+
+int irq = 0;
+int io = 0;
+
+int init_module(void)
+{
+	dev_eexpress.base_addr = io;
+	dev_eexpress.irq = irq;
+	if (register_netdev(&dev_eexpress) != 0) 
 		return -EIO;
 	return 0;
 }
 
-void
-cleanup_module(void)
+void cleanup_module(void)
 {
 	unregister_netdev(&dev_eexpress);
 	kfree_s(dev_eexpress.priv,sizeof(struct net_local));
-	dev_eexpress.priv=NULL;
-
-	/* If we don't do this, we can't re-insmod it later. */
-	release_region(dev_eexpress.base_addr, EEXPRESS_IO_EXTENT);
+	dev_eexpress.priv = NULL;
 }
-#endif /* MODULE */
-/*
- * Local variables:
- *  compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -I/usr/src/linux/drivers/net -Wall -Wstrict-prototypes -O6 -m486 -c eexpress.c"
- *  version-control: t
- *  kept-new-versions: 5
- *  tab-width: 4
- * End:
- */
+#endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this