diff options
author | Thomas Bushnell <thomas@gnu.org> | 1999-04-26 05:58:44 +0000 |
---|---|---|
committer | Thomas Bushnell <thomas@gnu.org> | 1999-04-26 05:58:44 +0000 |
commit | 86297c41a26f18d924e64fc93321c59cbc4c48dd (patch) | |
tree | 376954c6b95b735d361875319a1a2a9db6a27527 /linux/src/drivers/net/eexpress.c | |
parent | 851137902d3e7ad87af177487df3eea53e940a1c (diff) | |
download | gnumach-86297c41a26f18d924e64fc93321c59cbc4c48dd.tar.gz gnumach-86297c41a26f18d924e64fc93321c59cbc4c48dd.tar.bz2 gnumach-86297c41a26f18d924e64fc93321c59cbc4c48dd.zip |
1998-11-30 OKUJI Yoshinori <okuji@kuicr.kyoto-u.ac.jp>
Clean up linux emulation code to make it architecture-independent
as much as possible.
* linux: Renamed from linuxdev.
* Makefile.in (objfiles): Add linux.o instead of linuxdev.o.
(MAKE): New variable. Used for the linux.o target.
* configure.in: Add AC_CHECK_TOOL(MAKE, make).
* i386/i386/spl.h: Include <i386/ipl.h>, for compatibility with
OSF Mach 3.0. Suggested by Elgin Lee <ehl@funghi.com>.
* linux/src: Renamed from linux/linux.
* linux/dev: Renamed from linux/mach.
* linux/Drivers.in (AC_INIT): Use dev/include/linux/autoconf.h,
instead of mach/include/linux/autoconf.h.
* Makefile.in (all): Target ../linux.o instead of ../linuxdev.o.
* linux/dev/drivers/block/genhd.c: Include <machine/spl.h> instead
of <i386/ipl.h>.
* linux/dev/drivers/net/auto_irq.c: Remove unneeded header files,
<i386/ipl.h> and <i386/pic.h>.
* linux/dev/init/main.c: Many i386-dependent codes moved to ...
* linux/dev/arch/i386/irq.c: ... here.
* linux/dev/arch/i386/setup.c: New file.
* linux/dev/arch/i386/linux_emul.h: Likewise.
* linux/dev/arch/i386/glue/timer.c: Merged into sched.c.
* linux/dev/arch/i386/glue/sched.c: Include <machine/spl.h> instead
of <i386/ipl.h>, and moved to ...
* linux/dev/kernel/sched.c: ... here.
* linux/dev/arch/i386/glue/block.c: Include <machine/spl.h> and
<linux_emul.h>, instead of i386-dependent header files, and
moved to ...
* linux/dev/glue/blocl.c: ... here.
* linux/dev/arch/i386/glue/net.c: Include <machine/spl.h> and
<linux_emul.h>, instead of i386-dependent header files, and
moved to ...
* linux/dev/glue/net.c: ... here.
* linux/dev/arch/i386/glue/misc.c: Remove `x86' and moved to ...
* linux/dev/glue/misc.c: ... here.
* linux/dev/arch/i386/glue/kmem.c: Moved to ...
* linux/dev/glue/kmem.c: ... here.
Diffstat (limited to 'linux/src/drivers/net/eexpress.c')
-rw-r--r-- | linux/src/drivers/net/eexpress.c | 1285 |
1 files changed, 1285 insertions, 0 deletions
diff --git a/linux/src/drivers/net/eexpress.c b/linux/src/drivers/net/eexpress.c new file mode 100644 index 00000000..d7065509 --- /dev/null +++ b/linux/src/drivers/net/eexpress.c @@ -0,0 +1,1285 @@ +/* $Id: eexpress.c,v 1.1 1999/04/26 05:52:09 tb Exp $ + * + * 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> + * More fixes by Philip Blundell <pjb27@cam.ac.uk> + * Added the Compaq LTE Alan Cox <alan@redhat.com> + * + * Note - this driver is experimental still - it has problems on faster + * machines. Someone needs to sit down and go through it line by line with + * a databook... + */ + +/* + * The original EtherExpress driver was just about usable, 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 + * definite card bug, since Intel's own driver for DOS does exactly the + * same. + * + * This bug makes switching in and out of promiscuous mode a risky + * business, since we must do a 586 reset each time. + */ + +/* + * 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.10 04-May-95 John Sullivan <js10039@cam.ac.uk>\n" +" v0.14 19-May-96 Philip Blundell <phil@tazenda.demon.co.uk>\n" +" v0.15 04-Aug-98 Alan Cox <alan@redhat.com>\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/string.h> +#include <linux/in.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/delay.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/malloc.h> + +/* + * 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 + */ + +#ifndef NET_DEBUG +#define NET_DEBUG 4 +#endif +static unsigned int net_debug = NET_DEBUG; + +#undef F_DEB + +#include "eth82586.h" + +#define PRIV(x) ((struct net_local *)(x)->priv) +#define EEXP_IO_EXTENT 16 + +/* + * Private data declarations + */ + +struct net_local +{ + struct enet_statistics stats; + 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 */ + unsigned char started; + unsigned char promisc; + unsigned short rx_buf_start; + unsigned short rx_buf_end; + unsigned short num_tx_bufs; + unsigned short num_rx_bufs; +}; + +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, /* offset,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, + + 0x0000 + +}; + +#define CONF_LINK 0x0020 +#define CONF_HW_ADDR 0x0038 + +/* 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 }; + +/* + * Prototypes for Linux interface + */ + +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); + +static void eexp_irq (int irq, void *dev_addr, struct pt_regs *regs); +static void eexp_set_multicast(struct device *dev); + +/* + * Prototypes for hardware access functions + */ + +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_init586 (struct device *dev); +static void eexp_hw_ASICrst (struct device *dev); + +/* + * Linux interface + */ + +/* + * checks for presence of EtherExpress card + */ + +int express_probe(struct device *dev) +{ + unsigned short *port,ports[] = { 0x0300,0x0270,0x0320,0x0340,0 }; + unsigned short ioaddr = dev->base_addr; + + if (ioaddr&0xfe00) + return eexp_hw_probe(dev,ioaddr); + else if (ioaddr) + return ENXIO; + + 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; +} + +/* + * open and initialize the adapter, ready for use + */ + +static int eexp_open(struct device *dev) +{ + int irq = dev->irq; + unsigned short ioaddr = dev->base_addr; + +#if NET_DEBUG > 6 + printk(KERN_DEBUG "%s: eexp_open()\n", dev->name); +#endif + + if (!irq || !irqrmap[irq]) + return -ENXIO; + + if (irq2dev_map[irq] || + /* more consistent, surely? */ + ((irq2dev_map[irq]=dev),0) || + request_irq(irq,&eexp_irq,0,"eexpress",NULL)) + return -EAGAIN; + + request_region(ioaddr, EEXP_IO_EXTENT, "eexpress"); + dev->tbusy = 0; + dev->interrupt = 0; + eexp_hw_init586(dev); + dev->start = 1; + MOD_INC_USE_COUNT; +#if NET_DEBUG > 6 + printk(KERN_DEBUG "%s: leaving eexp_open()\n", dev->name); +#endif + return 0; +} + +/* + * 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); + PRIV(dev)->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); + MOD_DEC_USE_COUNT; + 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; + unsigned short ioaddr = dev->base_addr; + +#if NET_DEBUG > 6 + printk(KERN_DEBUG "%s: eexp_xmit()\n", dev->name); +#endif + + 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 *)&PRIV(dev)->started)) + { + if ((jiffies - dev->trans_start)>5) + { + if (lp->tx_link==lp->last_tx_restart) + { + unsigned short boguscount=200,rsst; + printk(KERN_WARNING "%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(KERN_WARNING "%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(KERN_WARNING "%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(KERN_WARNING "%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(KERN_WARNING "%s: i82586 startup timed out, status %04x, resetting...\n", + dev->name, status); + eexp_hw_init586(dev); + dev->tbusy = 0; + mark_bh(NET_BH); + } + } + } + + if (buf==NULL) + { + unsigned short status = inw(ioaddr+SCB_STATUS); + unsigned short txstatus = eexp_hw_lasttxstat(dev); + if (SCB_CUdead(status)) + { + printk(KERN_WARNING "%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); + dev_kfree_skb(buf, FREE_WRITE); + return 0; + } + + if (set_bit(0,(void *)&dev->tbusy)) + { + lp->stats.tx_dropped++; + } + else + { + unsigned short length = (ETH_ZLEN < buf->len) ? buf->len : ETH_ZLEN; + unsigned short *data = (unsigned short *)buf->data; + + 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; +} + +/* + * 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 = irq2dev_map[irq]; + struct net_local *lp; + unsigned short ioaddr,status,ack_cmd; + unsigned short old_rp,old_wp; + + if (dev==NULL) + { + printk(KERN_WARNING "net_interrupt(): irq %d for unknown device caught by EExpress\n",irq); + return; + } + +#if NET_DEBUG > 6 + printk(KERN_DEBUG "%s: interrupt\n", dev->name); +#endif + + 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 (PRIV(dev)->started==0 && SCB_complete(status)) + { +#if NET_DEBUG > 4 + printk(KERN_DEBUG "%s: SCBcomplete event received\n", dev->name); +#endif + while (SCB_CUstat(status)==2) + status = inw_p(ioaddr+SCB_STATUS); +#if NET_DEBUG > 4 + printk(KERN_DEBUG "%s: CU went non-active (status = %08x)\n", dev->name, status); +#endif + PRIV(dev)->started=1; + outw_p(lp->tx_link,ioaddr+SCB_CBL); + outw_p(PRIV(dev)->rx_buf_start,ioaddr+SCB_RFA); + ack_cmd |= SCB_CUstart | SCB_RUstart; + } + else if (PRIV(dev)->started) + { + unsigned short txstatus; + txstatus = eexp_hw_lasttxstat(dev); + } + + if (SCB_rxdframe(status)) + { + eexp_hw_rx(dev); + } + + if ((PRIV(dev)->started&2)!=0 && SCB_RUstat(status)!=4) + { + printk(KERN_WARNING "%s: RU stopped status %04x, restarting...\n", + dev->name,status); + lp->stats.rx_errors++; + eexp_hw_rxinit(dev); + outw(PRIV(dev)->rx_buf_start,ioaddr+SCB_RFA); + ack_cmd |= SCB_RUstart; + } + else if (PRIV(dev)->started==1 && SCB_RUstat(status)==4) + PRIV(dev)->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); + dev->interrupt = 0; +#if NET_DEBUG > 6 + printk(KERN_DEBUG "%s: leaving eexp_irq()\n", dev->name); +#endif + 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 = lp->num_rx_bufs; + +#if NET_DEBUG > 6 + printk(KERN_DEBUG "%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(KERN_WARNING "%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); + 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(KERN_WARNING "%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); + } + rx_block = rx_next; + } + outw(old_rp,ioaddr+READ_PTR); + outw(old_wp,ioaddr+WRITE_PTR); +} + +/* + * 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 + */ + +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+((lp->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); +} + +/* + * 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, in case someone has 2 + * differently configured cards in their box (Arghhh!) + */ + +static int eexp_hw_probe(struct device *dev, unsigned short ioaddr) +{ + unsigned short hw_addr[3]; + int i; + unsigned char *chw_addr = (unsigned char *)hw_addr; + + printk("%s: EtherExpress at %#x, ",dev->name,ioaddr); + + hw_addr[0] = eexp_hw_readeeprom(ioaddr,2); + hw_addr[1] = eexp_hw_readeeprom(ioaddr,3); + hw_addr[2] = eexp_hw_readeeprom(ioaddr,4); + + /* Standard Address or Compaq LTE Address */ + if (!((hw_addr[2]==0x00aa && ((hw_addr[1] & 0xff00)==0x0000)) || + (hw_addr[2]==0x0080 && ((hw_addr[1] & 0xff00)==0x5F00)))) + { + printk("rejected: invalid address %04x%04x%04x\n", + hw_addr[2],hw_addr[1],hw_addr[0]); + return -ENODEV; + } + + dev->base_addr = ioaddr; + for ( i=0 ; i<6 ; i++ ) + dev->dev_addr[i] = chw_addr[5-i]; + + { + 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); + + dev->irq = irqmap[setupval>>13]; + dev->if_port = !(setupval & 0x1000) ? AUI : + eexp_hw_readeeprom(ioaddr,5) & 0x1 ? TP : BNC; + + printk("IRQ %d, Interface %s, ",dev->irq,ifmap[dev->if_port]); + + outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ); + outb(0,ioaddr+SET_IRQ); + } + + dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); + if (!dev->priv) + return -ENOMEM; + + memset(dev->priv, 0, sizeof(struct net_local)); + + eexp_hw_ASICrst(dev); + + { + 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"); + PRIV(dev)->rx_buf_end = 0x7ff6; + PRIV(dev)->num_tx_bufs = 4; + } + else + { + printk("64k\n"); + PRIV(dev)->num_tx_bufs = 8; + PRIV(dev)->rx_buf_start = TX_BUF_START + (PRIV(dev)->num_tx_bufs*TX_BUF_SIZE); + PRIV(dev)->rx_buf_end = 0xfff6; + } + + 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); + } + + if (net_debug) + printk(version); + dev->open = eexp_open; + dev->stop = eexp_close; + dev->hard_start_xmit = eexp_xmit; + dev->get_stats = eexp_stats; + dev->set_multicast_list = &eexp_set_multicast; + ether_setup(dev); + return 0; +} + +/* + * 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; + + 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(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(); + } + wval &= ~EC_CS; + outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl); + eeprom_delay(); + outb(wval,ioaddr+EEPROM_Ctrl); + eeprom_delay(); + return rval; +} + +/* + * 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; + 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; + + 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+((lp->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); + + lp->tx_link = lp->tx_tail + 0x08; + outw(old_rp,ioaddr+READ_PTR); + outw(old_wp,ioaddr+WRITE_PTR); + + return status; +} + +/* + * This should never happen. It is called when some higher + * routine detects the CU has stopped, to try to 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); + + { + unsigned short boguscount=50,failcount=5; + while (!inw(ioaddr+SCB_STATUS)) + { + if (!--boguscount) + { + if (--failcount) + { + printk(KERN_WARNING "%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(KERN_WARNING "%s: Failed to restart CU, resetting board...\n",dev->name); + eexp_hw_init586(dev); + dev->tbusy = 0; + mark_bh(NET_BH); + return; + } + } + } + } +} + +/* + * Writes down the list of transmit buffers into card + * memory. Initial separate, 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; + unsigned short ioaddr = dev->base_addr; + unsigned short old_wp = inw(ioaddr+WRITE_PTR); + unsigned short tx_block = TX_BUF_START; + unsigned short curtbuf; + + for ( curtbuf=0 ; curtbuf<lp->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; + lp->rx_buf_start = tx_block; + outw(old_wp,ioaddr+WRITE_PTR); +} + +/* 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; + unsigned short ioaddr = dev->base_addr; + unsigned short old_wp = inw(ioaddr+WRITE_PTR); + unsigned short rx_block = lp->rx_buf_start; + + lp->num_rx_bufs = 0; + lp->rx_first = rx_block; + do + { + lp->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 <= lp->rx_buf_end-RX_BUF_SIZE); + + outw(lp->rx_last+4,ioaddr+WRITE_PTR); + outw(lp->rx_first,ioaddr); + + outw(old_wp,ioaddr+WRITE_PTR); +} + +/* + * 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; + unsigned short ioaddr = dev->base_addr; + +#if NET_DEBUG > 6 + printk("%s: eexp_hw_init586()\n", dev->name); +#endif + + lp->started = 0; + set_loopback; + + outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ); + outb_p(i586_RST,ioaddr+EEPROM_Ctrl); + udelay(2000); /* delay 20ms */ + { + unsigned long ofs; + for (ofs = 0; ofs < lp->rx_buf_end; ofs += 32) { + unsigned long i; + outw_p(ofs, ioaddr+SM_PTR); + for (i = 0; i < 16; i++) { + outw_p(0, ioaddr+SM_ADDR(i<<1)); + } + } + } + + outw_p(lp->rx_buf_end,ioaddr+WRITE_PTR); + start_code[28] = (dev->flags & IFF_PROMISC)?(start_code[28] | 1):(start_code[28] & ~1); + lp->promisc = dev->flags & IFF_PROMISC; + /* We may die here */ + 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(KERN_WARNING "%s: i82586 reset timed out, kicking...\n", + dev->name); + outw(0,ioaddr+SCB_CMD); + outb(0,ioaddr+SIGNAL_CA); + rboguscount = 100; + if (!--rfailcount) + { + printk(KERN_WARNING "%s: i82586 not responding, giving up.\n", + dev->name); + return; + } + } + } + } + + 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(KERN_WARNING "%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(KERN_WARNING "%s: Failed to initialize i82586, giving up.\n",dev->name); + return; + } + } + } + } + + outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ); + clear_loopback; + lp->init_time = jiffies; +#if NET_DEBUG > 6 + printk("%s: leaving eexp_hw_init586()\n", dev->name); +#endif + 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. + */ + +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); + + PRIV(dev)->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); + } + } + } + outb(i586_RST,ioaddr+EEPROM_Ctrl); +} + + +/* + * Set or clear the multicast filter for this adaptor. + * We have to do a complete 586 restart for this to take effect. + * At the moment only promiscuous mode is supported. + */ +static void +eexp_set_multicast(struct device *dev) +{ + if ((dev->flags & IFF_PROMISC) != PRIV(dev)->promisc) + eexp_hw_init586(dev); +} + + +/* + * MODULE stuff + */ +#ifdef MODULE + +#define EEXP_MAX_CARDS 4 /* max number of cards to support */ +#define NAMELEN 8 /* max length of dev->name (inc null) */ + +static char namelist[NAMELEN * EEXP_MAX_CARDS] = { 0, }; + +static struct device dev_eexp[EEXP_MAX_CARDS] = +{ + { NULL, /* will allocate dynamically */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, express_probe }, +}; + +int irq[EEXP_MAX_CARDS] = {0, }; +int io[EEXP_MAX_CARDS] = {0, }; + +/* Ideally the user would give us io=, irq= for every card. If any parameters + * are specified, we verify and then use them. If no parameters are given, we + * autoprobe for one card only. + */ +int init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) { + struct device *dev = &dev_eexp[this_dev]; + dev->name = namelist + (NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + if (io[this_dev] == 0) { + if (this_dev) break; + printk(KERN_NOTICE "eexpress.c: Module autoprobe not recommended, give io=xx.\n"); + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "eexpress.c: Failed to register card at 0x%x.\n", io[this_dev]); + if (found != 0) return 0; + return -ENXIO; + } + found++; + } + return 0; +} + +void cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) { + struct device *dev = &dev_eexp[this_dev]; + if (dev->priv != NULL) { + kfree(dev->priv); + dev->priv = NULL; + release_region(dev->base_addr, EEXP_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif + +/* + * Local Variables: + * c-file-style: "linux" + * tab-width: 8 + * compile-command: "gcc -D__KERNEL__ -I/discs/bibble/src/linux-1.3.69/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strength-reduce -pipe -m486 -DCPU=486 -DMODULE -c 3c505.c" + * End: + */ |