diff options
Diffstat (limited to 'linux/src/drivers/net')
69 files changed, 71314 insertions, 0 deletions
diff --git a/linux/src/drivers/net/3c501.c b/linux/src/drivers/net/3c501.c new file mode 100644 index 00000000..200b95c7 --- /dev/null +++ b/linux/src/drivers/net/3c501.c @@ -0,0 +1,856 @@ +/* 3c501.c: A 3Com 3c501 ethernet driver for linux. */ +/* + Written 1992,1993,1994 Donald Becker + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU Public License, + incorporated herein by reference. + + This is a device driver for the 3Com Etherlink 3c501. + Do not purchase this card, even as a joke. It's performance is horrible, + and it breaks in many ways. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Fixed (again!) the missing interrupt locking on TX/RX shifting. + Alan Cox <Alan.Cox@linux.org> + + Removed calls to init_etherdev since they are no longer needed, and + cleaned up modularization just a bit. The driver still allows only + the default address for cards when loaded as a module, but that's + really less braindead than anyone using a 3c501 board. :) + 19950208 (invid@msen.com) + + Added traps for interrupts hitting the window as we clear and TX load + the board. Now getting 150K/second FTP with a 3c501 card. Still playing + with a TX-TX optimisation to see if we can touch 180-200K/second as seems + theoretically maximum. + 19950402 Alan Cox <Alan.Cox@linux.org> + + Some notes on this thing if you have to hack it. [Alan] + + 1] Some documentation is available from 3Com. Due to the boards age + standard responses when you ask for this will range from 'be serious' + to 'give it to a museum'. The documentation is incomplete and mostly + of historical interest anyway. + + 2] The basic system is a single buffer which can be used to receive or + transmit a packet. A third command mode exists when you are setting + things up. + + 3] If it's transmitting it's not receiving and vice versa. In fact the + time to get the board back into useful state after an operation is + quite large. + + 4] The driver works by keeping the board in receive mode waiting for a + packet to arrive. When one arrives it is copied out of the buffer + and delivered to the kernel. The card is reloaded and off we go. + + 5] When transmitting dev->tbusy is set and the card is reset (from + receive mode) [possibly losing a packet just received] to command + mode. A packet is loaded and transmit mode triggered. The interrupt + handler runs different code for transmit interrupts and can handle + returning to receive mode or retransmissions (yes you have to help + out with those too). + + Problems: + There are a wide variety of undocumented error returns from the card + and you basically have to kick the board and pray if they turn up. Most + only occur under extreme load or if you do something the board doesn't + like (eg touching a register at the wrong time). + + The driver is less efficient than it could be. It switches through + receive mode even if more transmits are queued. If this worries you buy + a real ethernet card. + + The combination of slow receive restart and no real multicast + filter makes the board unusable with a kernel compiled for IP + multicasting in a real multicast environment. That's down to the board, + but even with no multicast programs running a multicast IP kernel is + in group 224.0.0.1 and you will therefore be listening to all multicasts. + One nv conference running over that ethernet and you can give up. + +*/ + +static const char *version = + "3c501.c: 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov).\n"; + +/* + * Braindamage remaining: + * The 3c501 board. + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/fcntl.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/config.h> /* for CONFIG_IP_MULTICAST */ + +#include <asm/bitops.h> +#include <asm/io.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#define BLOCKOUT_2 + +/* A zero-terminated list of I/O addresses to be probed. + The 3c501 can be at many locations, but here are the popular ones. */ +static unsigned int netcard_portlist[] = + { 0x280, 0x300, 0}; + + +/* + * Index to functions. + */ + +int el1_probe(struct device *dev); +static int el1_probe1(struct device *dev, int ioaddr); +static int el_open(struct device *dev); +static int el_start_xmit(struct sk_buff *skb, struct device *dev); +static void el_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void el_receive(struct device *dev); +static void el_reset(struct device *dev); +static int el1_close(struct device *dev); +static struct enet_statistics *el1_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev); + +#define EL1_IO_EXTENT 16 + +#ifndef EL_DEBUG +#define EL_DEBUG 0 /* use 0 for production, 1 for devel., >2 for debug */ +#endif /* Anything above 5 is wordy death! */ +static int el_debug = EL_DEBUG; + +/* + * Board-specific info in dev->priv. + */ + +struct net_local +{ + struct enet_statistics stats; + int tx_pkt_start; /* The length of the current Tx packet. */ + int collisions; /* Tx collisions this packet */ + int loading; /* Spot buffer load collisions */ +}; + + +#define RX_STATUS (ioaddr + 0x06) +#define RX_CMD RX_STATUS +#define TX_STATUS (ioaddr + 0x07) +#define TX_CMD TX_STATUS +#define GP_LOW (ioaddr + 0x08) +#define GP_HIGH (ioaddr + 0x09) +#define RX_BUF_CLR (ioaddr + 0x0A) +#define RX_LOW (ioaddr + 0x0A) +#define RX_HIGH (ioaddr + 0x0B) +#define SAPROM (ioaddr + 0x0C) +#define AX_STATUS (ioaddr + 0x0E) +#define AX_CMD AX_STATUS +#define DATAPORT (ioaddr + 0x0F) +#define TX_RDY 0x08 /* In TX_STATUS */ + +#define EL1_DATAPTR 0x08 +#define EL1_RXPTR 0x0A +#define EL1_SAPROM 0x0C +#define EL1_DATAPORT 0x0f + +/* + * Writes to the ax command register. + */ + +#define AX_OFF 0x00 /* Irq off, buffer access on */ +#define AX_SYS 0x40 /* Load the buffer */ +#define AX_XMIT 0x44 /* Transmit a packet */ +#define AX_RX 0x48 /* Receive a packet */ +#define AX_LOOP 0x0C /* Loopback mode */ +#define AX_RESET 0x80 + +/* + * Normal receive mode written to RX_STATUS. We must intr on short packets + * to avoid bogus rx lockups. + */ + +#define RX_NORM 0xA8 /* 0x68 == all addrs, 0xA8 only to me. */ +#define RX_PROM 0x68 /* Senior Prom, uhmm promiscuous mode. */ +#define RX_MULT 0xE8 /* Accept multicast packets. */ +#define TX_NORM 0x0A /* Interrupt on everything that might hang the chip */ + +/* + * TX_STATUS register. + */ + +#define TX_COLLISION 0x02 +#define TX_16COLLISIONS 0x04 +#define TX_READY 0x08 + +#define RX_RUNT 0x08 +#define RX_MISSED 0x01 /* Missed a packet due to 3c501 braindamage. */ +#define RX_GOOD 0x30 /* Good packet 0x20, or simple overflow 0x10. */ + + +/* + * The boilerplate probe code. + */ + +#ifdef HAVE_DEVLIST +struct netdev_entry el1_drv = {"3c501", el1_probe1, EL1_IO_EXTENT, netcard_portlist}; +#else + +int el1_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return el1_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; netcard_portlist[i]; i++) + { + int ioaddr = netcard_portlist[i]; + if (check_region(ioaddr, EL1_IO_EXTENT)) + continue; + if (el1_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +/* + * The actual probe. + */ + +static int el1_probe1(struct device *dev, int ioaddr) +{ + const char *mname; /* Vendor name */ + unsigned char station_addr[6]; + int autoirq = 0; + int i; + + /* + * Read the station address PROM data from the special port. + */ + + for (i = 0; i < 6; i++) + { + outw(i, ioaddr + EL1_DATAPTR); + station_addr[i] = inb(ioaddr + EL1_SAPROM); + } + /* + * Check the first three octets of the S.A. for 3Com's prefix, or + * for the Sager NP943 prefix. + */ + + if (station_addr[0] == 0x02 && station_addr[1] == 0x60 + && station_addr[2] == 0x8c) + { + mname = "3c501"; + } else if (station_addr[0] == 0x00 && station_addr[1] == 0x80 + && station_addr[2] == 0xC8) + { + mname = "NP943"; + } + else + return ENODEV; + + /* + * Grab the region so we can find the another board if autoIRQ fails. + */ + + request_region(ioaddr, EL1_IO_EXTENT,"3c501"); + + /* + * We auto-IRQ by shutting off the interrupt line and letting it float + * high. + */ + + if (dev->irq < 2) + { + autoirq_setup(2); + inb(RX_STATUS); /* Clear pending interrupts. */ + inb(TX_STATUS); + outb(AX_LOOP + 1, AX_CMD); + + outb(0x00, AX_CMD); + + autoirq = autoirq_report(1); + + if (autoirq == 0) + { + printk("%s probe at %#x failed to detect IRQ line.\n", + mname, ioaddr); + return EAGAIN; + } + } + + outb(AX_RESET+AX_LOOP, AX_CMD); /* Loopback mode. */ + dev->base_addr = ioaddr; + memcpy(dev->dev_addr, station_addr, ETH_ALEN); + + if (dev->mem_start & 0xf) + el_debug = dev->mem_start & 0x7; + if (autoirq) + dev->irq = autoirq; + + printk("%s: %s EtherLink at %#lx, using %sIRQ %d.\n", dev->name, mname, dev->base_addr, + autoirq ? "auto":"assigned ", dev->irq); + +#ifdef CONFIG_IP_MULTICAST + printk("WARNING: Use of the 3c501 in a multicast kernel is NOT recommended.\n"); +#endif + + if (el_debug) + printk("%s", version); + + /* + * 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)); + + /* + * The EL1-specific entries in the device structure. + */ + + dev->open = &el_open; + dev->hard_start_xmit = &el_start_xmit; + dev->stop = &el1_close; + dev->get_stats = &el1_get_stats; + dev->set_multicast_list = &set_multicast_list; + + /* + * Setup the generic properties + */ + + ether_setup(dev); + + return 0; +} + +/* + * Open/initialize the board. + */ + +static int el_open(struct device *dev) +{ + int ioaddr = dev->base_addr; + + if (el_debug > 2) + printk("%s: Doing el_open()...", dev->name); + + if (request_irq(dev->irq, &el_interrupt, 0, "3c501", NULL)) + return -EAGAIN; + + irq2dev_map[dev->irq] = dev; + el_reset(dev); + + dev->start = 1; + + outb(AX_RX, AX_CMD); /* Aux control, irq and receive enabled */ + MOD_INC_USE_COUNT; + return 0; +} + +static int el_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + unsigned long flags; + + if(dev->interrupt) /* May be unloading, don't stamp on */ + return 1; /* the packet buffer this time */ + + if (dev->tbusy) + { + if (jiffies - dev->trans_start < 20) + { + if (el_debug > 2) + printk(" transmitter busy, deferred.\n"); + return 1; + } + if (el_debug) + printk ("%s: transmit timed out, txsr %#2x axsr=%02x rxsr=%02x.\n", + dev->name, inb(TX_STATUS), inb(AX_STATUS), inb(RX_STATUS)); + lp->stats.tx_errors++; + outb(TX_NORM, TX_CMD); + outb(RX_NORM, RX_CMD); + outb(AX_OFF, AX_CMD); /* Just trigger a false interrupt. */ + outb(AX_RX, AX_CMD); /* Aux control, irq and receive enabled */ + dev->tbusy = 0; + dev->trans_start = jiffies; + } + + if (skb == NULL) + { + dev_tint(dev); + return 0; + } + + save_flags(flags); + + /* + * Avoid incoming interrupts between us flipping tbusy and flipping + * mode as the driver assumes tbusy is a faithful indicator of card + * state + */ + + cli(); + + /* + * Avoid timer-based retransmission conflicts. + */ + + if (set_bit(0, (void*)&dev->tbusy) != 0) + { + restore_flags(flags); + printk("%s: Transmitter access conflict.\n", dev->name); + } + else + { + int gp_start = 0x800 - (ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN); + unsigned char *buf = skb->data; + +load_it_again_sam: + lp->tx_pkt_start = gp_start; + lp->collisions = 0; + + /* + * Command mode with status cleared should [in theory] + * mean no more interrupts can be pending on the card. + */ + +#ifdef BLOCKOUT_1 + disable_irq(dev->irq); +#endif + outb_p(AX_SYS, AX_CMD); + inb_p(RX_STATUS); + inb_p(TX_STATUS); + + lp->loading=1; + + /* + * Turn interrupts back on while we spend a pleasant afternoon + * loading bytes into the board + */ + + restore_flags(flags); + outw(0x00, RX_BUF_CLR); /* Set rx packet area to 0. */ + outw(gp_start, GP_LOW); /* aim - packet will be loaded into buffer start */ + outsb(DATAPORT,buf,skb->len); /* load buffer (usual thing each byte increments the pointer) */ + outw(gp_start, GP_LOW); /* the board reuses the same register */ +#ifndef BLOCKOUT_1 + if(lp->loading==2) /* A receive upset our load, despite our best efforts */ + { + if(el_debug>2) + printk("%s: burped during tx load.\n", dev->name); + goto load_it_again_sam; /* Sigh... */ + } +#endif + outb(AX_XMIT, AX_CMD); /* fire ... Trigger xmit. */ + lp->loading=0; +#ifdef BLOCKOUT_1 + enable_irq(dev->irq); +#endif + dev->trans_start = jiffies; + } + + if (el_debug > 2) + printk(" queued xmit.\n"); + dev_kfree_skb (skb, FREE_WRITE); + return 0; +} + + +/* + * The typical workload of the driver: + * Handle the ether interface interrupts. + */ + +static void el_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct net_local *lp; + int ioaddr; + int axsr; /* Aux. status reg. */ + + if (dev == NULL || dev->irq != irq) + { + printk ("3c501 driver: irq %d for unknown device.\n", irq); + return; + } + + ioaddr = dev->base_addr; + lp = (struct net_local *)dev->priv; + + /* + * What happened ? + */ + + axsr = inb(AX_STATUS); + + /* + * Log it + */ + + if (el_debug > 3) + printk("%s: el_interrupt() aux=%#02x", dev->name, axsr); + if (dev->interrupt) + printk("%s: Reentering the interrupt driver!\n", dev->name); + dev->interrupt = 1; +#ifndef BLOCKOUT_1 + if(lp->loading==1 && !dev->tbusy) + printk("%s: Inconsistent state loading while not in tx\n", + dev->name); +#endif +#ifdef BLOCKOUT_3 + lp->loading=2; /* So we can spot loading interruptions */ +#endif + + if (dev->tbusy) + { + + /* + * Board in transmit mode. May be loading. If we are + * loading we shouldn't have got this. + */ + + int txsr = inb(TX_STATUS); +#ifdef BLOCKOUT_2 + if(lp->loading==1) + { + if(el_debug > 2) + { + printk("%s: Interrupt while loading [", dev->name); + printk(" txsr=%02x gp=%04x rp=%04x]\n", txsr, inw(GP_LOW),inw(RX_LOW)); + } + lp->loading=2; /* Force a reload */ + dev->interrupt = 0; + return; + } +#endif + if (el_debug > 6) + printk(" txsr=%02x gp=%04x rp=%04x", txsr, inw(GP_LOW),inw(RX_LOW)); + + if ((axsr & 0x80) && (txsr & TX_READY) == 0) + { + /* + * FIXME: is there a logic to whether to keep on trying or + * reset immediately ? + */ + if(el_debug>1) + printk("%s: Unusual interrupt during Tx, txsr=%02x axsr=%02x" + " gp=%03x rp=%03x.\n", dev->name, txsr, axsr, + inw(ioaddr + EL1_DATAPTR), inw(ioaddr + EL1_RXPTR)); + dev->tbusy = 0; + mark_bh(NET_BH); + } + else if (txsr & TX_16COLLISIONS) + { + /* + * Timed out + */ + if (el_debug) + printk("%s: Transmit failed 16 times, ethernet jammed?\n",dev->name); + outb(AX_SYS, AX_CMD); + lp->stats.tx_aborted_errors++; + } + else if (txsr & TX_COLLISION) + { + /* + * Retrigger xmit. + */ + + if (el_debug > 6) + printk(" retransmitting after a collision.\n"); + /* + * Poor little chip can't reset its own start pointer + */ + + outb(AX_SYS, AX_CMD); + outw(lp->tx_pkt_start, GP_LOW); + outb(AX_XMIT, AX_CMD); + lp->stats.collisions++; + dev->interrupt = 0; + return; + } + else + { + /* + * It worked.. we will now fall through and receive + */ + lp->stats.tx_packets++; + if (el_debug > 6) + printk(" Tx succeeded %s\n", + (txsr & TX_RDY) ? "." : "but tx is busy!"); + /* + * This is safe the interrupt is atomic WRT itself. + */ + + dev->tbusy = 0; + mark_bh(NET_BH); /* In case more to transmit */ + } + } + else + { + /* + * In receive mode. + */ + + int rxsr = inb(RX_STATUS); + if (el_debug > 5) + printk(" rxsr=%02x txsr=%02x rp=%04x", rxsr, inb(TX_STATUS),inw(RX_LOW)); + /* + * Just reading rx_status fixes most errors. + */ + if (rxsr & RX_MISSED) + lp->stats.rx_missed_errors++; + else if (rxsr & RX_RUNT) + { /* Handled to avoid board lock-up. */ + lp->stats.rx_length_errors++; + if (el_debug > 5) + printk(" runt.\n"); + } + else if (rxsr & RX_GOOD) + { + /* + * Receive worked. + */ + el_receive(dev); + } + else + { + /* + * Nothing? Something is broken! + */ + if (el_debug > 2) + printk("%s: No packet seen, rxsr=%02x **resetting 3c501***\n", + dev->name, rxsr); + el_reset(dev); + } + if (el_debug > 3) + printk(".\n"); + } + + /* + * Move into receive mode + */ + + outb(AX_RX, AX_CMD); + outw(0x00, RX_BUF_CLR); + inb(RX_STATUS); /* Be certain that interrupts are cleared. */ + inb(TX_STATUS); + dev->interrupt = 0; + return; +} + + +/* + * We have a good packet. Well, not really "good", just mostly not broken. + * We must check everything to see if it is good. + */ + +static void el_receive(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + int pkt_len; + struct sk_buff *skb; + + pkt_len = inw(RX_LOW); + + if (el_debug > 4) + printk(" el_receive %d.\n", pkt_len); + + if ((pkt_len < 60) || (pkt_len > 1536)) + { + if (el_debug) + printk("%s: bogus packet, length=%d\n", dev->name, pkt_len); + lp->stats.rx_over_errors++; + return; + } + + /* + * Command mode so we can empty the buffer + */ + + outb(AX_SYS, AX_CMD); + skb = dev_alloc_skb(pkt_len+2); + + /* + * Start of frame + */ + + outw(0x00, GP_LOW); + if (skb == NULL) + { + printk("%s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + return; + } + else + { + skb_reserve(skb,2); /* Force 16 byte alignment */ + skb->dev = dev; + /* + * The read increments through the bytes. The interrupt + * handler will fix the pointer when it returns to + * receive mode. + */ + insb(DATAPORT, skb_put(skb,pkt_len), pkt_len); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + return; +} + +static void el_reset(struct device *dev) +{ + int ioaddr = dev->base_addr; + + if (el_debug> 2) + printk("3c501 reset..."); + outb(AX_RESET, AX_CMD); /* Reset the chip */ + outb(AX_LOOP, AX_CMD); /* Aux control, irq and loopback enabled */ + { + int i; + for (i = 0; i < 6; i++) /* Set the station address. */ + outb(dev->dev_addr[i], ioaddr + i); + } + + outw(0, RX_BUF_CLR); /* Set rx packet area to 0. */ + cli(); /* Avoid glitch on writes to CMD regs */ + outb(TX_NORM, TX_CMD); /* tx irq on done, collision */ + outb(RX_NORM, RX_CMD); /* Set Rx commands. */ + inb(RX_STATUS); /* Clear status. */ + inb(TX_STATUS); + dev->interrupt = 0; + dev->tbusy = 0; + sti(); +} + +static int el1_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + + if (el_debug > 2) + printk("%s: Shutting down ethercard at %#x.\n", dev->name, ioaddr); + + dev->tbusy = 1; + dev->start = 0; + + /* + * Free and disable the IRQ. + */ + + free_irq(dev->irq, NULL); + outb(AX_RESET, AX_CMD); /* Reset the chip */ + irq2dev_map[dev->irq] = 0; + + MOD_DEC_USE_COUNT; + return 0; +} + +static struct enet_statistics *el1_get_stats(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + return &lp->stats; +} + +/* + * Set or clear the multicast filter for this adaptor. + * best-effort filtering. + */ + +static void set_multicast_list(struct device *dev) +{ + int ioaddr = dev->base_addr; + + if(dev->flags&IFF_PROMISC) + { + outb(RX_PROM, RX_CMD); + inb(RX_STATUS); + } + else if (dev->mc_list || dev->flags&IFF_ALLMULTI) + { + outb(RX_MULT, RX_CMD); /* Multicast or all multicast is the same */ + inb(RX_STATUS); /* Clear status. */ + } + else + { + outb(RX_NORM, RX_CMD); + inb(RX_STATUS); + } +} + +#ifdef MODULE + +static char devicename[9] = { 0, }; + +static struct device dev_3c501 = +{ + devicename, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0x280, 5, + 0, 0, 0, NULL, el1_probe +}; + +static int io=0x280; +static int irq=5; + +int init_module(void) +{ + dev_3c501.irq=irq; + dev_3c501.base_addr=io; + if (register_netdev(&dev_3c501) != 0) + return -EIO; + return 0; +} + +void cleanup_module(void) +{ + /* + * No need to check MOD_IN_USE, as sys_delete_module() checks. + */ + + unregister_netdev(&dev_3c501); + + /* + * Free up the private structure, or leak memory :-) + */ + + kfree(dev_3c501.priv); + dev_3c501.priv = NULL; /* gets re-allocated by el1_probe1 */ + + /* + * If we don't do this, we can't re-insmod it later. + */ + release_region(dev_3c501.base_addr, EL1_IO_EXTENT); +} + +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer -m486 -c -o 3c501.o 3c501.c" + * kept-new-versions: 5 + * End: + */ diff --git a/linux/src/drivers/net/3c503.c b/linux/src/drivers/net/3c503.c new file mode 100644 index 00000000..a562db35 --- /dev/null +++ b/linux/src/drivers/net/3c503.c @@ -0,0 +1,690 @@ +/* 3c503.c: A shared-memory NS8390 ethernet driver for linux. */ +/* + Written 1992-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU Public License, + incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This driver should work with the 3c503 and 3c503/16. It should be used + in shared memory mode for best performance, although it may also work + in programmed-I/O mode. + + Sources: + EtherLink II Technical Reference Manual, + EtherLink II/16 Technical Reference Manual Supplement, + 3Com Corporation, 5400 Bayfront Plaza, Santa Clara CA 95052-8145 + + The Crynwr 3c503 packet driver. + + Changelog: + + Paul Gortmaker : add support for the 2nd 8kB of RAM on 16 bit cards. + Paul Gortmaker : multiple card support for module users. + rjohnson@analogic.com : Fix up PIO interface for efficient operation. + +*/ + +static const char *version = + "3c503.c:v1.10 9/23/93 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/byteorder.h> + +#include "8390.h" +#include "3c503.h" +#define WRD_COUNT 4 + +int el2_probe(struct device *dev); +int el2_pio_probe(struct device *dev); +int el2_probe1(struct device *dev, int ioaddr); + +/* A zero-terminated list of I/O addresses to be probed in PIO mode. */ +static unsigned int netcard_portlist[] = + { 0x300,0x310,0x330,0x350,0x250,0x280,0x2a0,0x2e0,0}; + +#define EL2_IO_EXTENT 16 + +#ifdef HAVE_DEVLIST +/* The 3c503 uses two entries, one for the safe memory-mapped probe and + the other for the typical I/O probe. */ +struct netdev_entry el2_drv = +{"3c503", el2_probe, EL1_IO_EXTENT, 0}; +struct netdev_entry el2pio_drv = +{"3c503pio", el2_pioprobe1, EL1_IO_EXTENT, netcard_portlist}; +#endif + +static int el2_open(struct device *dev); +static int el2_close(struct device *dev); +static void el2_reset_8390(struct device *dev); +static void el2_init_card(struct device *dev); +static void el2_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static void el2_block_input(struct device *dev, int count, struct sk_buff *skb, + int ring_offset); +static void el2_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); + + +/* This routine probes for a memory-mapped 3c503 board by looking for + the "location register" at the end of the jumpered boot PROM space. + This works even if a PROM isn't there. + + If the ethercard isn't found there is an optional probe for + ethercard jumpered to programmed-I/O mode. + */ +int +el2_probe(struct device *dev) +{ + int *addr, addrs[] = { 0xddffe, 0xd9ffe, 0xcdffe, 0xc9ffe, 0}; + int base_addr = dev->base_addr; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return el2_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (addr = addrs; *addr; addr++) { + int i; + unsigned int base_bits = readb(*addr); + /* Find first set bit. */ + for(i = 7; i >= 0; i--, base_bits >>= 1) + if (base_bits & 0x1) + break; + if (base_bits != 1) + continue; + if (check_region(netcard_portlist[i], EL2_IO_EXTENT)) + continue; + if (el2_probe1(dev, netcard_portlist[i]) == 0) + return 0; + } +#if ! defined(no_probe_nonshared_memory) && ! defined (HAVE_DEVLIST) + return el2_pio_probe(dev); +#else + return ENODEV; +#endif +} + +#ifndef HAVE_DEVLIST +/* Try all of the locations that aren't obviously empty. This touches + a lot of locations, and is much riskier than the code above. */ +int +el2_pio_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return el2_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; netcard_portlist[i]; i++) { + int ioaddr = netcard_portlist[i]; + if (check_region(ioaddr, EL2_IO_EXTENT)) + continue; + if (el2_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +/* Probe for the Etherlink II card at I/O port base IOADDR, + returning non-zero on success. If found, set the station + address and memory parameters in DEVICE. */ +int +el2_probe1(struct device *dev, int ioaddr) +{ + int i, iobase_reg, membase_reg, saved_406, wordlength; + static unsigned version_printed = 0; + unsigned long vendor_id; + + /* Reset and/or avoid any lurking NE2000 */ + if (inb(ioaddr + 0x408) == 0xff) { + udelay(1000); + return ENODEV; + } + + /* We verify that it's a 3C503 board by checking the first three octets + of its ethernet address. */ + iobase_reg = inb(ioaddr+0x403); + membase_reg = inb(ioaddr+0x404); + /* ASIC location registers should be 0 or have only a single bit set. */ + if ( (iobase_reg & (iobase_reg - 1)) + || (membase_reg & (membase_reg - 1))) { + return ENODEV; + } + saved_406 = inb_p(ioaddr + 0x406); + outb_p(ECNTRL_RESET|ECNTRL_THIN, ioaddr + 0x406); /* Reset it... */ + outb_p(ECNTRL_THIN, ioaddr + 0x406); + /* Map the station addr PROM into the lower I/O ports. We now check + for both the old and new 3Com prefix */ + outb(ECNTRL_SAPROM|ECNTRL_THIN, ioaddr + 0x406); + vendor_id = inb(ioaddr)*0x10000 + inb(ioaddr + 1)*0x100 + inb(ioaddr + 2); + if ((vendor_id != OLD_3COM_ID) && (vendor_id != NEW_3COM_ID)) { + /* Restore the register we frobbed. */ + outb(saved_406, ioaddr + 0x406); + return ENODEV; + } + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk("3c503.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + if (ei_debug && version_printed++ == 0) + printk(version); + + dev->base_addr = ioaddr; + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk ("3c503: unable to allocate memory for dev->priv.\n"); + return -ENOMEM; + } + + printk("%s: 3c503 at i/o base %#3x, node ", dev->name, ioaddr); + + /* Retrieve and print the ethernet address. */ + for (i = 0; i < 6; i++) + printk(" %2.2x", dev->dev_addr[i] = inb(ioaddr + i)); + + /* Map the 8390 back into the window. */ + outb(ECNTRL_THIN, ioaddr + 0x406); + + /* Check for EL2/16 as described in tech. man. */ + outb_p(E8390_PAGE0, ioaddr + E8390_CMD); + outb_p(0, ioaddr + EN0_DCFG); + outb_p(E8390_PAGE2, ioaddr + E8390_CMD); + wordlength = inb_p(ioaddr + EN0_DCFG) & ENDCFG_WTS; + outb_p(E8390_PAGE0, ioaddr + E8390_CMD); + + /* Probe for, turn on and clear the board's shared memory. */ + if (ei_debug > 2) printk(" memory jumpers %2.2x ", membase_reg); + outb(EGACFR_NORM, ioaddr + 0x405); /* Enable RAM */ + + /* This should be probed for (or set via an ioctl()) at run-time. + Right now we use a sleazy hack to pass in the interface number + at boot-time via the low bits of the mem_end field. That value is + unused, and the low bits would be discarded even if it was used. */ +#if defined(EI8390_THICK) || defined(EL2_AUI) + ei_status.interface_num = 1; +#else + ei_status.interface_num = dev->mem_end & 0xf; +#endif + printk(", using %sternal xcvr.\n", ei_status.interface_num == 0 ? "in" : "ex"); + + if ((membase_reg & 0xf0) == 0) { + dev->mem_start = 0; + ei_status.name = "3c503-PIO"; + } else { + dev->mem_start = ((membase_reg & 0xc0) ? 0xD8000 : 0xC8000) + + ((membase_reg & 0xA0) ? 0x4000 : 0); + +#define EL2_MEMSIZE (EL2_MB1_STOP_PG - EL2_MB1_START_PG)*256 +#ifdef EL2MEMTEST + /* This has never found an error, but someone might care. + Note that it only tests the 2nd 8kB on 16kB 3c503/16 + cards between card addr. 0x2000 and 0x3fff. */ + { /* Check the card's memory. */ + unsigned long mem_base = dev->mem_start; + unsigned int test_val = 0xbbadf00d; + writel(0xba5eba5e, mem_base); + for (i = sizeof(test_val); i < EL2_MEMSIZE; i+=sizeof(test_val)) { + writel(test_val, mem_base + i); + if (readl(mem_base) != 0xba5eba5e + || readl(mem_base + i) != test_val) { + printk("3c503: memory failure or memory address conflict.\n"); + dev->mem_start = 0; + ei_status.name = "3c503-PIO"; + break; + } + test_val += 0x55555555; + writel(0, mem_base + i); + } + } +#endif /* EL2MEMTEST */ + + dev->mem_end = dev->rmem_end = dev->mem_start + EL2_MEMSIZE; + + if (wordlength) { /* No Tx pages to skip over to get to Rx */ + dev->rmem_start = dev->mem_start; + ei_status.name = "3c503/16"; + } else { + dev->rmem_start = TX_PAGES*256 + dev->mem_start; + ei_status.name = "3c503"; + } + } + + /* + Divide up the memory on the card. This is the same regardless of + whether shared-mem or PIO is used. For 16 bit cards (16kB RAM), + we use the entire 8k of bank1 for an Rx ring. We only use 3k + of the bank0 for 2 full size Tx packet slots. For 8 bit cards, + (8kB RAM) we use 3kB of bank1 for two Tx slots, and the remaining + 5kB for an Rx ring. */ + + if (wordlength) { + ei_status.tx_start_page = EL2_MB0_START_PG; + ei_status.rx_start_page = EL2_MB1_START_PG; + } else { + ei_status.tx_start_page = EL2_MB1_START_PG; + ei_status.rx_start_page = EL2_MB1_START_PG + TX_PAGES; + } + + /* Finish setting the board's parameters. */ + ei_status.stop_page = EL2_MB1_STOP_PG; + ei_status.word16 = wordlength; + ei_status.reset_8390 = &el2_reset_8390; + ei_status.get_8390_hdr = &el2_get_8390_hdr; + ei_status.block_input = &el2_block_input; + ei_status.block_output = &el2_block_output; + + request_region(ioaddr, EL2_IO_EXTENT, ei_status.name); + + if (dev->irq == 2) + dev->irq = 9; + else if (dev->irq > 5 && dev->irq != 9) { + printk("3c503: configured interrupt %d invalid, will use autoIRQ.\n", + dev->irq); + dev->irq = 0; + } + + ei_status.saved_irq = dev->irq; + + dev->start = 0; + dev->open = &el2_open; + dev->stop = &el2_close; + + if (dev->mem_start) + printk("%s: %s - %dkB RAM, 8kB shared mem window at %#6lx-%#6lx.\n", + dev->name, ei_status.name, (wordlength+1)<<3, + dev->mem_start, dev->mem_end-1); + + else + { + ei_status.tx_start_page = EL2_MB1_START_PG; + ei_status.rx_start_page = EL2_MB1_START_PG + TX_PAGES; + printk("\n%s: %s, %dkB RAM, using programmed I/O (REJUMPER for SHARED MEMORY).\n", + dev->name, ei_status.name, (wordlength+1)<<3); + } + return 0; +} + +static int +el2_open(struct device *dev) +{ + + if (dev->irq < 2) { + int irqlist[] = {5, 9, 3, 4, 0}; + int *irqp = irqlist; + + outb(EGACFR_NORM, E33G_GACFR); /* Enable RAM and interrupts. */ + do { + if (request_irq (*irqp, NULL, 0, "bogus", NULL) != -EBUSY) { + /* Twinkle the interrupt, and check if it's seen. */ + autoirq_setup(0); + outb_p(0x04 << ((*irqp == 9) ? 2 : *irqp), E33G_IDCFR); + outb_p(0x00, E33G_IDCFR); + if (*irqp == autoirq_report(0) /* It's a good IRQ line! */ + && request_irq (dev->irq = *irqp, &ei_interrupt, 0, ei_status.name, NULL) == 0) + break; + } + } while (*++irqp); + if (*irqp == 0) { + outb(EGACFR_IRQOFF, E33G_GACFR); /* disable interrupts. */ + return -EAGAIN; + } + } else { + if (request_irq(dev->irq, &ei_interrupt, 0, ei_status.name, NULL)) { + return -EAGAIN; + } + } + + el2_init_card(dev); + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static int +el2_close(struct device *dev) +{ + free_irq(dev->irq, NULL); + dev->irq = ei_status.saved_irq; + irq2dev_map[dev->irq] = NULL; + outb(EGACFR_IRQOFF, E33G_GACFR); /* disable interrupts. */ + + ei_close(dev); + MOD_DEC_USE_COUNT; + return 0; +} + +/* This is called whenever we have a unrecoverable failure: + transmit timeout + Bad ring buffer packet header + */ +static void +el2_reset_8390(struct device *dev) +{ + if (ei_debug > 1) { + printk("%s: Resetting the 3c503 board...", dev->name); + printk("%#lx=%#02x %#lx=%#02x %#lx=%#02x...", E33G_IDCFR, inb(E33G_IDCFR), + E33G_CNTRL, inb(E33G_CNTRL), E33G_GACFR, inb(E33G_GACFR)); + } + outb_p(ECNTRL_RESET|ECNTRL_THIN, E33G_CNTRL); + ei_status.txing = 0; + outb_p(ei_status.interface_num==0 ? ECNTRL_THIN : ECNTRL_AUI, E33G_CNTRL); + el2_init_card(dev); + if (ei_debug > 1) printk("done\n"); +} + +/* Initialize the 3c503 GA registers after a reset. */ +static void +el2_init_card(struct device *dev) +{ + /* Unmap the station PROM and select the DIX or BNC connector. */ + outb_p(ei_status.interface_num==0 ? ECNTRL_THIN : ECNTRL_AUI, E33G_CNTRL); + + /* Set ASIC copy of rx's first and last+1 buffer pages */ + /* These must be the same as in the 8390. */ + outb(ei_status.rx_start_page, E33G_STARTPG); + outb(ei_status.stop_page, E33G_STOPPG); + + /* Point the vector pointer registers somewhere ?harmless?. */ + outb(0xff, E33G_VP2); /* Point at the ROM restart location 0xffff0 */ + outb(0xff, E33G_VP1); + outb(0x00, E33G_VP0); + /* Turn off all interrupts until we're opened. */ + outb_p(0x00, dev->base_addr + EN0_IMR); + /* Enable IRQs iff started. */ + outb(EGACFR_NORM, E33G_GACFR); + + /* Set the interrupt line. */ + outb_p((0x04 << (dev->irq == 9 ? 2 : dev->irq)), E33G_IDCFR); + outb_p((WRD_COUNT << 1), E33G_DRQCNT); /* Set burst size to 8 */ + outb_p(0x20, E33G_DMAAH); /* Put a valid addr in the GA DMA */ + outb_p(0x00, E33G_DMAAL); + return; /* We always succeed */ +} + +/* + * Either use the shared memory (if enabled on the board) or put the packet + * out through the ASIC FIFO. + */ +static void +el2_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page) +{ + unsigned short int *wrd; + int boguscount; /* timeout counter */ + unsigned short word; /* temporary for better machine code */ + + if (ei_status.word16) /* Tx packets go into bank 0 on EL2/16 card */ + outb(EGACFR_RSEL|EGACFR_TCM, E33G_GACFR); + else + outb(EGACFR_NORM, E33G_GACFR); + + if (dev->mem_start) { /* Shared memory transfer */ + unsigned long dest_addr = dev->mem_start + + ((start_page - ei_status.tx_start_page) << 8); + memcpy_toio(dest_addr, buf, count); + outb(EGACFR_NORM, E33G_GACFR); /* Back to bank1 in case on bank0 */ + return; + } + +/* + * No shared memory, put the packet out the other way. + * Set up then start the internal memory transfer to Tx Start Page + */ + + word = (unsigned short)start_page; + outb(word&0xFF, E33G_DMAAH); + outb(word>>8, E33G_DMAAL); + + outb_p((ei_status.interface_num ? ECNTRL_AUI : ECNTRL_THIN ) | ECNTRL_OUTPUT + | ECNTRL_START, E33G_CNTRL); + +/* + * Here I am going to write data to the FIFO as quickly as possible. + * Note that E33G_FIFOH is defined incorrectly. It is really + * E33G_FIFOL, the lowest port address for both the byte and + * word write. Variable 'count' is NOT checked. Caller must supply a + * valid count. Note that I may write a harmless extra byte to the + * 8390 if the byte-count was not even. + */ + wrd = (unsigned short int *) buf; + count = (count + 1) >> 1; + for(;;) + { + boguscount = 0x1000; + while ((inb(E33G_STATUS) & ESTAT_DPRDY) == 0) + { + if(!boguscount--) + { + printk("%s: FIFO blocked in el2_block_output.\n", dev->name); + el2_reset_8390(dev); + goto blocked; + } + } + if(count > WRD_COUNT) + { + outsw(E33G_FIFOH, wrd, WRD_COUNT); + wrd += WRD_COUNT; + count -= WRD_COUNT; + } + else + { + outsw(E33G_FIFOH, wrd, count); + break; + } + } + blocked:; + outb_p(ei_status.interface_num==0 ? ECNTRL_THIN : ECNTRL_AUI, E33G_CNTRL); + return; +} + +/* Read the 4 byte, page aligned 8390 specific header. */ +static void +el2_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + int boguscount; + unsigned long hdr_start = dev->mem_start + ((ring_page - EL2_MB1_START_PG)<<8); + unsigned short word; + + if (dev->mem_start) { /* Use the shared memory. */ + memcpy_fromio(hdr, hdr_start, sizeof(struct e8390_pkt_hdr)); + return; + } + +/* + * No shared memory, use programmed I/O. + */ + + word = (unsigned short)ring_page; + outb(word&0xFF, E33G_DMAAH); + outb(word>>8, E33G_DMAAL); + + outb_p((ei_status.interface_num == 0 ? ECNTRL_THIN : ECNTRL_AUI) | ECNTRL_INPUT + | ECNTRL_START, E33G_CNTRL); + boguscount = 0x1000; + while ((inb(E33G_STATUS) & ESTAT_DPRDY) == 0) + { + if(!boguscount--) + { + printk("%s: FIFO blocked in el2_get_8390_hdr.\n", dev->name); + memset(hdr, 0x00, sizeof(struct e8390_pkt_hdr)); + el2_reset_8390(dev); + goto blocked; + } + } + insw(E33G_FIFOH, hdr, (sizeof(struct e8390_pkt_hdr))>> 1); + blocked:; + outb_p(ei_status.interface_num == 0 ? ECNTRL_THIN : ECNTRL_AUI, E33G_CNTRL); +} + + +static void +el2_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + int boguscount = 0; + unsigned short int *buf; + unsigned short word; + + int end_of_ring = dev->rmem_end; + + /* Maybe enable shared memory just be to be safe... nahh.*/ + if (dev->mem_start) { /* Use the shared memory. */ + ring_offset -= (EL2_MB1_START_PG<<8); + if (dev->mem_start + ring_offset + count > end_of_ring) { + /* We must wrap the input move. */ + int semi_count = end_of_ring - (dev->mem_start + ring_offset); + memcpy_fromio(skb->data, dev->mem_start + ring_offset, semi_count); + count -= semi_count; + memcpy_fromio(skb->data + semi_count, dev->rmem_start, count); + } else { + /* Packet is in one chunk -- we can copy + cksum. */ + eth_io_copy_and_sum(skb, dev->mem_start + ring_offset, count, 0); + } + return; + } + +/* + * No shared memory, use programmed I/O. + */ + word = (unsigned short) ring_offset; + outb(word>>8, E33G_DMAAH); + outb(word&0xFF, E33G_DMAAL); + + outb_p((ei_status.interface_num == 0 ? ECNTRL_THIN : ECNTRL_AUI) | ECNTRL_INPUT + | ECNTRL_START, E33G_CNTRL); + +/* + * Here I also try to get data as fast as possible. I am betting that I + * can read one extra byte without clobbering anything in the kernel because + * this would only occur on an odd byte-count and allocation of skb->data + * is word-aligned. Variable 'count' is NOT checked. Caller must check + * for a valid count. + * [This is currently quite safe.... but if one day the 3c503 explodes + * you know where to come looking ;)] + */ + + buf = (unsigned short int *) skb->data; + count = (count + 1) >> 1; + for(;;) + { + boguscount = 0x1000; + while ((inb(E33G_STATUS) & ESTAT_DPRDY) == 0) + { + if(!boguscount--) + { + printk("%s: FIFO blocked in el2_block_input.\n", dev->name); + el2_reset_8390(dev); + goto blocked; + } + } + if(count > WRD_COUNT) + { + insw(E33G_FIFOH, buf, WRD_COUNT); + buf += WRD_COUNT; + count -= WRD_COUNT; + } + else + { + insw(E33G_FIFOH, buf, count); + break; + } + } + blocked:; + outb_p(ei_status.interface_num == 0 ? ECNTRL_THIN : ECNTRL_AUI, E33G_CNTRL); + return; +} +#ifdef MODULE +#define MAX_EL2_CARDS 4 /* Max number of EL2 cards per module */ +#define NAMELEN 8 /* #of chars for storing dev->name */ + +static char namelist[NAMELEN * MAX_EL2_CARDS] = { 0, }; +static struct device dev_el2[MAX_EL2_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_EL2_CARDS] = { 0, }; +static int irq[MAX_EL2_CARDS] = { 0, }; +static int xcvr[MAX_EL2_CARDS] = { 0, }; /* choose int. or ext. xcvr */ + +/* This is set up so that only a single autoprobe takes place per call. +ISA device autoprobes on a running machine are not recommended. */ +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_EL2_CARDS; this_dev++) { + struct device *dev = &dev_el2[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->mem_end = xcvr[this_dev]; /* low 4bits = xcvr sel. */ + dev->init = el2_probe; + if (io[this_dev] == 0) { + if (this_dev != 0) break; /* only autoprobe 1st one */ + printk(KERN_NOTICE "3c503.c: Presently autoprobing (not recommended) for a single card.\n"); + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "3c503.c: No 3c503 card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_EL2_CARDS; this_dev++) { + struct device *dev = &dev_el2[this_dev]; + if (dev->priv != NULL) { + /* NB: el2_close() handles free_irq + irq2dev map */ + kfree(dev->priv); + dev->priv = NULL; + release_region(dev->base_addr, EL2_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + +/* + * Local variables: + * version-control: t + * kept-new-versions: 5 + * c-indent-level: 4 + * End: + */ diff --git a/linux/src/drivers/net/3c503.h b/linux/src/drivers/net/3c503.h new file mode 100644 index 00000000..b9f8a46f --- /dev/null +++ b/linux/src/drivers/net/3c503.h @@ -0,0 +1,91 @@ +/* Definitions for the 3Com 3c503 Etherlink 2. */ +/* This file is distributed under the GPL. + Many of these names and comments are directly from the Crynwr packet + drivers, which are released under the GPL. */ + +#define EL2H (dev->base_addr + 0x400) +#define EL2L (dev->base_addr) + +/* Vendor unique hardware addr. prefix. 3Com has 2 because they ran + out of available addresses on the first one... */ + +#define OLD_3COM_ID 0x02608c +#define NEW_3COM_ID 0x0020af + +/* Shared memory management parameters. NB: The 8 bit cards have only + one bank (MB1) which serves both Tx and Rx packet space. The 16bit + cards have 2 banks, MB0 for Tx packets, and MB1 for Rx packets. + You choose which bank appears in the sh. mem window with EGACFR_MBSn */ + +#define EL2_MB0_START_PG (0x00) /* EL2/16 Tx packets go in bank 0 */ +#define EL2_MB1_START_PG (0x20) /* First page of bank 1 */ +#define EL2_MB1_STOP_PG (0x40) /* Last page +1 of bank 1 */ + +/* 3Com 3c503 ASIC registers */ +#define E33G_STARTPG (EL2H+0) /* Start page, matching EN0_STARTPG */ +#define E33G_STOPPG (EL2H+1) /* Stop page, must match EN0_STOPPG */ +#define E33G_DRQCNT (EL2H+2) /* DMA burst count */ +#define E33G_IOBASE (EL2H+3) /* Read of I/O base jumpers. */ + /* (non-useful, but it also appears at the end of EPROM space) */ +#define E33G_ROMBASE (EL2H+4) /* Read of memory base jumpers. */ +#define E33G_GACFR (EL2H+5) /* Config/setup bits for the ASIC GA */ +#define E33G_CNTRL (EL2H+6) /* Board's main control register */ +#define E33G_STATUS (EL2H+7) /* Status on completions. */ +#define E33G_IDCFR (EL2H+8) /* Interrupt/DMA config register */ + /* (Which IRQ to assert, DMA chan to use) */ +#define E33G_DMAAH (EL2H+9) /* High byte of DMA address reg */ +#define E33G_DMAAL (EL2H+10) /* Low byte of DMA address reg */ +/* "Vector pointer" - if this address matches a read, the EPROM (rather than + shared RAM) is mapped into memory space. */ +#define E33G_VP2 (EL2H+11) +#define E33G_VP1 (EL2H+12) +#define E33G_VP0 (EL2H+13) +#define E33G_FIFOH (EL2H+14) /* FIFO for programmed I/O moves */ +#define E33G_FIFOL (EL2H+15) /* ... low byte of above. */ + +/* Bits in E33G_CNTRL register: */ + +#define ECNTRL_RESET (0x01) /* Software reset of the ASIC and 8390 */ +#define ECNTRL_THIN (0x02) /* Onboard xcvr enable, AUI disable */ +#define ECNTRL_AUI (0x00) /* Onboard xcvr disable, AUI enable */ +#define ECNTRL_SAPROM (0x04) /* Map the station address prom */ +#define ECNTRL_DBLBFR (0x20) /* FIFO configuration bit */ +#define ECNTRL_OUTPUT (0x40) /* PC-to-3C503 direction if 1 */ +#define ECNTRL_INPUT (0x00) /* 3C503-to-PC direction if 0 */ +#define ECNTRL_START (0x80) /* Start the DMA logic */ + +/* Bits in E33G_STATUS register: */ + +#define ESTAT_DPRDY (0x80) /* Data port (of FIFO) ready */ +#define ESTAT_UFLW (0x40) /* Tried to read FIFO when it was empty */ +#define ESTAT_OFLW (0x20) /* Tried to write FIFO when it was full */ +#define ESTAT_DTC (0x10) /* Terminal Count from PC bus DMA logic */ +#define ESTAT_DIP (0x08) /* DMA In Progress */ + +/* Bits in E33G_GACFR register: */ + +#define EGACFR_NIM (0x80) /* NIC interrupt mask */ +#define EGACFR_TCM (0x40) /* DMA term. count interrupt mask */ +#define EGACFR_RSEL (0x08) /* Map a bank of card mem into system mem */ +#define EGACFR_MBS2 (0x04) /* Memory bank select, bit 2. */ +#define EGACFR_MBS1 (0x02) /* Memory bank select, bit 1. */ +#define EGACFR_MBS0 (0x01) /* Memory bank select, bit 0. */ + +#define EGACFR_NORM (0x49) /* TCM | RSEL | MBS0 */ +#define EGACFR_IRQOFF (0xc9) /* TCM | RSEL | MBS0 | NIM */ + +/* + MBS2 MBS1 MBS0 Sh. mem windows card mem at: + ---- ---- ---- ----------------------------- + 0 0 0 0x0000 -- bank 0 + 0 0 1 0x2000 -- bank 1 (only choice for 8bit card) + 0 1 0 0x4000 -- bank 2, not used + 0 1 1 0x6000 -- bank 3, not used + +There was going to be a 32k card that used bank 2 and 3, but it +never got produced. + +*/ + + +/* End of 3C503 parameter definitions */ diff --git a/linux/src/drivers/net/3c505.c b/linux/src/drivers/net/3c505.c new file mode 100644 index 00000000..b2163ecc --- /dev/null +++ b/linux/src/drivers/net/3c505.c @@ -0,0 +1,1732 @@ +/* + * Linux ethernet device driver for the 3Com Etherlink Plus (3C505) + * By Craig Southeren, Juha Laiho and Philip Blundell + * + * 3c505.c This module implements an interface to the 3Com + * Etherlink Plus (3c505) ethernet card. Linux device + * driver interface reverse engineered from the Linux 3C509 + * device drivers. Some 3C505 information gleaned from + * the Crynwr packet driver. Still this driver would not + * be here without 3C505 technical reference provided by + * 3Com. + * + * $Id: 3c505.c,v 1.1 1999/04/26 05:51:48 tb Exp $ + * + * Authors: Linux 3c505 device driver by + * Craig Southeren, <craigs@ineluki.apana.org.au> + * Final debugging by + * Andrew Tridgell, <tridge@nimbus.anu.edu.au> + * Auto irq/address, tuning, cleanup and v1.1.4+ kernel mods by + * Juha Laiho, <jlaiho@ichaos.nullnet.fi> + * Linux 3C509 driver by + * Donald Becker, <becker@super.org> + * Crynwr packet driver by + * Krishnan Gopalan and Gregg Stefancik, + * Clemson University Engineering Computer Operations. + * Portions of the code have been adapted from the 3c505 + * driver for NCSA Telnet by Bruce Orchard and later + * modified by Warren Van Houten and krus@diku.dk. + * 3C505 technical information provided by + * Terry Murphy, of 3Com Network Adapter Division + * Linux 1.3.0 changes by + * Alan Cox <Alan.Cox@linux.org> + * More debugging and DMA version by Philip Blundell + */ + +/* Theory of operation: + + * The 3c505 is quite an intelligent board. All communication with it is done + * by means of Primary Command Blocks (PCBs); these are transferred using PIO + * through the command register. The card has 256k of on-board RAM, which is + * used to buffer received packets. It might seem at first that more buffers + * are better, but in fact this isn't true. From my tests, it seems that + * more than about 10 buffers are unnecessary, and there is a noticeable + * performance hit in having more active on the card. So the majority of the + * card's memory isn't, in fact, used. + * + * We keep up to 4 "receive packet" commands active on the board at a time. + * When a packet comes in, so long as there is a receive command active, the + * board will send us a "packet received" PCB and then add the data for that + * packet to the DMA queue. If a DMA transfer is not already in progress, we + * set one up to start uploading the data. We have to maintain a list of + * backlogged receive packets, because the card may decide to tell us about + * a newly-arrived packet at any time, and we may not be able to start a DMA + * transfer immediately (ie one may already be going on). We can't NAK the + * PCB, because then it would throw the packet away. + * + * Trying to send a PCB to the card at the wrong moment seems to have bad + * effects. If we send it a transmit PCB while a receive DMA is happening, + * it will just NAK the PCB and so we will have wasted our time. Worse, it + * sometimes seems to interrupt the transfer. The majority of the low-level + * code is protected by one huge semaphore -- "busy" -- which is set whenever + * it probably isn't safe to do anything to the card. The receive routine + * must gain a lock on "busy" before it can start a DMA transfer, and the + * transmit routine must gain a lock before it sends the first PCB to the card. + * The send_pcb() routine also has an internal semaphore to protect it against + * being re-entered (which would be disastrous) -- this is needed because + * several things can happen asynchronously (re-priming the receiver and + * asking the card for statistics, for example). send_pcb() will also refuse + * to talk to the card at all if a DMA upload is happening. The higher-level + * networking code will reschedule a later retry if some part of the driver + * is blocked. In practice, this doesn't seem to happen very often. + */ + +/* This driver will not work with revision 2 hardware, because the host + * control register is write-only. It should be fairly easy to arrange to + * keep our own soft-copy of the intended contents of this register, if + * somebody has the time. There may be firmware differences that cause + * other problems, though, and I don't have an old card to test. + */ + +/* The driver is a mess. I took Craig's and Juha's code, and hacked it firstly + * to make it more reliable, and secondly to add DMA mode. Many things could + * probably be done better; the concurrency protection is particularly awful. + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/ioport.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "3c505.h" + +#define ELP_DMA 6 /* DMA channel to use */ +#define ELP_RX_PCBS 4 + +/********************************************************* + * + * define debug messages here as common strings to reduce space + * + *********************************************************/ + +static const char *filename = __FILE__; + +static const char *timeout_msg = "*** timeout at %s:%s (line %d) ***\n"; +#define TIMEOUT_MSG(lineno) \ + printk(timeout_msg, filename,__FUNCTION__,(lineno)) + +static const char *invalid_pcb_msg = +"*** invalid pcb length %d at %s:%s (line %d) ***\n"; +#define INVALID_PCB_MSG(len) \ + printk(invalid_pcb_msg, (len),filename,__FUNCTION__,__LINE__) + +static const char *search_msg = "%s: Looking for 3c505 adapter at address %#x..."; + +static const char *stilllooking_msg = "still looking..."; + +static const char *found_msg = "found.\n"; + +static const char *notfound_msg = "not found (reason = %d)\n"; + +static const char *couldnot_msg = "%s: 3c505 not found\n"; + +/********************************************************* + * + * various other debug stuff + * + *********************************************************/ + +#ifdef ELP_DEBUG +static const int elp_debug = ELP_DEBUG; +#else +static const int elp_debug = 0; +#endif + +/* + * 0 = no messages (well, some) + * 1 = messages when high level commands performed + * 2 = messages when low level commands performed + * 3 = messages when interrupts received + */ + +/***************************************************************** + * + * useful macros + * + *****************************************************************/ + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +/***************************************************************** + * + * List of I/O-addresses we try to auto-sense + * Last element MUST BE 0! + *****************************************************************/ + +const int addr_list[] = {0x300, 0x280, 0x310, 0}; + +/* Dma Memory related stuff */ + +/* Pure 2^n version of get_order */ +static inline int __get_order(unsigned long size) +{ + int order; + + size = (size - 1) >> (PAGE_SHIFT - 1); + order = -1; + do { + size >>= 1; + order++; + } while (size); + return order; +} + +static unsigned long dma_mem_alloc(int size) +{ + int order = __get_order(size); + + return __get_dma_pages(GFP_KERNEL, order); +} + + +/***************************************************************** + * + * Functions for I/O (note the inline !) + * + *****************************************************************/ + +static inline unsigned char inb_status(unsigned int base_addr) +{ + return inb(base_addr + PORT_STATUS); +} + +static inline unsigned char inb_control(unsigned int base_addr) +{ + return inb(base_addr + PORT_CONTROL); +} + +static inline int inb_command(unsigned int base_addr) +{ + return inb(base_addr + PORT_COMMAND); +} + +static inline void outb_control(unsigned char val, unsigned int base_addr) +{ + outb(val, base_addr + PORT_CONTROL); +} + +static inline void outb_command(unsigned char val, unsigned int base_addr) +{ + outb(val, base_addr + PORT_COMMAND); +} + +static inline unsigned int inw_data(unsigned int base_addr) +{ + return inw(base_addr + PORT_DATA); +} + +static inline void outw_data(unsigned int val, unsigned int base_addr) +{ + outw(val, base_addr + PORT_DATA); +} + + +/***************************************************************** + * + * structure to hold context information for adapter + * + *****************************************************************/ + +#define DMA_BUFFER_SIZE 1600 +#define BACKLOG_SIZE 4 + +typedef struct { + volatile short got[NUM_TRANSMIT_CMDS]; /* flags for command completion */ + pcb_struct tx_pcb; /* PCB for foreground sending */ + pcb_struct rx_pcb; /* PCB for foreground receiving */ + pcb_struct itx_pcb; /* PCB for background sending */ + pcb_struct irx_pcb; /* PCB for background receiving */ + struct enet_statistics stats; + + void *dma_buffer; + + struct { + unsigned int length[BACKLOG_SIZE]; + unsigned int in; + unsigned int out; + } rx_backlog; + + struct { + unsigned int direction; + unsigned int length; + unsigned int copy_flag; + struct sk_buff *skb; + long int start_time; + } current_dma; + + /* flags */ + unsigned long send_pcb_semaphore; + unsigned int dmaing; + unsigned long busy; + + unsigned int rx_active; /* number of receive PCBs */ +} elp_device; + +static inline unsigned int backlog_next(unsigned int n) +{ + return (n + 1) % BACKLOG_SIZE; +} + +/***************************************************************** + * + * useful functions for accessing the adapter + * + *****************************************************************/ + +/* + * use this routine when accessing the ASF bits as they are + * changed asynchronously by the adapter + */ + +/* get adapter PCB status */ +#define GET_ASF(addr) \ + (get_status(addr)&ASF_PCB_MASK) + +static inline int get_status(unsigned int base_addr) +{ + int timeout = jiffies + 10; + register int stat1; + do { + stat1 = inb_status(base_addr); + } while (stat1 != inb_status(base_addr) && jiffies < timeout); + if (jiffies >= timeout) + TIMEOUT_MSG(__LINE__); + return stat1; +} + +static inline void set_hsf(unsigned int base_addr, int hsf) +{ + cli(); + outb_control((inb_control(base_addr) & ~HSF_PCB_MASK) | hsf, base_addr); + sti(); +} + +static int start_receive(struct device *, pcb_struct *); + +inline static void adapter_reset(struct device *dev) +{ + int timeout; + unsigned char orig_hcr = inb_control(dev->base_addr); + + elp_device *adapter = dev->priv; + + outb_control(0, dev->base_addr); + + if (inb_status(dev->base_addr) & ACRF) { + do { + inb_command(dev->base_addr); + timeout = jiffies + 2; + while ((jiffies <= timeout) && !(inb_status(dev->base_addr) & ACRF)); + } while (inb_status(dev->base_addr) & ACRF); + set_hsf(dev->base_addr, HSF_PCB_NAK); + } + outb_control(inb_control(dev->base_addr) | ATTN | DIR, dev->base_addr); + timeout = jiffies + 1; + while (jiffies <= timeout); + outb_control(inb_control(dev->base_addr) & ~ATTN, dev->base_addr); + timeout = jiffies + 1; + while (jiffies <= timeout); + outb_control(inb_control(dev->base_addr) | FLSH, dev->base_addr); + timeout = jiffies + 1; + while (jiffies <= timeout); + outb_control(inb_control(dev->base_addr) & ~FLSH, dev->base_addr); + timeout = jiffies + 1; + while (jiffies <= timeout); + + outb_control(orig_hcr, dev->base_addr); + if (!start_receive(dev, &adapter->tx_pcb)) + printk("%s: start receive command failed \n", dev->name); +} + +/* Check to make sure that a DMA transfer hasn't timed out. This should never happen + * in theory, but seems to occur occasionally if the card gets prodded at the wrong + * time. + */ +static inline void check_dma(struct device *dev) +{ + elp_device *adapter = dev->priv; + if (adapter->dmaing && (jiffies > (adapter->current_dma.start_time + 10))) { + unsigned long flags; + printk("%s: DMA %s timed out, %d bytes left\n", dev->name, adapter->current_dma.direction ? "download" : "upload", get_dma_residue(dev->dma)); + save_flags(flags); + cli(); + adapter->dmaing = 0; + adapter->busy = 0; + disable_dma(dev->dma); + if (adapter->rx_active) + adapter->rx_active--; + outb_control(inb_control(dev->base_addr) & ~(DMAE | TCEN | DIR), dev->base_addr); + restore_flags(flags); + } +} + +/* Primitive functions used by send_pcb() */ +static inline unsigned int send_pcb_slow(unsigned int base_addr, unsigned char byte) +{ + unsigned int timeout; + outb_command(byte, base_addr); + for (timeout = jiffies + 5; jiffies < timeout;) { + if (inb_status(base_addr) & HCRE) + return FALSE; + } + printk("3c505: send_pcb_slow timed out\n"); + return TRUE; +} + +static inline unsigned int send_pcb_fast(unsigned int base_addr, unsigned char byte) +{ + unsigned int timeout; + outb_command(byte, base_addr); + for (timeout = 0; timeout < 40000; timeout++) { + if (inb_status(base_addr) & HCRE) + return FALSE; + } + printk("3c505: send_pcb_fast timed out\n"); + return TRUE; +} + +/* Check to see if the receiver needs restarting, and kick it if so */ +static inline void prime_rx(struct device *dev) +{ + elp_device *adapter = dev->priv; + while (adapter->rx_active < ELP_RX_PCBS && dev->start) { + if (!start_receive(dev, &adapter->itx_pcb)) + break; + } +} + +/***************************************************************** + * + * send_pcb + * Send a PCB to the adapter. + * + * output byte to command reg --<--+ + * wait until HCRE is non zero | + * loop until all bytes sent -->--+ + * set HSF1 and HSF2 to 1 + * output pcb length + * wait until ASF give ACK or NAK + * set HSF1 and HSF2 to 0 + * + *****************************************************************/ + +/* This can be quite slow -- the adapter is allowed to take up to 40ms + * to respond to the initial interrupt. + * + * We run initially with interrupts turned on, but with a semaphore set + * so that nobody tries to re-enter this code. Once the first byte has + * gone through, we turn interrupts off and then send the others (the + * timeout is reduced to 500us). + */ + +static int send_pcb(struct device *dev, pcb_struct * pcb) +{ + int i; + int timeout; + elp_device *adapter = dev->priv; + + check_dma(dev); + + if (adapter->dmaing && adapter->current_dma.direction == 0) + return FALSE; + + /* Avoid contention */ + if (set_bit(1, &adapter->send_pcb_semaphore)) { + if (elp_debug >= 3) { + printk("%s: send_pcb entered while threaded\n", dev->name); + } + return FALSE; + } + /* + * load each byte into the command register and + * wait for the HCRE bit to indicate the adapter + * had read the byte + */ + set_hsf(dev->base_addr, 0); + + if (send_pcb_slow(dev->base_addr, pcb->command)) + goto abort; + + cli(); + + if (send_pcb_fast(dev->base_addr, pcb->length)) + goto sti_abort; + + for (i = 0; i < pcb->length; i++) { + if (send_pcb_fast(dev->base_addr, pcb->data.raw[i])) + goto sti_abort; + } + + outb_control(inb_control(dev->base_addr) | 3, dev->base_addr); /* signal end of PCB */ + outb_command(2 + pcb->length, dev->base_addr); + + /* now wait for the acknowledgement */ + sti(); + + for (timeout = jiffies + 5; jiffies < timeout;) { + switch (GET_ASF(dev->base_addr)) { + case ASF_PCB_ACK: + adapter->send_pcb_semaphore = 0; + return TRUE; + break; + case ASF_PCB_NAK: + printk("%s: send_pcb got NAK\n", dev->name); + goto abort; + break; + } + } + + if (elp_debug >= 1) + printk("%s: timeout waiting for PCB acknowledge (status %02x)\n", dev->name, inb_status(dev->base_addr)); + + sti_abort: + sti(); + abort: + adapter->send_pcb_semaphore = 0; + return FALSE; +} + + +/***************************************************************** + * + * receive_pcb + * Read a PCB from the adapter + * + * wait for ACRF to be non-zero ---<---+ + * input a byte | + * if ASF1 and ASF2 were not both one | + * before byte was read, loop --->---+ + * set HSF1 and HSF2 for ack + * + *****************************************************************/ + +static int receive_pcb(struct device *dev, pcb_struct * pcb) +{ + int i, j; + int total_length; + int stat; + int timeout; + + elp_device *adapter = dev->priv; + + set_hsf(dev->base_addr, 0); + + /* get the command code */ + timeout = jiffies + 2; + while (((stat = get_status(dev->base_addr)) & ACRF) == 0 && jiffies < timeout); + if (jiffies >= timeout) { + TIMEOUT_MSG(__LINE__); + return FALSE; + } + pcb->command = inb_command(dev->base_addr); + + /* read the data length */ + timeout = jiffies + 3; + while (((stat = get_status(dev->base_addr)) & ACRF) == 0 && jiffies < timeout); + if (jiffies >= timeout) { + TIMEOUT_MSG(__LINE__); + printk("%s: status %02x\n", dev->name, stat); + return FALSE; + } + pcb->length = inb_command(dev->base_addr); + + if (pcb->length > MAX_PCB_DATA) { + INVALID_PCB_MSG(pcb->length); + adapter_reset(dev); + return FALSE; + } + /* read the data */ + cli(); + i = 0; + do { + j = 0; + while (((stat = get_status(dev->base_addr)) & ACRF) == 0 && j++ < 20000); + pcb->data.raw[i++] = inb_command(dev->base_addr); + if (i > MAX_PCB_DATA) + INVALID_PCB_MSG(i); + } while ((stat & ASF_PCB_MASK) != ASF_PCB_END && j < 20000); + sti(); + if (j >= 20000) { + TIMEOUT_MSG(__LINE__); + return FALSE; + } + /* woops, the last "data" byte was really the length! */ + total_length = pcb->data.raw[--i]; + + /* safety check total length vs data length */ + if (total_length != (pcb->length + 2)) { + if (elp_debug >= 2) + printk("%s: mangled PCB received\n", dev->name); + set_hsf(dev->base_addr, HSF_PCB_NAK); + return FALSE; + } + + if (pcb->command == CMD_RECEIVE_PACKET_COMPLETE) { + if (set_bit(0, (void *) &adapter->busy)) { + if (backlog_next(adapter->rx_backlog.in) == adapter->rx_backlog.out) { + set_hsf(dev->base_addr, HSF_PCB_NAK); + printk("%s: PCB rejected, transfer in progress and backlog full\n", dev->name); + pcb->command = 0; + return TRUE; + } else { + pcb->command = 0xff; + } + } + } + set_hsf(dev->base_addr, HSF_PCB_ACK); + return TRUE; +} + +/****************************************************** + * + * queue a receive command on the adapter so we will get an + * interrupt when a packet is received. + * + ******************************************************/ + +static int start_receive(struct device *dev, pcb_struct * tx_pcb) +{ + int status; + elp_device *adapter = dev->priv; + + if (elp_debug >= 3) + printk("%s: restarting receiver\n", dev->name); + tx_pcb->command = CMD_RECEIVE_PACKET; + tx_pcb->length = sizeof(struct Rcv_pkt); + tx_pcb->data.rcv_pkt.buf_seg + = tx_pcb->data.rcv_pkt.buf_ofs = 0; /* Unused */ + tx_pcb->data.rcv_pkt.buf_len = 1600; + tx_pcb->data.rcv_pkt.timeout = 0; /* set timeout to zero */ + status = send_pcb(dev, tx_pcb); + if (status) + adapter->rx_active++; + return status; +} + +/****************************************************** + * + * extract a packet from the adapter + * this routine is only called from within the interrupt + * service routine, so no cli/sti calls are needed + * note that the length is always assumed to be even + * + ******************************************************/ + +static void receive_packet(struct device *dev, int len) +{ + int rlen; + elp_device *adapter = dev->priv; + unsigned long target; + struct sk_buff *skb; + + rlen = (len + 1) & ~1; + skb = dev_alloc_skb(rlen + 2); + + adapter->current_dma.copy_flag = 0; + + if (!skb) { + printk("%s: memory squeeze, dropping packet\n", dev->name); + target = virt_to_bus(adapter->dma_buffer); + } else { + skb_reserve(skb, 2); + target = virt_to_bus(skb_put(skb, rlen)); + if ((target + rlen) >= MAX_DMA_ADDRESS) { + target = virt_to_bus(adapter->dma_buffer); + adapter->current_dma.copy_flag = 1; + } + } + /* if this happens, we die */ + if (set_bit(0, (void *) &adapter->dmaing)) + printk("%s: rx blocked, DMA in progress, dir %d\n", dev->name, adapter->current_dma.direction); + + adapter->current_dma.direction = 0; + adapter->current_dma.length = rlen; + adapter->current_dma.skb = skb; + adapter->current_dma.start_time = jiffies; + + outb_control(inb_control(dev->base_addr) | DIR | TCEN | DMAE, dev->base_addr); + + disable_dma(dev->dma); + clear_dma_ff(dev->dma); + set_dma_mode(dev->dma, 0x04); /* dma read */ + set_dma_addr(dev->dma, target); + set_dma_count(dev->dma, rlen); + enable_dma(dev->dma); + + if (elp_debug >= 3) { + printk("%s: rx DMA transfer started\n", dev->name); + } + if (adapter->rx_active) + adapter->rx_active--; + + if (!adapter->busy) + printk("%s: receive_packet called, busy not set.\n", dev->name); +} + +/****************************************************** + * + * interrupt handler + * + ******************************************************/ + +static void elp_interrupt(int irq, void *dev_id, struct pt_regs *reg_ptr) +{ + int len; + int dlen; + int icount = 0; + struct device *dev; + elp_device *adapter; + int timeout; + + if (irq < 0 || irq > 15) { + printk("elp_interrupt(): illegal IRQ number found in interrupt routine (%i)\n", irq); + return; + } + dev = irq2dev_map[irq]; + + if (dev == NULL) { + printk("elp_interrupt(): irq %d for unknown device.\n", irq); + return; + } + adapter = (elp_device *) dev->priv; + + if (dev->interrupt) { + printk("%s: re-entering the interrupt handler!\n", dev->name); + return; + } + dev->interrupt = 1; + + do { + /* + * has a DMA transfer finished? + */ + if (inb_status(dev->base_addr) & DONE) { + if (!adapter->dmaing) { + printk("%s: phantom DMA completed\n", dev->name); + } + if (elp_debug >= 3) { + printk("%s: %s DMA complete, status %02x\n", dev->name, adapter->current_dma.direction ? "tx" : "rx", inb_status(dev->base_addr)); + } + + outb_control(inb_control(dev->base_addr) & ~(DMAE | TCEN | DIR), dev->base_addr); + if (adapter->current_dma.direction) { + dev_kfree_skb(adapter->current_dma.skb, FREE_WRITE); + } else { + struct sk_buff *skb = adapter->current_dma.skb; + if (skb) { + skb->dev = dev; + if (adapter->current_dma.copy_flag) { + memcpy(skb_put(skb, adapter->current_dma.length), adapter->dma_buffer, adapter->current_dma.length); + } + skb->protocol = eth_type_trans(skb,dev); + netif_rx(skb); + } + } + adapter->dmaing = 0; + if (adapter->rx_backlog.in != adapter->rx_backlog.out) { + int t = adapter->rx_backlog.length[adapter->rx_backlog.out]; + adapter->rx_backlog.out = backlog_next(adapter->rx_backlog.out); + if (elp_debug >= 2) + printk("%s: receiving backlogged packet (%d)\n", dev->name, t); + receive_packet(dev, t); + } else { + adapter->busy = 0; + } + } else { + /* has one timed out? */ + check_dma(dev); + } + + sti(); + + /* + * receive a PCB from the adapter + */ + timeout = jiffies + 3; + while ((inb_status(dev->base_addr) & ACRF) != 0 && jiffies < timeout) { + if (receive_pcb(dev, &adapter->irx_pcb)) { + switch (adapter->irx_pcb.command) { + case 0: + break; + /* + * received a packet - this must be handled fast + */ + case 0xff: + case CMD_RECEIVE_PACKET_COMPLETE: + /* if the device isn't open, don't pass packets up the stack */ + if (dev->start == 0) + break; + cli(); + len = adapter->irx_pcb.data.rcv_resp.pkt_len; + dlen = adapter->irx_pcb.data.rcv_resp.buf_len; + if (adapter->irx_pcb.data.rcv_resp.timeout != 0) { + printk("%s: interrupt - packet not received correctly\n", dev->name); + sti(); + } else { + if (elp_debug >= 3) { + sti(); + printk("%s: interrupt - packet received of length %i (%i)\n", dev->name, len, dlen); + cli(); + } + if (adapter->irx_pcb.command == 0xff) { + if (elp_debug >= 2) + printk("%s: adding packet to backlog (len = %d)\n", dev->name, dlen); + adapter->rx_backlog.length[adapter->rx_backlog.in] = dlen; + adapter->rx_backlog.in = backlog_next(adapter->rx_backlog.in); + } else { + receive_packet(dev, dlen); + } + sti(); + if (elp_debug >= 3) + printk("%s: packet received\n", dev->name); + } + break; + + /* + * 82586 configured correctly + */ + case CMD_CONFIGURE_82586_RESPONSE: + adapter->got[CMD_CONFIGURE_82586] = 1; + if (elp_debug >= 3) + printk("%s: interrupt - configure response received\n", dev->name); + break; + + /* + * Adapter memory configuration + */ + case CMD_CONFIGURE_ADAPTER_RESPONSE: + adapter->got[CMD_CONFIGURE_ADAPTER_MEMORY] = 1; + if (elp_debug >= 3) + printk("%s: Adapter memory configuration %s.\n", dev->name, + adapter->irx_pcb.data.failed ? "failed" : "succeeded"); + break; + + /* + * Multicast list loading + */ + case CMD_LOAD_MULTICAST_RESPONSE: + adapter->got[CMD_LOAD_MULTICAST_LIST] = 1; + if (elp_debug >= 3) + printk("%s: Multicast address list loading %s.\n", dev->name, + adapter->irx_pcb.data.failed ? "failed" : "succeeded"); + break; + + /* + * Station address setting + */ + case CMD_SET_ADDRESS_RESPONSE: + adapter->got[CMD_SET_STATION_ADDRESS] = 1; + if (elp_debug >= 3) + printk("%s: Ethernet address setting %s.\n", dev->name, + adapter->irx_pcb.data.failed ? "failed" : "succeeded"); + break; + + + /* + * received board statistics + */ + case CMD_NETWORK_STATISTICS_RESPONSE: + adapter->stats.rx_packets += adapter->irx_pcb.data.netstat.tot_recv; + adapter->stats.tx_packets += adapter->irx_pcb.data.netstat.tot_xmit; + adapter->stats.rx_crc_errors += adapter->irx_pcb.data.netstat.err_CRC; + adapter->stats.rx_frame_errors += adapter->irx_pcb.data.netstat.err_align; + adapter->stats.rx_fifo_errors += adapter->irx_pcb.data.netstat.err_ovrrun; + adapter->stats.rx_over_errors += adapter->irx_pcb.data.netstat.err_res; + adapter->got[CMD_NETWORK_STATISTICS] = 1; + if (elp_debug >= 3) + printk("%s: interrupt - statistics response received\n", dev->name); + break; + + /* + * sent a packet + */ + case CMD_TRANSMIT_PACKET_COMPLETE: + if (elp_debug >= 3) + printk("%s: interrupt - packet sent\n", dev->name); + if (dev->start == 0) + break; + switch (adapter->irx_pcb.data.xmit_resp.c_stat) { + case 0xffff: + adapter->stats.tx_aborted_errors++; + printk(KERN_INFO "%s: transmit timed out, network cable problem?\n", dev->name); + break; + case 0xfffe: + adapter->stats.tx_fifo_errors++; + printk(KERN_INFO "%s: transmit timed out, FIFO underrun\n", dev->name); + break; + } + dev->tbusy = 0; + mark_bh(NET_BH); + break; + + /* + * some unknown PCB + */ + default: + printk(KERN_DEBUG "%s: unknown PCB received - %2.2x\n", dev->name, adapter->irx_pcb.command); + break; + } + } else { + printk("%s: failed to read PCB on interrupt\n", dev->name); + adapter_reset(dev); + } + } + + } while (icount++ < 5 && (inb_status(dev->base_addr) & (ACRF | DONE))); + + prime_rx(dev); + + /* + * indicate no longer in interrupt routine + */ + dev->interrupt = 0; +} + + +/****************************************************** + * + * open the board + * + ******************************************************/ + +static int elp_open(struct device *dev) +{ + elp_device *adapter; + + adapter = dev->priv; + + if (elp_debug >= 3) + printk("%s: request to open device\n", dev->name); + + /* + * make sure we actually found the device + */ + if (adapter == NULL) { + printk("%s: Opening a non-existent physical device\n", dev->name); + return -EAGAIN; + } + /* + * disable interrupts on the board + */ + outb_control(0x00, dev->base_addr); + + /* + * clear any pending interrupts + */ + inb_command(dev->base_addr); + adapter_reset(dev); + + /* + * interrupt routine not entered + */ + dev->interrupt = 0; + + /* + * transmitter not busy + */ + dev->tbusy = 0; + + /* + * no receive PCBs active + */ + adapter->rx_active = 0; + + adapter->busy = 0; + adapter->send_pcb_semaphore = 0; + adapter->rx_backlog.in = 0; + adapter->rx_backlog.out = 0; + + /* + * make sure we can find the device header given the interrupt number + */ + irq2dev_map[dev->irq] = dev; + + /* + * install our interrupt service routine + */ + if (request_irq(dev->irq, &elp_interrupt, 0, "3c505", NULL)) { + irq2dev_map[dev->irq] = NULL; + return -EAGAIN; + } + if (request_dma(dev->dma, "3c505")) { + printk("%s: could not allocate DMA channel\n", dev->name); + return -EAGAIN; + } + adapter->dma_buffer = (void *) dma_mem_alloc(DMA_BUFFER_SIZE); + if (!adapter->dma_buffer) { + printk("Could not allocate DMA buffer\n"); + } + adapter->dmaing = 0; + + /* + * enable interrupts on the board + */ + outb_control(CMDE, dev->base_addr); + + /* + * device is now officially open! + */ + dev->start = 1; + + /* + * configure adapter memory: we need 10 multicast addresses, default==0 + */ + if (elp_debug >= 3) + printk("%s: sending 3c505 memory configuration command\n", dev->name); + adapter->tx_pcb.command = CMD_CONFIGURE_ADAPTER_MEMORY; + adapter->tx_pcb.data.memconf.cmd_q = 10; + adapter->tx_pcb.data.memconf.rcv_q = 20; + adapter->tx_pcb.data.memconf.mcast = 10; + adapter->tx_pcb.data.memconf.frame = 20; + adapter->tx_pcb.data.memconf.rcv_b = 20; + adapter->tx_pcb.data.memconf.progs = 0; + adapter->tx_pcb.length = sizeof(struct Memconf); + adapter->got[CMD_CONFIGURE_ADAPTER_MEMORY] = 0; + if (!send_pcb(dev, &adapter->tx_pcb)) + printk("%s: couldn't send memory configuration command\n", dev->name); + else { + int timeout = jiffies + TIMEOUT; + while (adapter->got[CMD_CONFIGURE_ADAPTER_MEMORY] == 0 && jiffies < timeout); + if (jiffies >= timeout) + TIMEOUT_MSG(__LINE__); + } + + + /* + * configure adapter to receive broadcast messages and wait for response + */ + if (elp_debug >= 3) + printk("%s: sending 82586 configure command\n", dev->name); + adapter->tx_pcb.command = CMD_CONFIGURE_82586; + adapter->tx_pcb.data.configure = NO_LOOPBACK | RECV_BROAD; + adapter->tx_pcb.length = 2; + adapter->got[CMD_CONFIGURE_82586] = 0; + if (!send_pcb(dev, &adapter->tx_pcb)) + printk("%s: couldn't send 82586 configure command\n", dev->name); + else { + int timeout = jiffies + TIMEOUT; + while (adapter->got[CMD_CONFIGURE_82586] == 0 && jiffies < timeout); + if (jiffies >= timeout) + TIMEOUT_MSG(__LINE__); + } + + /* enable burst-mode DMA */ + outb(0x1, dev->base_addr + PORT_AUXDMA); + + /* + * queue receive commands to provide buffering + */ + prime_rx(dev); + if (elp_debug >= 3) + printk("%s: %d receive PCBs active\n", dev->name, adapter->rx_active); + + MOD_INC_USE_COUNT; + + return 0; /* Always succeed */ +} + + +/****************************************************** + * + * send a packet to the adapter + * + ******************************************************/ + +static int send_packet(struct device *dev, struct sk_buff *skb) +{ + elp_device *adapter = dev->priv; + unsigned long target; + + /* + * make sure the length is even and no shorter than 60 bytes + */ + unsigned int nlen = (((skb->len < 60) ? 60 : skb->len) + 1) & (~1); + + if (set_bit(0, (void *) &adapter->busy)) { + if (elp_debug >= 2) + printk("%s: transmit blocked\n", dev->name); + return FALSE; + } + adapter = dev->priv; + + /* + * send the adapter a transmit packet command. Ignore segment and offset + * and make sure the length is even + */ + adapter->tx_pcb.command = CMD_TRANSMIT_PACKET; + adapter->tx_pcb.length = sizeof(struct Xmit_pkt); + adapter->tx_pcb.data.xmit_pkt.buf_ofs + = adapter->tx_pcb.data.xmit_pkt.buf_seg = 0; /* Unused */ + adapter->tx_pcb.data.xmit_pkt.pkt_len = nlen; + + if (!send_pcb(dev, &adapter->tx_pcb)) { + adapter->busy = 0; + return FALSE; + } + /* if this happens, we die */ + if (set_bit(0, (void *) &adapter->dmaing)) + printk("%s: tx: DMA %d in progress\n", dev->name, adapter->current_dma.direction); + + adapter->current_dma.direction = 1; + adapter->current_dma.start_time = jiffies; + + target = virt_to_bus(skb->data); + if ((target + nlen) >= MAX_DMA_ADDRESS) { + memcpy(adapter->dma_buffer, skb->data, nlen); + target = virt_to_bus(adapter->dma_buffer); + } + adapter->current_dma.skb = skb; + cli(); + disable_dma(dev->dma); + clear_dma_ff(dev->dma); + set_dma_mode(dev->dma, 0x08); /* dma memory -> io */ + set_dma_addr(dev->dma, target); + set_dma_count(dev->dma, nlen); + enable_dma(dev->dma); + outb_control(inb_control(dev->base_addr) | DMAE | TCEN, dev->base_addr); + if (elp_debug >= 3) + printk("%s: DMA transfer started\n", dev->name); + + return TRUE; +} + +/****************************************************** + * + * start the transmitter + * return 0 if sent OK, else return 1 + * + ******************************************************/ + +static int elp_start_xmit(struct sk_buff *skb, struct device *dev) +{ + if (dev->interrupt) { + printk("%s: start_xmit aborted (in irq)\n", dev->name); + return 1; + } + + check_dma(dev); + + /* + * if the transmitter is still busy, we have a transmit timeout... + */ + if (dev->tbusy) { + elp_device *adapter = dev->priv; + int tickssofar = jiffies - dev->trans_start; + int stat; + + if (tickssofar < 1000) + return 1; + + stat = inb_status(dev->base_addr); + printk("%s: transmit timed out, lost %s?\n", dev->name, (stat & ACRF) ? "interrupt" : "command"); + if (elp_debug >= 1) + printk("%s: status %#02x\n", dev->name, stat); + dev->trans_start = jiffies; + dev->tbusy = 0; + adapter->stats.tx_dropped++; + } + + /* Some upper layer thinks we've missed a tx-done interrupt */ + if (skb == NULL) { + dev_tint(dev); + return 0; + } + + if (skb->len <= 0) + return 0; + + if (elp_debug >= 3) + printk("%s: request to send packet of length %d\n", dev->name, (int) skb->len); + + if (set_bit(0, (void *) &dev->tbusy)) { + printk("%s: transmitter access conflict\n", dev->name); + return 1; + } + /* + * send the packet at skb->data for skb->len + */ + if (!send_packet(dev, skb)) { + if (elp_debug >= 2) { + printk("%s: failed to transmit packet\n", dev->name); + } + dev->tbusy = 0; + return 1; + } + if (elp_debug >= 3) + printk("%s: packet of length %d sent\n", dev->name, (int) skb->len); + + /* + * start the transmit timeout + */ + dev->trans_start = jiffies; + + prime_rx(dev); + + return 0; +} + +/****************************************************** + * + * return statistics on the board + * + ******************************************************/ + +static struct enet_statistics *elp_get_stats(struct device *dev) +{ + elp_device *adapter = (elp_device *) dev->priv; + + if (elp_debug >= 3) + printk("%s: request for stats\n", dev->name); + + /* If the device is closed, just return the latest stats we have, + - we cannot ask from the adapter without interrupts */ + if (!dev->start) + return &adapter->stats; + + /* send a get statistics command to the board */ + adapter->tx_pcb.command = CMD_NETWORK_STATISTICS; + adapter->tx_pcb.length = 0; + adapter->got[CMD_NETWORK_STATISTICS] = 0; + if (!send_pcb(dev, &adapter->tx_pcb)) + printk("%s: couldn't send get statistics command\n", dev->name); + else { + int timeout = jiffies + TIMEOUT; + while (adapter->got[CMD_NETWORK_STATISTICS] == 0 && jiffies < timeout); + if (jiffies >= timeout) { + TIMEOUT_MSG(__LINE__); + return &adapter->stats; + } + } + + /* statistics are now up to date */ + return &adapter->stats; +} + +/****************************************************** + * + * close the board + * + ******************************************************/ + +static int elp_close(struct device *dev) +{ + elp_device *adapter; + + adapter = dev->priv; + + if (elp_debug >= 3) + printk("%s: request to close device\n", dev->name); + + /* Someone may request the device statistic information even when + * the interface is closed. The following will update the statistics + * structure in the driver, so we'll be able to give current statistics. + */ + (void) elp_get_stats(dev); + + /* + * disable interrupts on the board + */ + outb_control(0x00, dev->base_addr); + + /* + * flag transmitter as busy (i.e. not available) + */ + dev->tbusy = 1; + + /* + * indicate device is closed + */ + dev->start = 0; + + /* + * release the IRQ + */ + free_irq(dev->irq, NULL); + + /* + * and we no longer have to map irq to dev either + */ + irq2dev_map[dev->irq] = 0; + + free_dma(dev->dma); + free_pages((unsigned long) adapter->dma_buffer, __get_order(DMA_BUFFER_SIZE)); + + MOD_DEC_USE_COUNT; + + return 0; +} + + +/************************************************************ + * + * Set multicast list + * num_addrs==0: clear mc_list + * num_addrs==-1: set promiscuous mode + * num_addrs>0: set mc_list + * + ************************************************************/ + +static void elp_set_mc_list(struct device *dev) +{ + elp_device *adapter = (elp_device *) dev->priv; + struct dev_mc_list *dmi = dev->mc_list; + int i; + + if (elp_debug >= 3) + printk("%s: request to set multicast list\n", dev->name); + + if (!(dev->flags & (IFF_PROMISC | IFF_ALLMULTI))) { + /* send a "load multicast list" command to the board, max 10 addrs/cmd */ + /* if num_addrs==0 the list will be cleared */ + adapter->tx_pcb.command = CMD_LOAD_MULTICAST_LIST; + adapter->tx_pcb.length = 6 * dev->mc_count; + for (i = 0; i < dev->mc_count; i++) { + memcpy(adapter->tx_pcb.data.multicast[i], dmi->dmi_addr, 6); + dmi = dmi->next; + } + adapter->got[CMD_LOAD_MULTICAST_LIST] = 0; + if (!send_pcb(dev, &adapter->tx_pcb)) + printk("%s: couldn't send set_multicast command\n", dev->name); + else { + int timeout = jiffies + TIMEOUT; + while (adapter->got[CMD_LOAD_MULTICAST_LIST] == 0 && jiffies < timeout); + if (jiffies >= timeout) { + TIMEOUT_MSG(__LINE__); + } + } + if (dev->mc_count) + adapter->tx_pcb.data.configure = NO_LOOPBACK | RECV_BROAD | RECV_MULTI; + else /* num_addrs == 0 */ + adapter->tx_pcb.data.configure = NO_LOOPBACK | RECV_BROAD; + } else + adapter->tx_pcb.data.configure = NO_LOOPBACK | RECV_PROMISC; + /* + * configure adapter to receive messages (as specified above) + * and wait for response + */ + if (elp_debug >= 3) + printk("%s: sending 82586 configure command\n", dev->name); + adapter->tx_pcb.command = CMD_CONFIGURE_82586; + adapter->tx_pcb.length = 2; + adapter->got[CMD_CONFIGURE_82586] = 0; + if (!send_pcb(dev, &adapter->tx_pcb)) + printk("%s: couldn't send 82586 configure command\n", dev->name); + else { + int timeout = jiffies + TIMEOUT; + while (adapter->got[CMD_CONFIGURE_82586] == 0 && jiffies < timeout); + if (jiffies >= timeout) + TIMEOUT_MSG(__LINE__); + } +} + +/****************************************************** + * + * initialise Etherlink Plus board + * + ******************************************************/ + +static void elp_init(struct device *dev) +{ + elp_device *adapter = dev->priv; + + /* + * set ptrs to various functions + */ + dev->open = elp_open; /* local */ + dev->stop = elp_close; /* local */ + dev->get_stats = elp_get_stats; /* local */ + dev->hard_start_xmit = elp_start_xmit; /* local */ + dev->set_multicast_list = elp_set_mc_list; /* local */ + + /* Setup the generic properties */ + ether_setup(dev); + + /* + * setup ptr to adapter specific information + */ + memset(&(adapter->stats), 0, sizeof(struct enet_statistics)); + + /* + * memory information + */ + dev->mem_start = dev->mem_end = dev->rmem_end = dev->rmem_start = 0; +} + +/************************************************************ + * + * A couple of tests to see if there's 3C505 or not + * Called only by elp_autodetect + ************************************************************/ + +static int elp_sense(struct device *dev) +{ + int timeout; + int addr = dev->base_addr; + const char *name = dev->name; + long flags; + byte orig_HCR, orig_HSR; + + if (check_region(addr, 0xf)) + return -1; + + orig_HCR = inb_control(addr); + orig_HSR = inb_status(addr); + + if (elp_debug > 0) + printk(search_msg, name, addr); + + if (((orig_HCR == 0xff) && (orig_HSR == 0xff)) || + ((orig_HCR & DIR) != (orig_HSR & DIR))) { + if (elp_debug > 0) + printk(notfound_msg, 1); + return -1; /* It can't be 3c505 if HCR.DIR != HSR.DIR */ + } + /* Enable interrupts - we need timers! */ + save_flags(flags); + sti(); + + /* Wait for a while; the adapter may still be booting up */ + if (elp_debug > 0) + printk(stilllooking_msg); + if (orig_HCR & DIR) { + /* If HCR.DIR is up, we pull it down. HSR.DIR should follow. */ + outb_control(orig_HCR & ~DIR, addr); + timeout = jiffies + 30; + while (jiffies < timeout); + restore_flags(flags); + if (inb_status(addr) & DIR) { + outb_control(orig_HCR, addr); + if (elp_debug > 0) + printk(notfound_msg, 2); + return -1; + } + } else { + /* If HCR.DIR is down, we pull it up. HSR.DIR should follow. */ + outb_control(orig_HCR | DIR, addr); + timeout = jiffies + 30; + while (jiffies < timeout); + restore_flags(flags); + if (!(inb_status(addr) & DIR)) { + outb_control(orig_HCR, addr); + if (elp_debug > 0) + printk(notfound_msg, 3); + return -1; + } + } + /* + * It certainly looks like a 3c505. If it has DMA enabled, it needs + * a hard reset. Also, do a hard reset if selected at the compile time. + */ + if (elp_debug > 0) + printk(found_msg); + + return 0; +} + +/************************************************************* + * + * Search through addr_list[] and try to find a 3C505 + * Called only by eplus_probe + *************************************************************/ + +static int elp_autodetect(struct device *dev) +{ + int idx = 0; + + /* if base address set, then only check that address + otherwise, run through the table */ + if (dev->base_addr != 0) { /* dev->base_addr == 0 ==> plain autodetect */ + if (elp_sense(dev) == 0) + return dev->base_addr; + } else + while ((dev->base_addr = addr_list[idx++])) { + if (elp_sense(dev) == 0) + return dev->base_addr; + } + + /* could not find an adapter */ + if (elp_debug > 0) + printk(couldnot_msg, dev->name); + + return 0; /* Because of this, the layer above will return -ENODEV */ +} + + +/****************************************************** + * + * probe for an Etherlink Plus board at the specified address + * + ******************************************************/ + +/* There are three situations we need to be able to detect here: + + * a) the card is idle + * b) the card is still booting up + * c) the card is stuck in a strange state (some DOS drivers do this) + * + * In case (a), all is well. In case (b), we wait 10 seconds to see if the + * card finishes booting, and carry on if so. In case (c), we do a hard reset, + * loop round, and hope for the best. + * + * This is all very unpleasant, but hopefully avoids the problems with the old + * probe code (which had a 15-second delay if the card was idle, and didn't + * work at all if it was in a weird state). + */ + +int elplus_probe(struct device *dev) +{ + elp_device *adapter; + int i, tries, tries1, timeout, okay; + + /* + * setup adapter structure + */ + + dev->base_addr = elp_autodetect(dev); + if (!(dev->base_addr)) + return -ENODEV; + + /* + * setup ptr to adapter specific information + */ + adapter = (elp_device *) (dev->priv = kmalloc(sizeof(elp_device), GFP_KERNEL)); + if (adapter == NULL) { + printk("%s: out of memory\n", dev->name); + return -ENODEV; + } + + for (tries1 = 0; tries1 < 3; tries1++) { + outb_control((inb_control(dev->base_addr) | CMDE) & ~DIR, dev->base_addr); + /* First try to write just one byte, to see if the card is + * responding at all normally. + */ + timeout = jiffies + 5; + okay = 0; + while (jiffies < timeout && !(inb_status(dev->base_addr) & HCRE)); + if ((inb_status(dev->base_addr) & HCRE)) { + outb_command(0, dev->base_addr); /* send a spurious byte */ + timeout = jiffies + 5; + while (jiffies < timeout && !(inb_status(dev->base_addr) & HCRE)); + if (inb_status(dev->base_addr) & HCRE) + okay = 1; + } + if (!okay) { + /* Nope, it's ignoring the command register. This means that + * either it's still booting up, or it's died. + */ + printk("%s: command register wouldn't drain, ", dev->name); + if ((inb_status(dev->base_addr) & 7) == 3) { + /* If the adapter status is 3, it *could* still be booting. + * Give it the benefit of the doubt for 10 seconds. + */ + printk("assuming 3c505 still starting\n"); + timeout = jiffies + 10 * HZ; + while (jiffies < timeout && (inb_status(dev->base_addr) & 7)); + if (inb_status(dev->base_addr) & 7) { + printk("%s: 3c505 failed to start\n", dev->name); + } else { + okay = 1; /* It started */ + } + } else { + /* Otherwise, it must just be in a strange state. We probably + * need to kick it. + */ + printk("3c505 is sulking\n"); + } + } + for (tries = 0; tries < 5 && okay; tries++) { + + /* + * Try to set the Ethernet address, to make sure that the board + * is working. + */ + adapter->tx_pcb.command = CMD_STATION_ADDRESS; + adapter->tx_pcb.length = 0; + autoirq_setup(0); + if (!send_pcb(dev, &adapter->tx_pcb)) { + printk("%s: could not send first PCB\n", dev->name); + autoirq_report(0); + continue; + } + if (!receive_pcb(dev, &adapter->rx_pcb)) { + printk("%s: could not read first PCB\n", dev->name); + autoirq_report(0); + continue; + } + if ((adapter->rx_pcb.command != CMD_ADDRESS_RESPONSE) || + (adapter->rx_pcb.length != 6)) { + printk("%s: first PCB wrong (%d, %d)\n", dev->name, adapter->rx_pcb.command, adapter->rx_pcb.length); + autoirq_report(0); + continue; + } + goto okay; + } + /* It's broken. Do a hard reset to re-initialise the board, + * and try again. + */ + printk(KERN_INFO "%s: resetting adapter\n", dev->name); + outb_control(inb_control(dev->base_addr) | FLSH | ATTN, dev->base_addr); + outb_control(inb_control(dev->base_addr) & ~(FLSH | ATTN), dev->base_addr); + } + printk("%s: failed to initialise 3c505\n", dev->name); + return -ENODEV; + + okay: + if (dev->irq) { /* Is there a preset IRQ? */ + int rpt = autoirq_report(0); + if (dev->irq != rpt) { + printk("%s: warning, irq %d configured but %d detected\n", dev->name, dev->irq, rpt); + return -ENODEV; + } + /* if dev->irq == autoirq_report(0), all is well */ + } else /* No preset IRQ; just use what we can detect */ + dev->irq = autoirq_report(0); + switch (dev->irq) { /* Legal, sane? */ + case 0: + printk("%s: No IRQ reported by autoirq_report().\n", dev->name); + printk("%s: Check the jumpers of your 3c505 board.\n", dev->name); + return -ENODEV; + case 1: + case 6: + case 8: + case 13: + printk("%s: Impossible IRQ %d reported by autoirq_report().\n", + dev->name, dev->irq); + return -ENODEV; + } + /* + * Now we have the IRQ number so we can disable the interrupts from + * the board until the board is opened. + */ + outb_control(inb_control(dev->base_addr) & ~CMDE, dev->base_addr); + + /* + * copy ethernet address into structure + */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = adapter->rx_pcb.data.eth_addr[i]; + + /* set up the DMA channel */ + dev->dma = ELP_DMA; + + /* + * print remainder of startup message + */ + printk("%s: 3c505 at %#lx, irq %d, dma %d, ", + dev->name, dev->base_addr, dev->irq, dev->dma); + printk("addr %02x:%02x:%02x:%02x:%02x:%02x, ", + dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + + /* + * read more information from the adapter + */ + + adapter->tx_pcb.command = CMD_ADAPTER_INFO; + adapter->tx_pcb.length = 0; + if (!send_pcb(dev, &adapter->tx_pcb) || + !receive_pcb(dev, &adapter->rx_pcb) || + (adapter->rx_pcb.command != CMD_ADAPTER_INFO_RESPONSE) || + (adapter->rx_pcb.length != 10)) { + printk("%s: not responding to second PCB\n", dev->name); + } + printk("rev %d.%d, %dk\n", adapter->rx_pcb.data.info.major_vers, adapter->rx_pcb.data.info.minor_vers, adapter->rx_pcb.data.info.RAM_sz); + + /* + * reconfigure the adapter memory to better suit our purposes + */ + adapter->tx_pcb.command = CMD_CONFIGURE_ADAPTER_MEMORY; + adapter->tx_pcb.length = 12; + adapter->tx_pcb.data.memconf.cmd_q = 8; + adapter->tx_pcb.data.memconf.rcv_q = 8; + adapter->tx_pcb.data.memconf.mcast = 10; + adapter->tx_pcb.data.memconf.frame = 10; + adapter->tx_pcb.data.memconf.rcv_b = 10; + adapter->tx_pcb.data.memconf.progs = 0; + if (!send_pcb(dev, &adapter->tx_pcb) || + !receive_pcb(dev, &adapter->rx_pcb) || + (adapter->rx_pcb.command != CMD_CONFIGURE_ADAPTER_RESPONSE) || + (adapter->rx_pcb.length != 2)) { + printk("%s: could not configure adapter memory\n", dev->name); + } + if (adapter->rx_pcb.data.configure) { + printk("%s: adapter configuration failed\n", dev->name); + } + /* + * and reserve the address region + */ + request_region(dev->base_addr, ELP_IO_EXTENT, "3c505"); + + /* + * initialise the device + */ + elp_init(dev); + + return 0; +} + +#ifdef MODULE +static char devicename[9] = {0,}; +static struct device dev_3c505 = +{ + devicename, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, elplus_probe}; + +int io = 0x300; +int irq = 0; + +int init_module(void) +{ + if (io == 0) + printk("3c505: You should not use auto-probing with insmod!\n"); + dev_3c505.base_addr = io; + dev_3c505.irq = irq; + if (register_netdev(&dev_3c505) != 0) { + return -EIO; + } + return 0; +} + +void cleanup_module(void) +{ + unregister_netdev(&dev_3c505); + kfree(dev_3c505.priv); + dev_3c505.priv = NULL; + + /* If we don't do this, we can't re-insmod it later. */ + release_region(dev_3c505.base_addr, ELP_IO_EXTENT); +} + +#endif /* MODULE */ + + +/* + * 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: + */ diff --git a/linux/src/drivers/net/3c505.h b/linux/src/drivers/net/3c505.h new file mode 100644 index 00000000..0598ca25 --- /dev/null +++ b/linux/src/drivers/net/3c505.h @@ -0,0 +1,245 @@ +/***************************************************************** + * + * defines for 3Com Etherlink Plus adapter + * + *****************************************************************/ + +/* + * I/O register offsets + */ +#define PORT_COMMAND 0x00 /* read/write, 8-bit */ +#define PORT_STATUS 0x02 /* read only, 8-bit */ +#define PORT_AUXDMA 0x02 /* write only, 8-bit */ +#define PORT_DATA 0x04 /* read/write, 16-bit */ +#define PORT_CONTROL 0x06 /* read/write, 8-bit */ + +#define ELP_IO_EXTENT 0x10 /* size of used IO registers */ + +/* + * host control registers bits + */ +#define ATTN 0x80 /* attention */ +#define FLSH 0x40 /* flush data register */ +#define DMAE 0x20 /* DMA enable */ +#define DIR 0x10 /* direction */ +#define TCEN 0x08 /* terminal count interrupt enable */ +#define CMDE 0x04 /* command register interrupt enable */ +#define HSF2 0x02 /* host status flag 2 */ +#define HSF1 0x01 /* host status flag 1 */ + +/* + * combinations of HSF flags used for PCB transmission + */ +#define HSF_PCB_ACK HSF1 +#define HSF_PCB_NAK HSF2 +#define HSF_PCB_END (HSF2|HSF1) +#define HSF_PCB_MASK (HSF2|HSF1) + +/* + * host status register bits + */ +#define HRDY 0x80 /* data register ready */ +#define HCRE 0x40 /* command register empty */ +#define ACRF 0x20 /* adapter command register full */ +/* #define DIR 0x10 direction - same as in control register */ +#define DONE 0x08 /* DMA done */ +#define ASF3 0x04 /* adapter status flag 3 */ +#define ASF2 0x02 /* adapter status flag 2 */ +#define ASF1 0x01 /* adapter status flag 1 */ + +/* + * combinations of ASF flags used for PCB reception + */ +#define ASF_PCB_ACK ASF1 +#define ASF_PCB_NAK ASF2 +#define ASF_PCB_END (ASF2|ASF1) +#define ASF_PCB_MASK (ASF2|ASF1) + +/* + * host aux DMA register bits + */ +#define DMA_BRST 0x01 /* DMA burst */ + +/* + * maximum amount of data allowed in a PCB + */ +#define MAX_PCB_DATA 62 + +/***************************************************************** + * + * timeout value + * this is a rough value used for loops to stop them from + * locking up the whole machine in the case of failure or + * error conditions + * + *****************************************************************/ + +#define TIMEOUT 300 + +/***************************************************************** + * + * PCB commands + * + *****************************************************************/ + +enum { + /* + * host PCB commands + */ + CMD_CONFIGURE_ADAPTER_MEMORY = 0x01, + CMD_CONFIGURE_82586 = 0x02, + CMD_STATION_ADDRESS = 0x03, + CMD_DMA_DOWNLOAD = 0x04, + CMD_DMA_UPLOAD = 0x05, + CMD_PIO_DOWNLOAD = 0x06, + CMD_PIO_UPLOAD = 0x07, + CMD_RECEIVE_PACKET = 0x08, + CMD_TRANSMIT_PACKET = 0x09, + CMD_NETWORK_STATISTICS = 0x0a, + CMD_LOAD_MULTICAST_LIST = 0x0b, + CMD_CLEAR_PROGRAM = 0x0c, + CMD_DOWNLOAD_PROGRAM = 0x0d, + CMD_EXECUTE_PROGRAM = 0x0e, + CMD_SELF_TEST = 0x0f, + CMD_SET_STATION_ADDRESS = 0x10, + CMD_ADAPTER_INFO = 0x11, + NUM_TRANSMIT_CMDS, + + /* + * adapter PCB commands + */ + CMD_CONFIGURE_ADAPTER_RESPONSE = 0x31, + CMD_CONFIGURE_82586_RESPONSE = 0x32, + CMD_ADDRESS_RESPONSE = 0x33, + CMD_DOWNLOAD_DATA_REQUEST = 0x34, + CMD_UPLOAD_DATA_REQUEST = 0x35, + CMD_RECEIVE_PACKET_COMPLETE = 0x38, + CMD_TRANSMIT_PACKET_COMPLETE = 0x39, + CMD_NETWORK_STATISTICS_RESPONSE = 0x3a, + CMD_LOAD_MULTICAST_RESPONSE = 0x3b, + CMD_CLEAR_PROGRAM_RESPONSE = 0x3c, + CMD_DOWNLOAD_PROGRAM_RESPONSE = 0x3d, + CMD_EXECUTE_RESPONSE = 0x3e, + CMD_SELF_TEST_RESPONSE = 0x3f, + CMD_SET_ADDRESS_RESPONSE = 0x40, + CMD_ADAPTER_INFO_RESPONSE = 0x41 +}; + +/* Definitions for the PCB data structure */ + +/* Data units */ +typedef unsigned char byte; +typedef unsigned short int word; +typedef unsigned long int dword; + +/* Data structures */ +struct Memconf { + word cmd_q, + rcv_q, + mcast, + frame, + rcv_b, + progs; +}; + +struct Rcv_pkt { + word buf_ofs, + buf_seg, + buf_len, + timeout; +}; + +struct Xmit_pkt { + word buf_ofs, + buf_seg, + pkt_len; +}; + +struct Rcv_resp { + word buf_ofs, + buf_seg, + buf_len, + pkt_len, + timeout, + status; + dword timetag; +}; + +struct Xmit_resp { + word buf_ofs, + buf_seg, + c_stat, + status; +}; + + +struct Netstat { + dword tot_recv, + tot_xmit; + word err_CRC, + err_align, + err_res, + err_ovrrun; +}; + + +struct Selftest { + word error; + union { + word ROM_cksum; + struct { + word ofs, seg; + } RAM; + word i82586; + } failure; +}; + +struct Info { + byte minor_vers, + major_vers; + word ROM_cksum, + RAM_sz, + free_ofs, + free_seg; +}; + +struct Memdump { + word size, + off, + seg; +}; + +/* +Primary Command Block. The most important data structure. All communication +between the host and the adapter is done with these. (Except for the actual +ethernet data, which has different packaging.) +*/ +typedef struct { + byte command; + byte length; + union { + struct Memconf memconf; + word configure; + struct Rcv_pkt rcv_pkt; + struct Xmit_pkt xmit_pkt; + byte multicast[10][6]; + byte eth_addr[6]; + byte failed; + struct Rcv_resp rcv_resp; + struct Xmit_resp xmit_resp; + struct Netstat netstat; + struct Selftest selftest; + struct Info info; + struct Memdump memdump; + byte raw[62]; + } data; +} pcb_struct; + +/* These defines for 'configure' */ +#define RECV_STATION 0x00 +#define RECV_BROAD 0x01 +#define RECV_MULTI 0x02 +#define RECV_PROMISC 0x04 +#define NO_LOOPBACK 0x00 +#define INT_LOOPBACK 0x08 +#define EXT_LOOPBACK 0x10 diff --git a/linux/src/drivers/net/3c507.c b/linux/src/drivers/net/3c507.c new file mode 100644 index 00000000..60054875 --- /dev/null +++ b/linux/src/drivers/net/3c507.c @@ -0,0 +1,923 @@ +/* 3c507.c: An EtherLink16 device driver for Linux. */ +/* + Written 1993,1994 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Thanks go to jennings@Montrouge.SMR.slb.com ( Patrick Jennings) + and jrs@world.std.com (Rick Sladkey) for testing and bugfixes. + Mark Salazar <leslie@access.digex.net> made the changes for cards with + only 16K packet buffers. + + Things remaining to do: + Verify that the tx and rx buffers don't have fencepost errors. + Move the theory of operation and memory map documentation. + The statistics need to be updated correctly. +*/ + +static const char *version = + "3c507.c:v1.10 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + + +#include <linux/module.h> + +/* + 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 :-<. +*/ + +#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/in.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/malloc.h> + + +/* use 0 for production, 1 for verification, 2..7 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 1 +#endif +static unsigned int net_debug = NET_DEBUG; + +/* A zero-terminated list of common I/O addresses to be probed. */ +static unsigned int netcard_portlist[] = + { 0x300, 0x320, 0x340, 0x280, 0}; + +/* + 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. + Both are started from a list of three configuration tables, of which only + the last, the System Control Block (SCB), is used after reset-time. The SCB + has the following fields: + Status word + Command word + Tx/Command block addr. + Rx block addr. + The command word accepts the following controls for the Tx and Rx units: + */ + +#define CUC_START 0x0100 +#define CUC_RESUME 0x0200 +#define CUC_SUSPEND 0x0300 +#define RX_START 0x0010 +#define RX_RESUME 0x0020 +#define RX_SUSPEND 0x0030 + +/* 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. + + 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). + + Some definitions related to the Command Word are: + */ +#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 enet_statistics stats; + int last_restart; + ushort rx_head; + ushort rx_tail; + ushort tx_head; + ushort tx_cmd_link; + ushort tx_reap; +}; + +/* + Details of the EtherLink16 Implementation + The 3c507 is a generic shared-memory i82586 implementation. + The host can map 16K, 32K, 48K, or 64K of the 64K memory into + 0x0[CD][08]0000, or all 64K into 0xF[02468]0000. + */ + +/* Offsets from the base I/O address. */ +#define SA_DATA 0 /* Station address data, or 3Com signature. */ +#define MISC_CTRL 6 /* Switch the SA_DATA banks, and bus config bits. */ +#define RESET_IRQ 10 /* Reset the latched IRQ line. */ +#define SIGNAL_CA 11 /* Frob the 82586 Channel Attention line. */ +#define ROM_CONFIG 13 +#define MEM_CONFIG 14 +#define IRQ_CONFIG 15 +#define EL16_IO_EXTENT 16 + +/* The ID port is used at boot-time to locate the ethercard. */ +#define ID_PORT 0x100 + +/* Offsets to registers in the mailbox (SCB). */ +#define iSCB_STATUS 0x8 +#define iSCB_CMD 0xA +#define iSCB_CBL 0xC /* Command BLock offset. */ +#define iSCB_RFA 0xE /* Rx Frame Area offset. */ + +/* Since the 3c507 maps the shared memory window so that the last byte is + at 82586 address FFFF, the first byte is at 82586 address 0, 16K, 32K, or + 48K corresponding to window sizes of 64K, 48K, 32K and 16K respectively. + We can account for this be setting the 'SBC Base' entry in the ISCP table + below for all the 16 bit offset addresses, and also adding the 'SCB Base' + value to all 24 bit physical addresses (in the SCP table and the TX and RX + Buffer Descriptors). + -Mark + */ +#define SCB_BASE ((unsigned)64*1024 - (dev->mem_end - dev->mem_start)) + +/* + 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. This is designed to be similar to the EtherExpress, + thus the unusual location of the SCB at 0x0008. + + 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, 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 (1518+14+20+16) /* packet+header+TBD */ + +#define RX_BUF_START 0x2000 +#define RX_BUF_SIZE (1518+14+18) /* packet+header+RBD */ +#define RX_BUF_END (dev->mem_end - dev->mem_start) + +/* + 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. + + */ + +unsigned short init_words[] = { + /* System Configuration Pointer (SCP). */ + 0x0000, /* Set bus size to 16 bits. */ + 0,0, /* pad words. */ + 0x0000,0x0000, /* ISCP phys addr, set in init_82586_mem(). */ + + /* Intermediate System Configuration Pointer (ISCP). */ + 0x0001, /* Status word that's cleared when init is done. */ + 0x0008,0,0, /* SCB offset, (skip, skip) */ + + /* System Control Block (SCB). */ + 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. */ + 0, CmdMulticastList, IDLELOOP, 0, +}; + +/* Index to functions, as function prototypes. */ + +extern int el16_probe(struct device *dev); /* Called from Space.c */ + +static int el16_probe1(struct device *dev, int ioaddr); +static int el16_open(struct device *dev); +static int el16_send_packet(struct sk_buff *skb, struct device *dev); +static void el16_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void el16_rx(struct device *dev); +static int el16_close(struct device *dev); +static struct enet_statistics *el16_get_stats(struct device *dev); + +static void hardware_send_packet(struct device *dev, void *buf, short length); +void init_82586_mem(struct device *dev); + + +#ifdef HAVE_DEVLIST +struct netdev_entry netcard_drv = +{"3c507", el16_probe1, EL16_IO_EXTENT, netcard_portlist}; +#endif + +/* 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 +el16_probe(struct device *dev) +{ + int base_addr = dev ? dev->base_addr : 0; + int i; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return el16_probe1(dev, base_addr); + else if (base_addr != 0) + return ENXIO; /* Don't probe at all. */ + + for (i = 0; netcard_portlist[i]; i++) { + int ioaddr = netcard_portlist[i]; + if (check_region(ioaddr, EL16_IO_EXTENT)) + continue; + if (el16_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} + +int el16_probe1(struct device *dev, int ioaddr) +{ + static unsigned char init_ID_done = 0, version_printed = 0; + int i, irq, irqval; + + if (init_ID_done == 0) { + ushort lrs_state = 0xff; + /* Send the ID sequence to the ID_PORT to enable the board(s). */ + outb(0x00, ID_PORT); + for(i = 0; i < 255; i++) { + outb(lrs_state, ID_PORT); + lrs_state <<= 1; + if (lrs_state & 0x100) + lrs_state ^= 0xe7; + } + outb(0x00, ID_PORT); + init_ID_done = 1; + } + + if (inb(ioaddr) == '*' && inb(ioaddr+1) == '3' + && inb(ioaddr+2) == 'C' && inb(ioaddr+3) == 'O') + ; + else + return ENODEV; + + /* Allocate a new 'dev' if needed. */ + if (dev == NULL) + dev = init_etherdev(0, sizeof(struct net_local)); + + if (net_debug && version_printed++ == 0) + printk(version); + + printk("%s: 3c507 at %#x,", dev->name, ioaddr); + + /* We should make a few more checks here, like the first three octets of + the S.A. for the manufacturer's code. */ + + irq = inb(ioaddr + IRQ_CONFIG) & 0x0f; + + irqval = request_irq(irq, &el16_interrupt, 0, "3c507", NULL); + if (irqval) { + printk ("unable to get IRQ %d (irqval=%d).\n", irq, irqval); + return EAGAIN; + } + + /* We've committed to using the board, and can start filling in *dev. */ + request_region(ioaddr, EL16_IO_EXTENT, "3c507"); + dev->base_addr = ioaddr; + + outb(0x01, ioaddr + MISC_CTRL); + for (i = 0; i < 6; i++) { + dev->dev_addr[i] = inb(ioaddr + i); + printk(" %02x", dev->dev_addr[i]); + } + + if ((dev->mem_start & 0xf) > 0) + net_debug = dev->mem_start & 7; + +#ifdef MEM_BASE + dev->mem_start = MEM_BASE; + dev->mem_end = dev->mem_start + 0x10000; +#else + { + int base; + int size; + char mem_config = inb(ioaddr + MEM_CONFIG); + if (mem_config & 0x20) { + size = 64*1024; + base = 0xf00000 + (mem_config & 0x08 ? 0x080000 + : ((mem_config & 3) << 17)); + } else { + size = ((mem_config & 3) + 1) << 14; + base = 0x0c0000 + ( (mem_config & 0x18) << 12); + } + dev->mem_start = base; + dev->mem_end = base + size; + } +#endif + + dev->if_port = (inb(ioaddr + ROM_CONFIG) & 0x80) ? 1 : 0; + dev->irq = inb(ioaddr + IRQ_CONFIG) & 0x0f; + + printk(", IRQ %d, %sternal xcvr, memory %#lx-%#lx.\n", dev->irq, + dev->if_port ? "ex" : "in", dev->mem_start, dev->mem_end-1); + + if (net_debug) + printk(version); + + /* 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)); + + dev->open = el16_open; + dev->stop = el16_close; + dev->hard_start_xmit = el16_send_packet; + dev->get_stats = el16_get_stats; + + ether_setup(dev); /* Generic ethernet behaviour */ + + dev->flags&=~IFF_MULTICAST; /* Multicast doesn't work */ + + return 0; +} + + + +static int +el16_open(struct device *dev) +{ + irq2dev_map[dev->irq] = dev; + + /* Initialize the 82586 memory and start it. */ + init_82586_mem(dev); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + MOD_INC_USE_COUNT; + + return 0; +} + +static int +el16_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + short *shmem = (short*)dev->mem_start; + + 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, + shmem[iSCB_STATUS>>1] & 0x8000 ? "IRQ conflict" : + "network cable problem"); + /* 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"); + shmem[iSCB_CMD>>1] = 0xf000|CUC_START|RX_START; + outb(0, ioaddr + SIGNAL_CA); /* Issue channel-attn. */ + lp->last_restart = lp->stats.tx_packets; + } + 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) { + dev_tint(dev); + 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(0x80, ioaddr + MISC_CTRL); + hardware_send_packet(dev, buf, length); + dev->trans_start = jiffies; + /* Enable the 82586 interrupt input. */ + outb(0x84, ioaddr + MISC_CTRL); + } + + dev_kfree_skb (skb, FREE_WRITE); + + /* You might need to clean up and record Tx statistics here. */ + + return 0; +} + +/* The typical workload of the driver: + Handle the network interface interrupts. */ +static void +el16_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct net_local *lp; + int ioaddr, status, boguscount = 0; + ushort ack_cmd = 0; + ushort *shmem; + + if (dev == NULL) { + printk ("net_interrupt(): irq %d for unknown device.\n", irq); + return; + } + dev->interrupt = 1; + + ioaddr = dev->base_addr; + lp = (struct net_local *)dev->priv; + shmem = ((ushort*)dev->mem_start); + + status = shmem[iSCB_STATUS>>1]; + + if (net_debug > 4) { + printk("%s: 3c507 interrupt, status %4.4x.\n", dev->name, status); + } + + /* Disable the 82586's input to the interrupt line. */ + outb(0x80, ioaddr + MISC_CTRL); + + /* Reap the Tx packet buffers. */ + while (lp->tx_reap != lp->tx_head) { + unsigned short tx_status = shmem[lp->tx_reap>>1]; + + 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 > RX_BUF_START - 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); + el16_rx(dev); + } + + /* Acknowledge the interrupt sources. */ + ack_cmd = status & 0xf000; + + if ((status & 0x0700) != 0x0200 && dev->start) { + if (net_debug) + printk("%s: Command unit stopped, status %04x, restarting.\n", + dev->name, status); + /* If this ever occurs we should really re-write the idle loop, reset + the Tx list, and do a complete restart of the command unit. + For now we rely on the Tx timeout if the resume doesn't work. */ + ack_cmd |= CUC_RESUME; + } + + if ((status & 0x0070) != 0x0040 && dev->start) { + static void init_rx_bufs(struct device *); + /* 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 (net_debug) + printk("%s: Rx unit stopped, status %04x, restarting.\n", + dev->name, status); + init_rx_bufs(dev); + shmem[iSCB_RFA >> 1] = RX_BUF_START; + ack_cmd |= RX_START; + } + + shmem[iSCB_CMD>>1] = ack_cmd; + outb(0, ioaddr + SIGNAL_CA); /* Issue channel-attn. */ + + /* Clear the latched interrupt. */ + outb(0, ioaddr + RESET_IRQ); + + /* Enable the 82586's interrupt input. */ + outb(0x84, ioaddr + MISC_CTRL); + + return; +} + +static int +el16_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + ushort *shmem = (short*)dev->mem_start; + + dev->tbusy = 1; + dev->start = 0; + + /* Flush the Tx and disable Rx. */ + shmem[iSCB_CMD >> 1] = RX_SUSPEND | CUC_SUSPEND; + outb(0, ioaddr + SIGNAL_CA); + + /* Disable the 82586's input to the interrupt line. */ + outb(0x80, ioaddr + MISC_CTRL); + + /* We always physically use the IRQ line, so we don't do free_irq(). + We do remove ourselves from the map. */ + + irq2dev_map[dev->irq] = 0; + + /* Update the statistics here. */ + + MOD_DEC_USE_COUNT; + + return 0; +} + +/* Get the current statistics. This may be called with the card open or + closed. */ +static struct enet_statistics * +el16_get_stats(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + + /* ToDo: decide if there are any useful statistics from the SCB. */ + + return &lp->stats; +} + +/* Initialize the Rx-block list. */ +static void +init_rx_bufs(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + unsigned short *write_ptr; + unsigned short SCB_base = SCB_BASE; + + int cur_rxbuf = lp->rx_head = RX_BUF_START; + + /* Initialize each Rx frame + data buffer. */ + do { /* While there is room for one more. */ + + write_ptr = (unsigned short *)(dev->mem_start + cur_rxbuf); + + *write_ptr++ = 0x0000; /* Status */ + *write_ptr++ = 0x0000; /* Command */ + *write_ptr++ = cur_rxbuf + RX_BUF_SIZE; /* Link */ + *write_ptr++ = cur_rxbuf + 22; /* Buffer offset */ + *write_ptr++ = 0x0000; /* Pad for dest addr. */ + *write_ptr++ = 0x0000; + *write_ptr++ = 0x0000; + *write_ptr++ = 0x0000; /* Pad for source addr. */ + *write_ptr++ = 0x0000; + *write_ptr++ = 0x0000; + *write_ptr++ = 0x0000; /* Pad for protocol. */ + + *write_ptr++ = 0x0000; /* Buffer: Actual count */ + *write_ptr++ = -1; /* Buffer: Next (none). */ + *write_ptr++ = cur_rxbuf + 0x20 + SCB_base; /* Buffer: Address low */ + *write_ptr++ = 0x0000; + /* Finally, the number of bytes in the buffer. */ + *write_ptr++ = 0x8000 + RX_BUF_SIZE-0x20; + + 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. */ + write_ptr = (unsigned short *) + (dev->mem_start + lp->rx_tail + 2); + *write_ptr++ = 0xC000; /* Command, mark as last. */ + *write_ptr++ = lp->rx_head; /* Link */ + +} + +void +init_82586_mem(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + short ioaddr = dev->base_addr; + ushort *shmem = (short*)dev->mem_start; + + /* Enable loopback to protect the wire while starting up, + and hold the 586 in reset during the memory initialization. */ + outb(0x20, ioaddr + MISC_CTRL); + + /* Fix the ISCP address and base. */ + init_words[3] = SCB_BASE; + init_words[7] = SCB_BASE; + + /* Write the words at 0xfff6 (address-aliased to 0xfffff6). */ + memcpy((void*)dev->mem_end-10, init_words, 10); + + /* Write the words at 0x0000. */ + memcpy((char*)dev->mem_start, init_words + 5, sizeof(init_words) - 10); + + /* Fill in the station address. */ + memcpy((char*)dev->mem_start+SA_OFFSET, dev->dev_addr, + sizeof(dev->dev_addr)); + + /* The Tx-block list is written as needed. We just set up the values. */ + lp->tx_cmd_link = IDLELOOP + 4; + lp->tx_head = lp->tx_reap = TX_BUF_START; + + init_rx_bufs(dev); + + /* Start the 586 by releasing the reset line, but leave loopback. */ + outb(0xA0, ioaddr + MISC_CTRL); + + /* 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); + + { + int boguscnt = 50; + while (shmem[iSCB_STATUS>>1] == 0) + if (--boguscnt == 0) { + printk("%s: i82586 initialization timed out with status %04x," + "cmd %04x.\n", dev->name, + shmem[iSCB_STATUS>>1], shmem[iSCB_CMD>>1]); + break; + } + /* Issue channel-attn -- the 82586 won't start. */ + outb(0, ioaddr + SIGNAL_CA); + } + + /* Disable loopback and enable interrupts. */ + outb(0x84, ioaddr + MISC_CTRL); + if (net_debug > 4) + printk("%s: Initialized 82586, status %04x.\n", dev->name, + shmem[iSCB_STATUS>>1]); + return; +} + +static void +hardware_send_packet(struct device *dev, void *buf, short length) +{ + struct net_local *lp = (struct net_local *)dev->priv; + short ioaddr = dev->base_addr; + ushort tx_block = lp->tx_head; + ushort *write_ptr = (ushort *)(dev->mem_start + tx_block); + + /* Set the write pointer to the Tx block, and put out the header. */ + *write_ptr++ = 0x0000; /* Tx status */ + *write_ptr++ = CMD_INTR|CmdTx; /* Tx command */ + *write_ptr++ = tx_block+16; /* Next command is a NoOp. */ + *write_ptr++ = tx_block+8; /* Data Buffer offset. */ + + /* Output the data buffer descriptor. */ + *write_ptr++ = length | 0x8000; /* Byte count parameter. */ + *write_ptr++ = -1; /* No next data buffer. */ + *write_ptr++ = tx_block+22+SCB_BASE;/* Buffer follows the NoOp command. */ + *write_ptr++ = 0x0000; /* Buffer address high bits (always zero). */ + + /* Output the Loop-back NoOp command. */ + *write_ptr++ = 0x0000; /* Tx status */ + *write_ptr++ = CmdNOp; /* Tx command */ + *write_ptr++ = tx_block+16; /* Next is myself. */ + + /* Output the packet at the write pointer. */ + memcpy(write_ptr, buf, length); + + /* Set the old command link pointing to this send packet. */ + *(ushort*)(dev->mem_start + lp->tx_cmd_link) = tx_block; + 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 > RX_BUF_START - TX_BUF_SIZE) + lp->tx_head = TX_BUF_START; + + if (net_debug > 4) { + printk("%s: 3c507 @%x send length = %d, tx_block %3x, next %3x.\n", + dev->name, ioaddr, length, tx_block, lp->tx_head); + } + + if (lp->tx_head != lp->tx_reap) + dev->tbusy = 0; +} + +static void +el16_rx(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + short *shmem = (short*)dev->mem_start; + ushort rx_head = lp->rx_head; + ushort rx_tail = lp->rx_tail; + ushort boguscount = 10; + short frame_status; + + while ((frame_status = shmem[rx_head>>1]) < 0) { /* Command complete */ + ushort *read_frame = (short *)(dev->mem_start + rx_head); + ushort rfd_cmd = read_frame[1]; + ushort next_rx_frame = read_frame[2]; + ushort data_buffer_addr = read_frame[3]; + ushort *data_frame = (short *)(dev->mem_start + data_buffer_addr); + ushort pkt_len = data_frame[0]; + + 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_reserve(skb,2); + skb->dev = dev; + + /* 'skb->data' points to the start of sk_buff data area. */ + memcpy(skb_put(skb,pkt_len), data_frame + 5, pkt_len); + + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + + /* Clear the status word and set End-of-List on the rx frame. */ + read_frame[0] = 0; + read_frame[1] = 0xC000; + /* Clear the end-of-list on the prev. RFD. */ + *(short*)(dev->mem_start + rx_tail + 2) = 0x0000; + + rx_tail = rx_head; + rx_head = next_rx_frame; + if (--boguscount == 0) + break; + } + + lp->rx_head = rx_head; + lp->rx_tail = rx_tail; +} +#ifdef MODULE +static char devicename[9] = { 0, }; +static struct device dev_3c507 = { + devicename, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, el16_probe +}; + +static int io = 0x300; +static int irq = 0; + +int init_module(void) +{ + if (io == 0) + printk("3c507: You should not use auto-probing with insmod!\n"); + dev_3c507.base_addr = io; + dev_3c507.irq = irq; + if (register_netdev(&dev_3c507) != 0) { + printk("3c507: register_netdev() returned non-zero.\n"); + return -EIO; + } + return 0; +} + +void +cleanup_module(void) +{ + unregister_netdev(&dev_3c507); + kfree(dev_3c507.priv); + dev_3c507.priv = NULL; + + /* If we don't do this, we can't re-insmod it later. */ + free_irq(dev_3c507.irq, NULL); + release_region(dev_3c507.base_addr, EL16_IO_EXTENT); +} +#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 3c507.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * c-indent-level: 4 + * End: + */ diff --git a/linux/src/drivers/net/3c509.c b/linux/src/drivers/net/3c509.c new file mode 100644 index 00000000..f8842882 --- /dev/null +++ b/linux/src/drivers/net/3c509.c @@ -0,0 +1,842 @@ +/* 3c509.c: A 3c509 EtherLink3 ethernet driver for linux. */ +/* + Written 1993-1998 by Donald Becker. + + Copyright 1994-1998 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU Public License, + incorporated herein by reference. + + This driver is for the 3Com EtherLinkIII series. + + The author may be reached as becker@cesdis.gsfc.nasa.gov or + C/O Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Known limitations: + Because of the way 3c509 ISA detection works it's difficult to predict + a priori which of several ISA-mode cards will be detected first. + + This driver does not use predictive interrupt mode, resulting in higher + packet latency but lower overhead. If interrupts are disabled for an + unusually long time it could also result in missed packets, but in + practice this rarely happens. + + + FIXES: + Alan Cox: Removed the 'Unexpected interrupt' bug. + Michael Meskes: Upgraded to Donald Becker's version 1.07. + Alan Cox: Increased the eeprom delay. Regardless of + what the docs say some people definitely + get problems with lower (but in card spec) + delays + v1.10 4/21/97 Fixed module code so that multiple cards may be detected, + other cleanups. -djb + v1.13 9/8/97 Made 'max_interrupt_work' an insmod-settable variable -djb + v1.14 10/15/97 Avoided waiting..discard message for fast machines -djb + v1.15 1/31/98 Faster recovery for Tx errors. -djb + v1.16 2/3/98 Different ID port handling to avoid sound cards. -djb +*/ + +static char *version = "3c509.c:1.16 2/3/98 becker@cesdis.gsfc.nasa.gov\n"; +/* A few values that may be tweaked. */ + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT (400*HZ/1000) +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 10; + +#include <linux/module.h> + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/config.h> /* for CONFIG_MCA */ +#include <linux/delay.h> /* for udelay() */ + +#include <asm/bitops.h> +#include <asm/io.h> + +#ifdef EL3_DEBUG +int el3_debug = EL3_DEBUG; +#else +int el3_debug = 2; +#endif + +/* To minimize the size of the driver source I only define operating + constants if they are used several times. You'll need the manual + anyway if you want to understand driver details. */ +/* Offsets from base I/O address. */ +#define EL3_DATA 0x00 +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e +#define EEPROM_READ 0x80 + +#define EL3_IO_EXTENT 16 + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. */ +enum c509cmd { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, + TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11,}; + +enum c509status { + IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000, }; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 }; + +/* Register window 1 offsets, the window used in normal operation. */ +#define TX_FIFO 0x00 +#define RX_FIFO 0x00 +#define RX_STATUS 0x08 +#define TX_STATUS 0x0B +#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */ + +#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */ +#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */ +#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ + +/* + * Must be a power of two (we use a binary and in the + * circular queue) + */ +#define SKB_QUEUE_SIZE 64 + +struct el3_private { + struct enet_statistics stats; + struct device *next_dev; + /* skb send-queue */ + int head, size; + struct sk_buff *queue[SKB_QUEUE_SIZE]; +}; +static int id_port = 0x110; /* Start with 0x110 to avoid new sound cards.*/ +static struct device *el3_root_dev = NULL; + +static ushort id_read_eeprom(int index); +static ushort read_eeprom(int ioaddr, int index); +static int el3_open(struct device *dev); +static int el3_start_xmit(struct sk_buff *skb, struct device *dev); +static void el3_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void update_stats(int addr, struct device *dev); +static struct enet_statistics *el3_get_stats(struct device *dev); +static int el3_rx(struct device *dev); +static int el3_close(struct device *dev); +static void set_multicast_list(struct device *dev); + + + +int el3_probe(struct device *dev) +{ + short lrs_state = 0xff, i; + int ioaddr, irq, if_port; + u16 phys_addr[3]; + static int current_tag = 0; + + /* First check all slots of the EISA bus. The next slot address to + probe is kept in 'eisa_addr' to support multiple probe() calls. */ + if (EISA_bus) { + static int eisa_addr = 0x1000; + while (eisa_addr < 0x9000) { + ioaddr = eisa_addr; + eisa_addr += 0x1000; + + /* Check the standard EISA ID register for an encoded '3Com'. */ + if (inw(ioaddr + 0xC80) != 0x6d50) + continue; + + /* Change the register set to the configuration window 0. */ + outw(SelectWindow | 0, ioaddr + 0xC80 + EL3_CMD); + + irq = inw(ioaddr + WN0_IRQ) >> 12; + if_port = inw(ioaddr + 6)>>14; + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i)); + + /* Restore the "Product ID" to the EEPROM read register. */ + read_eeprom(ioaddr, 3); + + /* Was the EISA code an add-on hack? Nahhhhh... */ + goto found; + } + } + +#ifdef CONFIG_MCA + if (MCA_bus) { + mca_adaptor_select_mode(1); + for (i = 0; i < 8; i++) + if ((mca_adaptor_id(i) | 1) == 0x627c) { + ioaddr = mca_pos_base_addr(i); + irq = inw(ioaddr + WN0_IRQ) >> 12; + if_port = inw(ioaddr + 6)>>14; + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i)); + + mca_adaptor_select_mode(0); + goto found; + } + mca_adaptor_select_mode(0); + + } +#endif + + /* Reset the ISA PnP mechanism on 3c509b. */ + outb(0x02, 0x279); /* Select PnP config control register. */ + outb(0x02, 0xA79); /* Return to WaitForKey state. */ + /* Select an open I/O location at 0x1*0 to do contention select. */ + for ( ; id_port < 0x200; id_port += 0x10) { + if (check_region(id_port, 1)) + continue; + outb(0x00, id_port); + outb(0xff, id_port); + if (inb(id_port) & 0x01) + break; + } + if (id_port >= 0x200) { /* GCC optimizes this test out. */ + /* Rare -- do we really need a warning? */ + printk(" WARNING: No I/O port available for 3c509 activation.\n"); + return -ENODEV; + } + /* Next check for all ISA bus boards by sending the ID sequence to the + ID_PORT. We find cards past the first by setting the 'current_tag' + on cards as they are found. Cards with their tag set will not + respond to subsequent ID sequences. */ + + outb(0x00, id_port); + outb(0x00, id_port); + for(i = 0; i < 255; i++) { + outb(lrs_state, id_port); + lrs_state <<= 1; + lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state; + } + + /* For the first probe, clear all board's tag registers. */ + if (current_tag == 0) + outb(0xd0, id_port); + else /* Otherwise kill off already-found boards. */ + outb(0xd8, id_port); + + if (id_read_eeprom(7) != 0x6d50) { + return -ENODEV; + } + + /* Read in EEPROM data, which does contention-select. + Only the lowest address board will stay "on-line". + 3Com got the byte order backwards. */ + for (i = 0; i < 3; i++) { + phys_addr[i] = htons(id_read_eeprom(i)); + } + + { + unsigned int iobase = id_read_eeprom(8); + if_port = iobase >> 14; + ioaddr = 0x200 + ((iobase & 0x1f) << 4); + } + irq = id_read_eeprom(9) >> 12; + + if (dev) { /* Set passed-in IRQ or I/O Addr. */ + if (dev->irq > 1 && dev->irq < 16) + irq = dev->irq; + + if (dev->base_addr) { + if (dev->mem_end == 0x3c509 /* Magic key */ + && dev->base_addr >= 0x200 && dev->base_addr <= 0x3e0) + ioaddr = dev->base_addr & 0x3f0; + else if (dev->base_addr != ioaddr) + return -ENODEV; + } + } + + /* Set the adaptor tag so that the next card can be found. */ + outb(0xd0 + ++current_tag, id_port); + + /* Activate the adaptor at the EEPROM location. */ + outb((ioaddr >> 4) | 0xe0, id_port); + + EL3WINDOW(0); + if (inw(ioaddr) != 0x6d50) + return -ENODEV; + + /* Free the interrupt so that some other card can use it. */ + outw(0x0f00, ioaddr + WN0_IRQ); + found: + if (dev == NULL) { + dev = init_etherdev(dev, sizeof(struct el3_private)); + } + memcpy(dev->dev_addr, phys_addr, sizeof(phys_addr)); + dev->base_addr = ioaddr; + dev->irq = irq; + dev->if_port = (dev->mem_start & 0x1f) ? dev->mem_start & 3 : if_port; + + request_region(dev->base_addr, EL3_IO_EXTENT, "3c509"); + + { + const char *if_names[] = {"10baseT", "AUI", "undefined", "BNC"}; + printk("%s: 3c509 at %#3.3lx tag %d, %s port, address ", + dev->name, dev->base_addr, current_tag, if_names[dev->if_port]); + } + + /* Read in the station address. */ + for (i = 0; i < 6; i++) + printk(" %2.2x", dev->dev_addr[i]); + printk(", IRQ %d.\n", dev->irq); + + /* Make up a EL3-specific-data structure. */ + if (dev->priv == NULL) + dev->priv = kmalloc(sizeof(struct el3_private), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + memset(dev->priv, 0, sizeof(struct el3_private)); + + ((struct el3_private *)dev->priv)->next_dev = el3_root_dev; + el3_root_dev = dev; + + if (el3_debug > 0) + printk(version); + + /* The EL3-specific entries in the device structure. */ + dev->open = &el3_open; + dev->hard_start_xmit = &el3_start_xmit; + dev->stop = &el3_close; + dev->get_stats = &el3_get_stats; + dev->set_multicast_list = &set_multicast_list; + + /* Fill in the generic fields of the device structure. */ + ether_setup(dev); + return 0; +} + +/* Read a word from the EEPROM using the regular EEPROM access register. + Assume that we are in register window zero. + */ +static ushort read_eeprom(int ioaddr, int index) +{ + outw(EEPROM_READ + index, ioaddr + 10); + /* Pause for at least 162 us. for the read to take place. */ + udelay (500); + return inw(ioaddr + 12); +} + +/* Read a word from the EEPROM when in the ISA ID probe state. */ +static ushort id_read_eeprom(int index) +{ + int bit, word = 0; + + /* Issue read command, and pause for at least 162 us. for it to complete. + Assume extra-fast 16Mhz bus. */ + outb(EEPROM_READ + index, id_port); + + /* Pause for at least 162 us. for the read to take place. */ + udelay (500); + + for (bit = 15; bit >= 0; bit--) + word = (word << 1) + (inb(id_port) & 0x01); + + if (el3_debug > 3) + printk(" 3c509 EEPROM word %d %#4.4x.\n", index, word); + + return word; +} + + + +static int +el3_open(struct device *dev) +{ + int ioaddr = dev->base_addr; + int i; + + outw(TxReset, ioaddr + EL3_CMD); + outw(RxReset, ioaddr + EL3_CMD); + outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + + if (request_irq(dev->irq, &el3_interrupt, 0, "3c509", dev)) { + return -EAGAIN; + } + + EL3WINDOW(0); + if (el3_debug > 3) + printk("%s: Opening, IRQ %d status@%x %4.4x.\n", dev->name, + dev->irq, ioaddr + EL3_STATUS, inw(ioaddr + EL3_STATUS)); + + /* Activate board: this is probably unnecessary. */ + outw(0x0001, ioaddr + 4); + + /* Set the IRQ line. */ + outw((dev->irq << 12) | 0x0f00, ioaddr + WN0_IRQ); + + /* Set the station address in window 2 each time opened. */ + EL3WINDOW(2); + + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + + if (dev->if_port == 3) + /* Start the thinnet transceiver. We should really wait 50ms...*/ + outw(StartCoax, ioaddr + EL3_CMD); + else if (dev->if_port == 0) { + /* 10baseT interface, enabled link beat and jabber check. */ + EL3WINDOW(4); + outw(inw(ioaddr + WN4_MEDIA) | MEDIA_TP, ioaddr + WN4_MEDIA); + } + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 9; i++) + inb(ioaddr + i); + inw(ioaddr + 10); + inw(ioaddr + 12); + + /* Switch to register set 1 for normal use. */ + EL3WINDOW(1); + + /* Accept b-case and phys addr only. */ + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + + dev->interrupt = 0; + dev->tbusy = 0; + dev->start = 1; + + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch|TxAvailable|TxComplete|RxComplete|StatsFull, + ioaddr + EL3_CMD); + + if (el3_debug > 3) + printk("%s: Opened 3c509 IRQ %d status %4.4x.\n", + dev->name, dev->irq, inw(ioaddr + EL3_STATUS)); + + MOD_INC_USE_COUNT; + return 0; /* Always succeed */ +} + +static int +el3_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + int ioaddr = dev->base_addr; + + /* Transmitter timeout, serious problems. */ + if (dev->tbusy) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < TX_TIMEOUT) + return 1; + printk("%s: transmit timed out, Tx_status %2.2x status %4.4x " + "Tx FIFO room %d.\n", + dev->name, inb(ioaddr + TX_STATUS), inw(ioaddr + EL3_STATUS), + inw(ioaddr + TX_FREE)); + lp->stats.tx_errors++; + dev->trans_start = jiffies; + /* Issue TX_RESET and TX_START commands. */ + outw(TxReset, ioaddr + EL3_CMD); + outw(TxEnable, ioaddr + EL3_CMD); + dev->tbusy = 0; + } + + if (el3_debug > 4) { + printk("%s: el3_start_xmit(length = %ld) called, status %4.4x.\n", + dev->name, skb->len, inw(ioaddr + EL3_STATUS)); + } +#if 0 +#ifndef final_version + { /* Error-checking code, delete someday. */ + ushort status = inw(ioaddr + EL3_STATUS); + if (status & 0x0001 /* IRQ line active, missed one. */ + && inw(ioaddr + EL3_STATUS) & 1) { /* Make sure. */ + printk("%s: Missed interrupt, status then %04x now %04x" + " Tx %2.2x Rx %4.4x.\n", dev->name, status, + inw(ioaddr + EL3_STATUS), inb(ioaddr + TX_STATUS), + inw(ioaddr + RX_STATUS)); + /* Fake interrupt trigger by masking, acknowledge interrupts. */ + outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + } + } +#endif +#endif + /* Avoid timer-based retransmission conflicts. */ + if (set_bit(0, (void*)&dev->tbusy) != 0) + printk("%s: Transmitter access conflict.\n", dev->name); + else { + /* Put out the doubleword header... */ + outw(skb->len, ioaddr + TX_FIFO); + outw(0x00, ioaddr + TX_FIFO); + /* ... and the packet rounded to a doubleword. */ +#ifdef __powerpc__ + outsl_unswapped(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); +#else + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); +#endif + + dev->trans_start = jiffies; + if (inw(ioaddr + TX_FREE) > 1536) { + dev->tbusy = 0; + } else + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); + } + + dev_kfree_skb (skb, FREE_WRITE); + + /* Clear the Tx status stack. */ + { + short tx_status; + int i = 4; + + while (--i > 0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { + if (tx_status & 0x38) lp->stats.tx_aborted_errors++; + if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); + if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); + outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + } + } + return 0; +} + +/* The EL3 interrupt handler. */ +static void +el3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + int ioaddr, status; + int i = max_interrupt_work; + + if (dev == NULL) { + printk ("el3_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + if (dev->interrupt) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + dev->interrupt = 1; + + ioaddr = dev->base_addr; + status = inw(ioaddr + EL3_STATUS); + + if (el3_debug > 4) + printk("%s: interrupt, status %4.4x.\n", dev->name, status); + + while ((status = inw(ioaddr + EL3_STATUS)) & + (IntLatch | RxComplete | StatsFull)) { + + if (status & RxComplete) + el3_rx(dev); + + if (status & TxAvailable) { + if (el3_debug > 5) + printk(" TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + dev->tbusy = 0; + mark_bh(NET_BH); + } + if (status & (AdapterFailure | RxEarly | StatsFull | TxComplete)) { + /* Handle all uncommon interrupts. */ + if (status & StatsFull) /* Empty statistics. */ + update_stats(ioaddr, dev); + if (status & RxEarly) { /* Rx early is unused. */ + el3_rx(dev); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & TxComplete) { /* Really Tx error. */ + struct el3_private *lp = (struct el3_private *)dev->priv; + short tx_status; + int i = 4; + + while (--i>0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { + if (tx_status & 0x38) lp->stats.tx_aborted_errors++; + if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); + if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); + outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + } + } + if (status & AdapterFailure) { + /* Adapter failure requires Rx reset and reinit. */ + outw(RxReset, ioaddr + EL3_CMD); + /* Set the Rx filter to the current state. */ + outw(SetRxFilter | RxStation | RxBroadcast + | (dev->flags & IFF_ALLMULTI ? RxMulticast : 0) + | (dev->flags & IFF_PROMISC ? RxProm : 0), + ioaddr + EL3_CMD); + outw(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */ + outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + } + } + + if (--i < 0) { + printk("%s: Infinite loop in interrupt, status %4.4x.\n", + dev->name, status); + /* Clear all interrupts. */ + outw(AckIntr | 0xFF, ioaddr + EL3_CMD); + break; + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); /* Ack IRQ */ + } + + if (el3_debug > 4) { + printk("%s: exiting interrupt, status %4.4x.\n", dev->name, + inw(ioaddr + EL3_STATUS)); + } + + dev->interrupt = 0; + return; +} + + +static struct enet_statistics * +el3_get_stats(struct device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + unsigned long flags; + + save_flags(flags); + cli(); + update_stats(dev->base_addr, dev); + restore_flags(flags); + return &lp->stats; +} + +/* Update statistics. We change to register window 6, so this should be run + single-threaded if the device is active. This is expected to be a rare + operation, and it's simpler for the rest of the driver to assume that + window 1 is always valid rather than use a special window-state variable. + */ +static void update_stats(int ioaddr, struct device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + + if (el3_debug > 5) + printk(" Updating the statistics.\n"); + /* Turn off statistics updates while reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + lp->stats.tx_carrier_errors += inb(ioaddr + 0); + lp->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + lp->stats.collisions += inb(ioaddr + 3); + lp->stats.tx_window_errors += inb(ioaddr + 4); + lp->stats.rx_fifo_errors += inb(ioaddr + 5); + lp->stats.tx_packets += inb(ioaddr + 6); + /* Rx packets */ inb(ioaddr + 7); + /* Tx deferrals */ inb(ioaddr + 8); + inw(ioaddr + 10); /* Total Rx and Tx octets. */ + inw(ioaddr + 12); + + /* Back to window 1, and turn statistics back on. */ + EL3WINDOW(1); + outw(StatsEnable, ioaddr + EL3_CMD); + return; +} + +static int +el3_rx(struct device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + int ioaddr = dev->base_addr; + short rx_status; + + if (el3_debug > 5) + printk(" In rx_packet(), status %4.4x, rx_status %4.4x.\n", + inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); + while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) { + if (rx_status & 0x4000) { /* Error, update stats. */ + short error = rx_status & 0x3800; + + outw(RxDiscard, ioaddr + EL3_CMD); + lp->stats.rx_errors++; + switch (error) { + case 0x0000: lp->stats.rx_over_errors++; break; + case 0x0800: lp->stats.rx_length_errors++; break; + case 0x1000: lp->stats.rx_frame_errors++; break; + case 0x1800: lp->stats.rx_length_errors++; break; + case 0x2000: lp->stats.rx_frame_errors++; break; + case 0x2800: lp->stats.rx_crc_errors++; break; + } + } else { + short pkt_len = rx_status & 0x7ff; + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+5); + if (el3_debug > 4) + printk("Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); /* Align IP on 16 byte */ + + /* 'skb->data' points to the start of sk_buff data area. */ +#ifdef __powerpc__ + insl_unswapped(ioaddr+RX_FIFO, skb_put(skb,pkt_len), + (pkt_len + 3) >> 2); +#else + insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len), + (pkt_len + 3) >> 2); +#endif + + outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ + skb->protocol = eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + continue; + } + outw(RxDiscard, ioaddr + EL3_CMD); + lp->stats.rx_dropped++; + if (el3_debug) + printk("%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, pkt_len); + } + inw(ioaddr + EL3_STATUS); /* Delay. */ + while (inw(ioaddr + EL3_STATUS) & 0x1000) + printk(" Waiting for 3c509 to discard packet, status %x.\n", + inw(ioaddr + EL3_STATUS) ); + } + + return 0; +} + +/* + * Set or clear the multicast filter for this adaptor. + */ +static void +set_multicast_list(struct device *dev) +{ + int ioaddr = dev->base_addr; + if (el3_debug > 1) { + static int old = 0; + if (old != dev->mc_count) { + old = dev->mc_count; + printk("%s: Setting Rx mode to %d addresses.\n", dev->name, dev->mc_count); + } + } + if (dev->flags&IFF_PROMISC) { + outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, + ioaddr + EL3_CMD); + } + else if (dev->mc_count || (dev->flags&IFF_ALLMULTI)) { + outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast, ioaddr + EL3_CMD); + } + else + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); +} + +static int +el3_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + + if (el3_debug > 2) + printk("%s: Shutting down ethercard.\n", dev->name); + + dev->tbusy = 1; + dev->start = 0; + + /* Turn off statistics ASAP. We update lp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + if (dev->if_port == 3) + /* Turn off thinnet power. Green! */ + outw(StopCoax, ioaddr + EL3_CMD); + else if (dev->if_port == 0) { + /* Disable link beat and jabber, if_port may change ere next open(). */ + EL3WINDOW(4); + outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA); + } + + free_irq(dev->irq, dev); + /* Switching back to window 0 disables the IRQ. */ + EL3WINDOW(0); + /* But we explicitly zero the IRQ line select anyway. */ + outw(0x0f00, ioaddr + WN0_IRQ); + + update_stats(ioaddr, dev); + MOD_DEC_USE_COUNT; + return 0; +} + +#ifdef MODULE +/* Parameters that may be passed into the module. */ +static int debug = -1; +static int irq[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +static int xcvr[] = {-1, -1, -1, -1, -1, -1, -1, -1}; + +int +init_module(void) +{ + int el3_cards = 0; + + if (debug >= 0) + el3_debug = debug; + + el3_root_dev = NULL; + while (el3_probe(0) == 0) { + if (irq[el3_cards] > 1) + el3_root_dev->irq = irq[el3_cards]; + if (xcvr[el3_cards] >= 0) + el3_root_dev->if_port = xcvr[el3_cards]; + el3_cards++; + } + + return el3_cards ? 0 : -ENODEV; +} + +void +cleanup_module(void) +{ + struct device *next_dev; + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (el3_root_dev) { + next_dev = ((struct el3_private *)el3_root_dev->priv)->next_dev; + unregister_netdev(el3_root_dev); + release_region(el3_root_dev->base_addr, EL3_IO_EXTENT); + kfree(el3_root_dev); + el3_root_dev = next_dev; + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c 3c509.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/3c515.c b/linux/src/drivers/net/3c515.c new file mode 100644 index 00000000..a283babd --- /dev/null +++ b/linux/src/drivers/net/3c515.c @@ -0,0 +1,1501 @@ +/* 3c515.c: A 3Com ISA EtherLink XL "Corkscrew" ethernet driver for linux. */ +/* + Written 1997-1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + This driver is for the 3Com ISA EtherLink XL "Corkscrew" 3c515 ethercard. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 +*/ + +static char *version = "3c515.c:v0.99 4/7/98 becker@cesdis.gsfc.nasa.gov\n"; +#define CORKSCREW 1 + +/* "Knobs" that adjust features and parameters. */ +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + Setting to > 1512 effectively disables this feature. */ +static const int rx_copybreak = 200; +/* Allow setting MTU to a larger size, bypassing the normal ethernet setup. */ +static const int mtu = 1500; +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 20; + +/* Enable the automatic media selection code -- usually set. */ +#define AUTOMEDIA 1 + +/* Allow the use of fragment bus master transfers instead of only + programmed-I/O for Vortex cards. Full-bus-master transfers are always + enabled by default on Boomerang cards. If VORTEX_BUS_MASTER is defined, + the feature may be turned on using 'options'. */ +#define VORTEX_BUS_MASTER + +/* A few values that may be tweaked. */ +/* Keep the ring sizes a power of two for efficiency. */ +#define TX_RING_SIZE 16 +#define RX_RING_SIZE 16 +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <linux/timer.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#if (LINUX_VERSION_CODE >= 0x10344) +#define NEW_MULTICAST +#include <linux/delay.h> +#else +#define udelay(microsec) do { int _i = 4*microsec; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0) +#endif + +/* Kernel version compatibility functions. */ +#define RUN_AT(x) (jiffies + (x)) +#define DEV_ALLOC_SKB(len) dev_alloc_skb(len + 2) + +#define FREE_IRQ(irqnum, dev) free_irq(irqnum, dev) +#define REQUEST_IRQ(i,h,f,n, instance) request_irq(i,h,f,n, instance) +#define IRQ(irq, dev_id, pt_regs) (irq, dev_id, pt_regs) + +#if (LINUX_VERSION_CODE < 0x20123) +#define test_and_set_bit(val, addr) set_bit(val, addr) +#elif defined(MODULE) +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("3Com 3c515 Corkscrew driver"); +MODULE_PARM(debug, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(max_interrupt_work, "i"); +#endif + +/* "Knobs" for adjusting internal parameters. */ +/* Put out somewhat more debugging messages. (0 - no msg, 1 minimal msgs). */ +#define DRIVER_DEBUG 1 +/* Some values here only for performance evaluation and path-coverage + debugging. */ +static int rx_nocopy = 0, rx_copy = 0, queued_packet = 0; + +/* Number of times to check to see if the Tx FIFO has space, used in some + limited cases. */ +#define WAIT_TX_AVAIL 200 + +/* Operational parameter that usually are not changed. */ +#define TX_TIMEOUT 40 /* Time in jiffies before concluding Tx hung */ + +/* The size here is somewhat misleading: the Corkscrew also uses the ISA + aliased registers at <base>+0x400. + */ +#define CORKSCREW_TOTAL_SIZE 0x20 + +#ifdef HAVE_DEVLIST +struct netdev_entry tc515_drv = +{"3c515", tc515_probe, CORKSCREW_TOTAL_SIZE, NULL}; +#endif + +#ifdef DRIVER_DEBUG +int vortex_debug = DRIVER_DEBUG; +#else +int vortex_debug = 1; +#endif + +#define CORKSCREW_ID 10 + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the 3Com 3c515 ISA Fast EtherLink XL, +3Com's ISA bus adapter for Fast Ethernet. Due to the unique I/O port layout, +it's not practical to integrate this driver with the other EtherLink drivers. + +II. Board-specific settings + +The Corkscrew has an EEPROM for configuration, but no special settings are +needed for Linux. + +III. Driver operation + +The 3c515 series use an interface that's very similar to the 3c900 "Boomerang" +PCI cards, with the bus master interface extensively modified to work with +the ISA bus. + +The card is capable of full-bus-master transfers with separate +lists of transmit and receive descriptors, similar to the AMD LANCE/PCnet, +DEC Tulip and Intel Speedo3. + +This driver uses a "RX_COPYBREAK" scheme rather than a fixed intermediate +receive buffer. This scheme allocates full-sized skbuffs as receive +buffers. The value RX_COPYBREAK is used as the copying breakpoint: it is +chosen to trade-off the memory wasted by passing the full-sized skbuff to +the queue layer for all frames vs. the copying cost of copying a frame to a +correctly-sized skbuff. + + +IIIC. Synchronization +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +IV. Notes + +Thanks to Terry Murphy of 3Com for providing documentation and a development +board. + +The names "Vortex", "Boomerang" and "Corkscrew" are the internal 3Com +project names. I use these names to eliminate confusion -- 3Com product +numbers and names are very similar and often confused. + +The new chips support both ethernet (1.5K) and FDDI (4.5K) frame sizes! +This driver only supports ethernet frames because of the recent MTU limit +of 1.5K, but the changes to support 4.5K are minimal. +*/ + +/* Operational definitions. + These are not used by other compilation units and thus are not + exported in a ".h" file. + + First the windows. There are eight register windows, with the command + and status registers available in each. + */ +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. + Note that 11 parameters bits was fine for ethernet, but the new chips + can handle FDDI length frames (~4500 octets) and now parameters count + 32-bit 'Dwords' rather than octets. */ + +enum vortex_cmd { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, + UpStall = 6<<11, UpUnstall = (6<<11)+1, + DownStall = (6<<11)+2, DownUnstall = (6<<11)+3, + RxDiscard = 8<<11, TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, + StartDMAUp = 20<<11, StartDMADown = (20<<11)+1, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11,}; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 }; + +/* Bits in the general status register. */ +enum vortex_status { + IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, + DMADone = 1<<8, DownComplete = 1<<9, UpComplete = 1<<10, + DMAInProgress = 1<<11, /* DMA controller is still busy.*/ + CmdInProgress = 1<<12, /* EL3_CMD is still busy.*/ +}; + +/* Register window 1 offsets, the window used in normal operation. + On the Corkscrew this window is always mapped at offsets 0x10-0x1f. */ +enum Window1 { + TX_FIFO = 0x10, RX_FIFO = 0x10, RxErrors = 0x14, + RxStatus = 0x18, Timer=0x1A, TxStatus = 0x1B, + TxFree = 0x1C, /* Remaining free bytes in Tx buffer. */ +}; +enum Window0 { + Wn0IRQ = 0x08, +#if defined(CORKSCREW) + Wn0EepromCmd = 0x200A, /* Corkscrew EEPROM command register. */ + Wn0EepromData = 0x200C, /* Corkscrew EEPROM results register. */ +#else + Wn0EepromCmd = 10, /* Window 0: EEPROM command register. */ + Wn0EepromData = 12, /* Window 0: EEPROM results register. */ +#endif +}; +enum Win0_EEPROM_bits { + EEPROM_Read = 0x80, EEPROM_WRITE = 0x40, EEPROM_ERASE = 0xC0, + EEPROM_EWENB = 0x30, /* Enable erasing/writing for 10 msec. */ + EEPROM_EWDIS = 0x00, /* Disable EWENB before 10 msec timeout. */ +}; +/* EEPROM locations. */ +enum eeprom_offset { + PhysAddr01=0, PhysAddr23=1, PhysAddr45=2, ModelID=3, + EtherLink3ID=7, }; + +enum Window3 { /* Window 3: MAC/config bits. */ + Wn3_Config=0, Wn3_MAC_Ctrl=6, Wn3_Options=8, +}; +union wn3_config { + int i; + struct w3_config_fields { + unsigned int ram_size:3, ram_width:1, ram_speed:2, rom_size:2; + int pad8:8; + unsigned int ram_split:2, pad18:2, xcvr:3, pad21:1, autoselect:1; + int pad24:7; + } u; +}; + +enum Window4 { + Wn4_NetDiag = 6, Wn4_Media = 10, /* Window 4: Xcvr/media bits. */ +}; +enum Win4_Media_bits { + Media_SQE = 0x0008, /* Enable SQE error counting for AUI. */ + Media_10TP = 0x00C0, /* Enable link beat and jabber for 10baseT. */ + Media_Lnk = 0x0080, /* Enable just link beat for 100TX/100FX. */ + Media_LnkBeat = 0x0800, +}; +enum Window7 { /* Window 7: Bus Master control. */ + Wn7_MasterAddr = 0, Wn7_MasterLen = 6, Wn7_MasterStatus = 12, +}; +/* Boomerang-style bus master control registers. Note ISA aliases! */ +enum MasterCtrl { + PktStatus = 0x400, DownListPtr = 0x404, FragAddr = 0x408, FragLen = 0x40c, + TxFreeThreshold = 0x40f, UpPktStatus = 0x410, UpListPtr = 0x418, +}; + +/* The Rx and Tx descriptor lists. + Caution Alpha hackers: these types are 32 bits! Note also the 8 byte + alignment contraint on tx_ring[] and rx_ring[]. */ +struct boom_rx_desc { + u32 next; + s32 status; + u32 addr; + s32 length; +}; +/* Values for the Rx status entry. */ +enum rx_desc_status { + RxDComplete=0x00008000, RxDError=0x4000, + /* See boomerang_rx() for actual error bits */ +}; + +struct boom_tx_desc { + u32 next; + s32 status; + u32 addr; + s32 length; +}; + +struct vortex_private { + char devname[8]; /* "ethN" string, also for kernel debug. */ + const char *product_name; + struct device *next_module; + /* The Rx and Tx rings are here to keep them quad-word-aligned. */ + struct boom_rx_desc rx_ring[RX_RING_SIZE]; + struct boom_tx_desc tx_ring[TX_RING_SIZE]; + /* The addresses of transmit- and receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + unsigned int cur_rx, cur_tx; /* The next free ring entry */ + unsigned int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + struct enet_statistics stats; + struct sk_buff *tx_skb; /* Packet being eaten by bus master ctrl. */ + struct timer_list timer; /* Media selection timer. */ + int capabilities; /* Adapter capabilities word. */ + int options; /* User-settable misc. driver options. */ + int last_rx_packets; /* For media autoselection. */ + unsigned int available_media:8, /* From Wn3_Options */ + media_override:3, /* Passed-in media type. */ + default_media:3, /* Read from the EEPROM. */ + full_duplex:1, autoselect:1, + bus_master:1, /* Vortex can only do a fragment bus-m. */ + full_bus_master_tx:1, full_bus_master_rx:1, /* Boomerang */ + tx_full:1; +}; + +/* The action to take with a media selection timer tick. + Note that we deviate from the 3Com order by checking 10base2 before AUI. + */ +enum xcvr_types { + XCVR_10baseT=0, XCVR_AUI, XCVR_10baseTOnly, XCVR_10base2, XCVR_100baseTx, + XCVR_100baseFx, XCVR_MII=6, XCVR_Default=8, +}; + +static struct media_table { + char *name; + unsigned int media_bits:16, /* Bits to set in Wn4_Media register. */ + mask:8, /* The transceiver-present bit in Wn3_Config.*/ + next:8; /* The media type to try next. */ + short wait; /* Time before we check media status. */ +} media_tbl[] = { + { "10baseT", Media_10TP,0x08, XCVR_10base2, (14*HZ)/10}, + { "10Mbs AUI", Media_SQE, 0x20, XCVR_Default, (1*HZ)/10}, + { "undefined", 0, 0x80, XCVR_10baseT, 10000}, + { "10base2", 0, 0x10, XCVR_AUI, (1*HZ)/10}, + { "100baseTX", Media_Lnk, 0x02, XCVR_100baseFx, (14*HZ)/10}, + { "100baseFX", Media_Lnk, 0x04, XCVR_MII, (14*HZ)/10}, + { "MII", 0, 0x40, XCVR_10baseT, 3*HZ }, + { "undefined", 0, 0x01, XCVR_10baseT, 10000}, + { "Default", 0, 0xFF, XCVR_10baseT, 10000}, +}; + +static int vortex_scan(struct device *dev); +static struct device *vortex_found_device(struct device *dev, int ioaddr, + int irq, int product_index, + int options); +static int vortex_probe1(struct device *dev); +static int vortex_open(struct device *dev); +static void vortex_timer(unsigned long arg); +static int vortex_start_xmit(struct sk_buff *skb, struct device *dev); +static int vortex_rx(struct device *dev); +static int boomerang_rx(struct device *dev); +static void vortex_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs); +static int vortex_close(struct device *dev); +static void update_stats(int addr, struct device *dev); +static struct enet_statistics *vortex_get_stats(struct device *dev); +static void set_rx_mode(struct device *dev); + + +/* Unlike the other PCI cards the 59x cards don't need a large contiguous + memory region, so making the driver a loadable module is feasible. + + Unfortunately maximizing the shared code between the integrated and + module version of the driver results in a complicated set of initialization + procedures. + init_module() -- modules / tc59x_init() -- built-in + The wrappers for vortex_scan() + vortex_scan() The common routine that scans for PCI and EISA cards + vortex_found_device() Allocate a device structure when we find a card. + Different versions exist for modules and built-in. + vortex_probe1() Fill in the device structure -- this is separated + so that the modules code can put it in dev->init. +*/ +/* This driver uses 'options' to pass the media type, full-duplex flag, etc. */ +/* Note: this is the only limit on the number of cards supported!! */ +static int options[8] = { -1, -1, -1, -1, -1, -1, -1, -1,}; + +#ifdef MODULE +static int debug = -1; +/* A list of all installed Vortex devices, for removing the driver module. */ +static struct device *root_vortex_dev = NULL; + +int +init_module(void) +{ + int cards_found; + + if (debug >= 0) + vortex_debug = debug; + if (vortex_debug) + printk(version); + + root_vortex_dev = NULL; + cards_found = vortex_scan(0); + return cards_found ? 0 : -ENODEV; +} + +#else +int tc515_probe(struct device *dev) +{ + int cards_found = 0; + + cards_found = vortex_scan(dev); + + if (vortex_debug > 0 && cards_found) + printk(version); + + return cards_found ? 0 : -ENODEV; +} +#endif /* not MODULE */ + +static int vortex_scan(struct device *dev) +{ + int cards_found = 0; + static int ioaddr = 0x100; + + /* Check all locations on the ISA bus -- evil! */ + for (; ioaddr < 0x400; ioaddr += 0x20) { + int irq; + if (check_region(ioaddr, CORKSCREW_TOTAL_SIZE)) + continue; + /* Check the resource configuration for a matching ioaddr. */ + if ((inw(ioaddr + 0x2002) & 0x1f0) != (ioaddr & 0x1f0)) + continue; + /* Verify by reading the device ID from the EEPROM. */ + { + int timer; + outw(EEPROM_Read + 7, ioaddr + Wn0EepromCmd); + /* Pause for at least 162 us. for the read to take place. */ + for (timer = 4; timer >= 0; timer--) { + udelay(162); + if ((inw(ioaddr + Wn0EepromCmd) & 0x0200) == 0) + break; + } + if (inw(ioaddr + Wn0EepromData) != 0x6d50) + continue; + } + printk("3c515 Resource configuraiton register %#4.4x, DCR %4.4x.\n", + inl(ioaddr + 0x2002), inw(ioaddr + 0x2000)); + irq = inw(ioaddr + 0x2002) & 15; + vortex_found_device(dev, ioaddr, irq, CORKSCREW_ID, dev && dev->mem_start + ? dev->mem_start : options[cards_found]); + dev = 0; + cards_found++; + } + + if (vortex_debug) + printk("%d 3c515 cards found.\n", cards_found); + return cards_found; +} + +static struct device *vortex_found_device(struct device *dev, int ioaddr, + int irq, int product_index, + int options) +{ + struct vortex_private *vp; + +#ifdef MODULE + /* Allocate and fill new device structure. */ + int dev_size = sizeof(struct device) + + sizeof(struct vortex_private) + 15; /* Pad for alignment */ + + dev = (struct device *) kmalloc(dev_size, GFP_KERNEL); + memset(dev, 0, dev_size); + /* Align the Rx and Tx ring entries. */ + dev->priv = (void *)(((long)dev + sizeof(struct device) + 15) & ~15); + vp = (struct vortex_private *)dev->priv; + dev->name = vp->devname; /* An empty string. */ + dev->base_addr = ioaddr; + dev->irq = irq; + dev->dma = (product_index == CORKSCREW_ID ? inw(ioaddr + 0x2000) & 7 : 0); + dev->init = vortex_probe1; + vp->product_name = "3c515"; + vp->options = options; + if (options >= 0) { + vp->media_override = ((options & 7) == 2) ? 0 : options & 7; + vp->full_duplex = (options & 8) ? 1 : 0; + vp->bus_master = (options & 16) ? 1 : 0; + } else { + vp->media_override = 7; + vp->full_duplex = 0; + vp->bus_master = 0; + } + ether_setup(dev); + vp->next_module = root_vortex_dev; + root_vortex_dev = dev; + if (register_netdev(dev) != 0) + return 0; +#else /* not a MODULE */ + if (dev) { + /* Caution: quad-word alignment required for rings! */ + dev->priv = kmalloc(sizeof (struct vortex_private), GFP_KERNEL); + memset(dev->priv, 0, sizeof (struct vortex_private)); + } + dev = init_etherdev(dev, sizeof(struct vortex_private)); + dev->base_addr = ioaddr; + dev->irq = irq; + dev->dma = (product_index == CORKSCREW_ID ? inw(ioaddr + 0x2000) & 7 : 0); + vp = (struct vortex_private *)dev->priv; + vp->product_name = "3c515"; + vp->options = options; + if (options >= 0) { + vp->media_override = ((options & 7) == 2) ? 0 : options & 7; + vp->full_duplex = (options & 8) ? 1 : 0; + vp->bus_master = (options & 16) ? 1 : 0; + } else { + vp->media_override = 7; + vp->full_duplex = 0; + vp->bus_master = 0; + } + + vortex_probe1(dev); +#endif /* MODULE */ + return dev; +} + +static int vortex_probe1(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct vortex_private *vp = (struct vortex_private *)dev->priv; + unsigned int eeprom[0x40], checksum = 0; /* EEPROM contents */ + int i; + + printk("%s: 3Com %s at %#3x,", dev->name, + vp->product_name, ioaddr); + + /* Read the station address from the EEPROM. */ + EL3WINDOW(0); + for (i = 0; i < 0x18; i++) { + short *phys_addr = (short *)dev->dev_addr; + int timer; + outw(EEPROM_Read + i, ioaddr + Wn0EepromCmd); + /* Pause for at least 162 us. for the read to take place. */ + for (timer = 4; timer >= 0; timer--) { + udelay(162); + if ((inw(ioaddr + Wn0EepromCmd) & 0x0200) == 0) + break; + } + eeprom[i] = inw(ioaddr + Wn0EepromData); + checksum ^= eeprom[i]; + if (i < 3) + phys_addr[i] = htons(eeprom[i]); + } + checksum = (checksum ^ (checksum >> 8)) & 0xff; + if (checksum != 0x00) + printk(" ***INVALID CHECKSUM %4.4x*** ", checksum); + for (i = 0; i < 6; i++) + printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i]); + if (eeprom[16] == 0x11c7) { /* Corkscrew */ + if (request_dma(dev->dma, "3c515")) { + printk(", DMA %d allocation failed", dev->dma); + dev->dma = 0; + } else + printk(", DMA %d", dev->dma); + } + printk(", IRQ %d\n", dev->irq); + /* Tell them about an invalid IRQ. */ + if (vortex_debug && (dev->irq <= 0 || dev->irq > 15)) + printk(" *** Warning: this IRQ is unlikely to work! ***\n"); + + { + char *ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; + union wn3_config config; + EL3WINDOW(3); + vp->available_media = inw(ioaddr + Wn3_Options); + config.i = inl(ioaddr + Wn3_Config); + if (vortex_debug > 1) + printk(" Internal config register is %4.4x, transceivers %#x.\n", + config.i, inw(ioaddr + Wn3_Options)); + printk(" %dK %s-wide RAM %s Rx:Tx split, %s%s interface.\n", + 8 << config.u.ram_size, + config.u.ram_width ? "word" : "byte", + ram_split[config.u.ram_split], + config.u.autoselect ? "autoselect/" : "", + media_tbl[config.u.xcvr].name); + dev->if_port = config.u.xcvr; + vp->default_media = config.u.xcvr; + vp->autoselect = config.u.autoselect; + } + if (vp->media_override != 7) { + printk(" Media override to transceiver type %d (%s).\n", + vp->media_override, media_tbl[vp->media_override].name); + dev->if_port = vp->media_override; + } + + vp->capabilities = eeprom[16]; + vp->full_bus_master_tx = (vp->capabilities & 0x20) ? 1 : 0; + /* Rx is broken at 10mbps, so we always disable it. */ + /* vp->full_bus_master_rx = 0;*/ + vp->full_bus_master_rx = (vp->capabilities & 0x20) ? 1 : 0; + + /* We do a request_region() to register /proc/ioports info. */ + request_region(ioaddr, CORKSCREW_TOTAL_SIZE, vp->product_name); + + /* The 3c59x-specific entries in the device structure. */ + dev->open = &vortex_open; + dev->hard_start_xmit = &vortex_start_xmit; + dev->stop = &vortex_close; + dev->get_stats = &vortex_get_stats; + dev->set_multicast_list = &set_rx_mode; + + return 0; +} + + +static int +vortex_open(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct vortex_private *vp = (struct vortex_private *)dev->priv; + union wn3_config config; + int i; + + /* Before initializing select the active media port. */ + EL3WINDOW(3); + if (vp->full_duplex) + outb(0x20, ioaddr + Wn3_MAC_Ctrl); /* Set the full-duplex bit. */ + config.i = inl(ioaddr + Wn3_Config); + + if (vp->media_override != 7) { + if (vortex_debug > 1) + printk("%s: Media override to transceiver %d (%s).\n", + dev->name, vp->media_override, + media_tbl[vp->media_override].name); + dev->if_port = vp->media_override; + } else if (vp->autoselect) { + /* Find first available media type, starting with 100baseTx. */ + dev->if_port = 4; + while (! (vp->available_media & media_tbl[dev->if_port].mask)) + dev->if_port = media_tbl[dev->if_port].next; + + if (vortex_debug > 1) + printk("%s: Initial media type %s.\n", + dev->name, media_tbl[dev->if_port].name); + + init_timer(&vp->timer); + vp->timer.expires = RUN_AT(media_tbl[dev->if_port].wait); + vp->timer.data = (unsigned long)dev; + vp->timer.function = &vortex_timer; /* timer handler */ + add_timer(&vp->timer); + } else + dev->if_port = vp->default_media; + + config.u.xcvr = dev->if_port; + outl(config.i, ioaddr + Wn3_Config); + + if (vortex_debug > 1) { + printk("%s: vortex_open() InternalConfig %8.8x.\n", + dev->name, config.i); + } + + outw(TxReset, ioaddr + EL3_CMD); + for (i = 20; i >= 0 ; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + + outw(RxReset, ioaddr + EL3_CMD); + /* Wait a few ticks for the RxReset command to complete. */ + for (i = 20; i >= 0 ; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + + outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + + /* Use the now-standard shared IRQ implementation. */ + if (vp->capabilities == 0x11c7) { + /* Corkscrew: Cannot share ISA resources. */ + if (dev->irq == 0 + || dev->dma == 0 + || request_irq(dev->irq, &vortex_interrupt, 0, + vp->product_name, dev)) + return -EAGAIN; + enable_dma(dev->dma); + set_dma_mode(dev->dma, DMA_MODE_CASCADE); + } else if (request_irq(dev->irq, &vortex_interrupt, SA_SHIRQ, + vp->product_name, dev)) { + return -EAGAIN; + } + + if (vortex_debug > 1) { + EL3WINDOW(4); + printk("%s: vortex_open() irq %d media status %4.4x.\n", + dev->name, dev->irq, inw(ioaddr + Wn4_Media)); + } + + /* Set the station address and mask in window 2 each time opened. */ + EL3WINDOW(2); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + for (; i < 12; i+=2) + outw(0, ioaddr + i); + + if (dev->if_port == 3) + /* Start the thinnet transceiver. We should really wait 50ms...*/ + outw(StartCoax, ioaddr + EL3_CMD); + EL3WINDOW(4); + outw((inw(ioaddr + Wn4_Media) & ~(Media_10TP|Media_SQE)) | + media_tbl[dev->if_port].media_bits, ioaddr + Wn4_Media); + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 10; i++) + inb(ioaddr + i); + inw(ioaddr + 10); + inw(ioaddr + 12); + /* New: On the Vortex we must also clear the BadSSD counter. */ + EL3WINDOW(4); + inb(ioaddr + 12); + /* ..and on the Boomerang we enable the extra statistics bits. */ + outw(0x0040, ioaddr + Wn4_NetDiag); + + /* Switch to register set 7 for normal use. */ + EL3WINDOW(7); + + if (vp->full_bus_master_rx) { /* Boomerang bus master. */ + vp->cur_rx = vp->dirty_rx = 0; + if (vortex_debug > 2) + printk("%s: Filling in the Rx ring.\n", dev->name); + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb; + if (i < (RX_RING_SIZE - 1)) + vp->rx_ring[i].next = virt_to_bus(&vp->rx_ring[i+1]); + else + vp->rx_ring[i].next = 0; + vp->rx_ring[i].status = 0; /* Clear complete bit. */ + vp->rx_ring[i].length = PKT_BUF_SZ | 0x80000000; + skb = dev_alloc_skb(PKT_BUF_SZ); + vp->rx_skbuff[i] = skb; + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + vp->rx_ring[i].addr = virt_to_bus(skb->tail); + } + vp->rx_ring[i-1].next = virt_to_bus(&vp->rx_ring[0]); /* Wrap the ring. */ + outl(virt_to_bus(&vp->rx_ring[0]), ioaddr + UpListPtr); + } + if (vp->full_bus_master_tx) { /* Boomerang bus master Tx. */ + vp->cur_tx = vp->dirty_tx = 0; + outb(PKT_BUF_SZ>>8, ioaddr + TxFreeThreshold); /* Room for a packet. */ + /* Clear the Tx ring. */ + for (i = 0; i < TX_RING_SIZE; i++) + vp->tx_skbuff[i] = 0; + outl(0, ioaddr + DownListPtr); + } + /* Set reciever mode: presumably accept b-case and phys addr only. */ + set_rx_mode(dev); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + outw(SetStatusEnb | AdapterFailure|IntReq|StatsFull | + (vp->full_bus_master_tx ? DownComplete : TxAvailable) | + (vp->full_bus_master_rx ? UpComplete : RxComplete) | + (vp->bus_master ? DMADone : 0), + ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull + | (vp->bus_master ? DMADone : 0) | UpComplete | DownComplete, + ioaddr + EL3_CMD); + + MOD_INC_USE_COUNT; + + return 0; +} + +static void vortex_timer(unsigned long data) +{ +#ifdef AUTOMEDIA + struct device *dev = (struct device *)data; + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + unsigned long flags; + int ok = 0; + + if (vortex_debug > 1) + printk("%s: Media selection timer tick happened, %s.\n", + dev->name, media_tbl[dev->if_port].name); + + save_flags(flags); cli(); { + int old_window = inw(ioaddr + EL3_CMD) >> 13; + int media_status; + EL3WINDOW(4); + media_status = inw(ioaddr + Wn4_Media); + switch (dev->if_port) { + case 0: case 4: case 5: /* 10baseT, 100baseTX, 100baseFX */ + if (media_status & Media_LnkBeat) { + ok = 1; + if (vortex_debug > 1) + printk("%s: Media %s has link beat, %x.\n", + dev->name, media_tbl[dev->if_port].name, media_status); + } else if (vortex_debug > 1) + printk("%s: Media %s is has no link beat, %x.\n", + dev->name, media_tbl[dev->if_port].name, media_status); + + break; + default: /* Other media types handled by Tx timeouts. */ + if (vortex_debug > 1) + printk("%s: Media %s is has no indication, %x.\n", + dev->name, media_tbl[dev->if_port].name, media_status); + ok = 1; + } + if ( ! ok) { + union wn3_config config; + + do { + dev->if_port = media_tbl[dev->if_port].next; + } while ( ! (vp->available_media & media_tbl[dev->if_port].mask)); + if (dev->if_port == 8) { /* Go back to default. */ + dev->if_port = vp->default_media; + if (vortex_debug > 1) + printk("%s: Media selection failing, using default %s port.\n", + dev->name, media_tbl[dev->if_port].name); + } else { + if (vortex_debug > 1) + printk("%s: Media selection failed, now trying %s port.\n", + dev->name, media_tbl[dev->if_port].name); + vp->timer.expires = RUN_AT(media_tbl[dev->if_port].wait); + add_timer(&vp->timer); + } + outw((media_status & ~(Media_10TP|Media_SQE)) | + media_tbl[dev->if_port].media_bits, ioaddr + Wn4_Media); + + EL3WINDOW(3); + config.i = inl(ioaddr + Wn3_Config); + config.u.xcvr = dev->if_port; + outl(config.i, ioaddr + Wn3_Config); + + outw(dev->if_port == 3 ? StartCoax : StopCoax, ioaddr + EL3_CMD); + } + EL3WINDOW(old_window); + } restore_flags(flags); + if (vortex_debug > 1) + printk("%s: Media selection timer finished, %s.\n", + dev->name, media_tbl[dev->if_port].name); + +#endif /* AUTOMEDIA*/ + return; +} + +static int +vortex_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + + if (dev->tbusy) { + int tickssofar = jiffies - dev->trans_start; + int i; + + /* Min. wait before assuming a Tx failed == 400ms. */ + + if (tickssofar < 400*HZ/1000) /* We probably aren't empty. */ + return 1; + printk("%s: transmit timed out, tx_status %2.2x status %4.4x.\n", + dev->name, inb(ioaddr + TxStatus), + inw(ioaddr + EL3_STATUS)); + /* Slight code bloat to be user friendly. */ + if ((inb(ioaddr + TxStatus) & 0x88) == 0x88) + printk("%s: Transmitter encountered 16 collisions -- network" + " network cable problem?\n", dev->name); +#ifndef final_version + printk(" Flags; bus-master %d, full %d; dirty %d current %d.\n", + vp->full_bus_master_tx, vp->tx_full, vp->dirty_tx, vp->cur_tx); + printk(" Down list %8.8x vs. %p.\n", inl(ioaddr + DownListPtr), + &vp->tx_ring[0]); + for (i = 0; i < TX_RING_SIZE; i++) { + printk(" %d: %p length %8.8x status %8.8x\n", i, + &vp->tx_ring[i], + vp->tx_ring[i].length, + vp->tx_ring[i].status); + } +#endif + /* Issue TX_RESET and TX_START commands. */ + outw(TxReset, ioaddr + EL3_CMD); + for (i = 20; i >= 0 ; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + outw(TxEnable, ioaddr + EL3_CMD); + dev->trans_start = jiffies; + /* dev->tbusy = 0;*/ + vp->stats.tx_errors++; + vp->stats.tx_dropped++; + return 0; /* Yes, silently *drop* the packet! */ + } + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. + If this ever occurs the queue layer is doing something evil! */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + printk("%s: Transmitter access conflict.\n", dev->name); + return 1; + } + + if (vp->full_bus_master_tx) { /* BOOMERANG bus-master */ + /* Calculate the next Tx descriptor entry. */ + int entry = vp->cur_tx % TX_RING_SIZE; + struct boom_tx_desc *prev_entry; + unsigned long flags, i; + + if (vp->tx_full) /* No room to transmit with */ + return 1; + if (vp->cur_tx != 0) + prev_entry = &vp->tx_ring[(vp->cur_tx-1) % TX_RING_SIZE]; + else + prev_entry = NULL; + if (vortex_debug > 3) + printk("%s: Trying to send a packet, Tx index %d.\n", + dev->name, vp->cur_tx); + /* vp->tx_full = 1; */ + vp->tx_skbuff[entry] = skb; + vp->tx_ring[entry].next = 0; + vp->tx_ring[entry].addr = virt_to_bus(skb->data); + vp->tx_ring[entry].length = skb->len | 0x80000000; + vp->tx_ring[entry].status = skb->len | 0x80000000; + + save_flags(flags); + cli(); + outw(DownStall, ioaddr + EL3_CMD); + /* Wait for the stall to complete. */ + for (i = 20; i >= 0 ; i--) + if ( (inw(ioaddr + EL3_STATUS) & CmdInProgress) == 0) + break; + if (prev_entry) + prev_entry->next = virt_to_bus(&vp->tx_ring[entry]); + if (inl(ioaddr + DownListPtr) == 0) { + outl(virt_to_bus(&vp->tx_ring[entry]), ioaddr + DownListPtr); + queued_packet++; + } + outw(DownUnstall, ioaddr + EL3_CMD); + restore_flags(flags); + + vp->cur_tx++; + if (vp->cur_tx - vp->dirty_tx > TX_RING_SIZE - 1) + vp->tx_full = 1; + else { /* Clear previous interrupt enable. */ + if (prev_entry) + prev_entry->status &= ~0x80000000; + dev->tbusy = 0; + } + dev->trans_start = jiffies; + return 0; + } + /* Put out the doubleword header... */ + outl(skb->len, ioaddr + TX_FIFO); +#ifdef VORTEX_BUS_MASTER + if (vp->bus_master) { + /* Set the bus-master controller to transfer the packet. */ + outl((int)(skb->data), ioaddr + Wn7_MasterAddr); + outw((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen); + vp->tx_skb = skb; + outw(StartDMADown, ioaddr + EL3_CMD); + /* dev->tbusy will be cleared at the DMADone interrupt. */ + } else { + /* ... and the packet rounded to a doubleword. */ + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + dev_kfree_skb (skb, FREE_WRITE); + if (inw(ioaddr + TxFree) > 1536) { + dev->tbusy = 0; + } else + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); + } +#else + /* ... and the packet rounded to a doubleword. */ + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + dev_kfree_skb (skb, FREE_WRITE); + if (inw(ioaddr + TxFree) > 1536) { + dev->tbusy = 0; + } else + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); +#endif /* bus master */ + + dev->trans_start = jiffies; + + /* Clear the Tx status stack. */ + { + short tx_status; + int i = 4; + + while (--i > 0 && (tx_status = inb(ioaddr + TxStatus)) > 0) { + if (tx_status & 0x3C) { /* A Tx-disabling error occurred. */ + if (vortex_debug > 2) + printk("%s: Tx error, status %2.2x.\n", + dev->name, tx_status); + if (tx_status & 0x04) vp->stats.tx_fifo_errors++; + if (tx_status & 0x38) vp->stats.tx_aborted_errors++; + if (tx_status & 0x30) { + int j; + outw(TxReset, ioaddr + EL3_CMD); + for (j = 20; j >= 0 ; j--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + } + outw(TxEnable, ioaddr + EL3_CMD); + } + outb(0x00, ioaddr + TxStatus); /* Pop the status stack. */ + } + } + return 0; +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void vortex_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs) +{ + /* Use the now-standard shared IRQ implementation. */ + struct device *dev = dev_id; + struct vortex_private *lp; + int ioaddr, status; + int latency; + int i = max_interrupt_work; + + if (test_and_set_bit(0, (void*)&dev->interrupt)) { + printk("%s: Re-entering the interrupt handler.\n", dev->name); + return; + } + + ioaddr = dev->base_addr; + latency = inb(ioaddr + Timer); + lp = (struct vortex_private *)dev->priv; + + status = inw(ioaddr + EL3_STATUS); + + if (vortex_debug > 4) + printk("%s: interrupt, status %4.4x, timer %d.\n", dev->name, + status, latency); + if ((status & 0xE000) != 0xE000) { + static int donedidthis=0; + /* Some interrupt controllers store a bogus interrupt from boot-time. + Ignore a single early interrupt, but don't hang the machine for + other interrupt problems. */ + if (donedidthis++ > 100) { + printk("%s: Bogus interrupt, bailing. Status %4.4x, start=%d.\n", + dev->name, status, dev->start); + FREE_IRQ(dev->irq, dev); + } + } + + do { + if (vortex_debug > 5) + printk("%s: In interrupt loop, status %4.4x.\n", + dev->name, status); + if (status & RxComplete) + vortex_rx(dev); + + if (status & TxAvailable) { + if (vortex_debug > 5) + printk(" TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + dev->tbusy = 0; + mark_bh(NET_BH); + } + if (status & DownComplete) { + unsigned int dirty_tx = lp->dirty_tx; + + while (lp->cur_tx - dirty_tx > 0) { + int entry = dirty_tx % TX_RING_SIZE; + if (inl(ioaddr + DownListPtr) == + virt_to_bus(&lp->tx_ring[entry])) + break; /* It still hasn't been processed. */ + if (lp->tx_skbuff[entry]) { + dev_kfree_skb(lp->tx_skbuff[entry], FREE_WRITE); + lp->tx_skbuff[entry] = 0; + } + dirty_tx++; + } + lp->dirty_tx = dirty_tx; + outw(AckIntr | DownComplete, ioaddr + EL3_CMD); + if (lp->tx_full && (lp->cur_tx - dirty_tx <= TX_RING_SIZE - 1)) { + lp->tx_full= 0; + dev->tbusy = 0; + mark_bh(NET_BH); + } + } +#ifdef VORTEX_BUS_MASTER + if (status & DMADone) { + outw(0x1000, ioaddr + Wn7_MasterStatus); /* Ack the event. */ + dev->tbusy = 0; + dev_kfree_skb (lp->tx_skb, FREE_WRITE); /* Release the transfered buffer */ + mark_bh(NET_BH); + } +#endif + if (status & UpComplete) { + boomerang_rx(dev); + outw(AckIntr | UpComplete, ioaddr + EL3_CMD); + } + if (status & (AdapterFailure | RxEarly | StatsFull)) { + /* Handle all uncommon interrupts at once. */ + if (status & RxEarly) { /* Rx early is unused. */ + vortex_rx(dev); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & StatsFull) { /* Empty statistics. */ + static int DoneDidThat = 0; + if (vortex_debug > 4) + printk("%s: Updating stats.\n", dev->name); + update_stats(ioaddr, dev); + /* DEBUG HACK: Disable statistics as an interrupt source. */ + /* This occurs when we have the wrong media type! */ + if (DoneDidThat == 0 && + inw(ioaddr + EL3_STATUS) & StatsFull) { + int win, reg; + printk("%s: Updating stats failed, disabling stats as an" + " interrupt source.\n", dev->name); + for (win = 0; win < 8; win++) { + EL3WINDOW(win); + printk("\n Vortex window %d:", win); + for (reg = 0; reg < 16; reg++) + printk(" %2.2x", inb(ioaddr+reg)); + } + EL3WINDOW(7); + outw(SetIntrEnb | TxAvailable | RxComplete | AdapterFailure + | UpComplete | DownComplete | TxComplete, + ioaddr + EL3_CMD); + DoneDidThat++; + } + } + if (status & AdapterFailure) { + /* Adapter failure requires Rx reset and reinit. */ + outw(RxReset, ioaddr + EL3_CMD); + /* Set the Rx filter to the current state. */ + set_rx_mode(dev); + outw(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */ + outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + } + } + + if (--i < 0) { + printk("%s: Too much work in interrupt, status %4.4x. " + "Disabling functions (%4.4x).\n", + dev->name, status, SetStatusEnb | ((~status) & 0x7FE)); + /* Disable all pending interrupts. */ + outw(SetStatusEnb | ((~status) & 0x7FE), ioaddr + EL3_CMD); + outw(AckIntr | 0x7FF, ioaddr + EL3_CMD); + break; + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); + + } while ((status = inw(ioaddr + EL3_STATUS)) & (IntLatch | RxComplete)); + + if (vortex_debug > 4) + printk("%s: exiting interrupt, status %4.4x.\n", dev->name, status); + + dev->interrupt = 0; + return; +} + +static int +vortex_rx(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + short rx_status; + + if (vortex_debug > 5) + printk(" In rx_packet(), status %4.4x, rx_status %4.4x.\n", + inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); + while ((rx_status = inw(ioaddr + RxStatus)) > 0) { + if (rx_status & 0x4000) { /* Error, update stats. */ + unsigned char rx_error = inb(ioaddr + RxErrors); + if (vortex_debug > 2) + printk(" Rx error: status %2.2x.\n", rx_error); + vp->stats.rx_errors++; + if (rx_error & 0x01) vp->stats.rx_over_errors++; + if (rx_error & 0x02) vp->stats.rx_length_errors++; + if (rx_error & 0x04) vp->stats.rx_frame_errors++; + if (rx_error & 0x08) vp->stats.rx_crc_errors++; + if (rx_error & 0x10) vp->stats.rx_length_errors++; + } else { + /* The packet length: up to 4.5K!. */ + short pkt_len = rx_status & 0x1fff; + struct sk_buff *skb; + + skb = DEV_ALLOC_SKB(pkt_len + 5); + if (vortex_debug > 4) + printk("Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb->dev = dev; +#if LINUX_VERSION_CODE >= 0x10300 + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + /* 'skb_put()' points to the start of sk_buff data area. */ + insl(ioaddr + RX_FIFO, skb_put(skb, pkt_len), + (pkt_len + 3) >> 2); + outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ + skb->protocol = eth_type_trans(skb, dev); +#else + skb->len = pkt_len; + /* 'skb->data' points to the start of sk_buff data area. */ + insl(ioaddr + RX_FIFO, skb->data, (pkt_len + 3) >> 2); + outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ +#endif /* KERNEL_1_3_0 */ + netif_rx(skb); + dev->last_rx = jiffies; + vp->stats.rx_packets++; + /* Wait a limited time to go to next packet. */ + for (i = 200; i >= 0; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + continue; + } else if (vortex_debug) + printk("%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, pkt_len); + } + outw(RxDiscard, ioaddr + EL3_CMD); + vp->stats.rx_dropped++; + /* Wait a limited time to skip this packet. */ + for (i = 200; i >= 0; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + } + + return 0; +} + +static int +boomerang_rx(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int entry = vp->cur_rx % RX_RING_SIZE; + int ioaddr = dev->base_addr; + int rx_status; + + if (vortex_debug > 5) + printk(" In boomerang_rx(), status %4.4x, rx_status %4.4x.\n", + inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); + while ((rx_status = vp->rx_ring[entry].status) & RxDComplete) { + if (rx_status & RxDError) { /* Error, update stats. */ + unsigned char rx_error = rx_status >> 16; + if (vortex_debug > 2) + printk(" Rx error: status %2.2x.\n", rx_error); + vp->stats.rx_errors++; + if (rx_error & 0x01) vp->stats.rx_over_errors++; + if (rx_error & 0x02) vp->stats.rx_length_errors++; + if (rx_error & 0x04) vp->stats.rx_frame_errors++; + if (rx_error & 0x08) vp->stats.rx_crc_errors++; + if (rx_error & 0x10) vp->stats.rx_length_errors++; + } else { + /* The packet length: up to 4.5K!. */ + short pkt_len = rx_status & 0x1fff; + struct sk_buff *skb; + + if (vortex_debug > 4) + printk("Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + + /* Check if the packet is long enough to just accept without + copying to a properly sized skbuff. */ + if (pkt_len < rx_copybreak + && (skb = DEV_ALLOC_SKB(pkt_len + 2)) != 0) { + skb->dev = dev; + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + /* 'skb_put()' points to the start of sk_buff data area. */ + memcpy(skb_put(skb, pkt_len), + bus_to_virt(vp->rx_ring[entry].addr), + pkt_len); + rx_copy++; + } else{ + void *temp; + /* Pass up the skbuff already on the Rx ring. */ + skb = vp->rx_skbuff[entry]; + vp->rx_skbuff[entry] = NULL; + temp = skb_put(skb, pkt_len); + /* Remove this checking code for final release. */ + if (bus_to_virt(vp->rx_ring[entry].addr) != temp) + printk("%s: Warning -- the skbuff addresses do not match" + " in boomerang_rx: %p vs. %p / %p.\n", dev->name, + bus_to_virt(vp->rx_ring[entry].addr), + skb->head, temp); + rx_nocopy++; + } +#if LINUX_VERSION_CODE > 0x10300 + skb->protocol = eth_type_trans(skb, dev); +#else + skb->len = pkt_len; +#endif + netif_rx(skb); + dev->last_rx = jiffies; + vp->stats.rx_packets++; + } + entry = (++vp->cur_rx) % RX_RING_SIZE; + } + /* Refill the Rx ring buffers. */ + for (; vp->dirty_rx < vp->cur_rx; vp->dirty_rx++) { + struct sk_buff *skb; + entry = vp->dirty_rx % RX_RING_SIZE; + if (vp->rx_skbuff[entry] == NULL) { + skb = dev_alloc_skb(PKT_BUF_SZ); + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ +#if LINUX_VERSION_CODE > 0x10300 + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + vp->rx_ring[entry].addr = virt_to_bus(skb->tail); +#else + vp->rx_ring[entry].addr = virt_to_bus(skb->data); +#endif + vp->rx_skbuff[entry] = skb; + } + vp->rx_ring[entry].status = 0; /* Clear complete bit. */ + } + return 0; +} + +static int +vortex_close(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (vortex_debug > 1) { + printk("%s: vortex_close() status %4.4x, Tx status %2.2x.\n", + dev->name, inw(ioaddr + EL3_STATUS), inb(ioaddr + TxStatus)); + printk("%s: vortex close stats: rx_nocopy %d rx_copy %d" + " tx_queued %d.\n", + dev->name, rx_nocopy, rx_copy, queued_packet); + } + + del_timer(&vp->timer); + + /* Turn off statistics ASAP. We update lp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + if (dev->if_port == XCVR_10base2) + /* Turn off thinnet power. Green! */ + outw(StopCoax, ioaddr + EL3_CMD); + +#ifdef SA_SHIRQ + free_irq(dev->irq, dev); +#else + free_irq(dev->irq); + irq2dev_map[dev->irq] = 0; +#endif + + outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); + + update_stats(ioaddr, dev); + if (vp->full_bus_master_rx) { /* Free Boomerang bus master Rx buffers. */ + outl(0, ioaddr + UpListPtr); + for (i = 0; i < RX_RING_SIZE; i++) + if (vp->rx_skbuff[i]) { +#if LINUX_VERSION_CODE < 0x20100 + vp->rx_skbuff[i]->free = 1; +#endif + dev_kfree_skb (vp->rx_skbuff[i], FREE_WRITE); + vp->rx_skbuff[i] = 0; + } + } + if (vp->full_bus_master_tx) { /* Free Boomerang bus master Tx buffers. */ + outl(0, ioaddr + DownListPtr); + for (i = 0; i < TX_RING_SIZE; i++) + if (vp->tx_skbuff[i]) { + dev_kfree_skb(vp->tx_skbuff[i], FREE_WRITE); + vp->tx_skbuff[i] = 0; + } + } + + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics * +vortex_get_stats(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + unsigned long flags; + + if (dev->start) { + save_flags(flags); + cli(); + update_stats(dev->base_addr, dev); + restore_flags(flags); + } + return &vp->stats; +} + +/* Update statistics. + Unlike with the EL3 we need not worry about interrupts changing + the window setting from underneath us, but we must still guard + against a race condition with a StatsUpdate interrupt updating the + table. This is done by checking that the ASM (!) code generated uses + atomic updates with '+='. + */ +static void update_stats(int ioaddr, struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + + /* Unlike the 3c5x9 we need not turn off stats updates while reading. */ + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + vp->stats.tx_carrier_errors += inb(ioaddr + 0); + vp->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + vp->stats.collisions += inb(ioaddr + 3); + vp->stats.tx_window_errors += inb(ioaddr + 4); + vp->stats.rx_fifo_errors += inb(ioaddr + 5); + vp->stats.tx_packets += inb(ioaddr + 6); + vp->stats.tx_packets += (inb(ioaddr + 9)&0x30) << 4; + /* Rx packets */ inb(ioaddr + 7); /* Must read to clear */ + /* Tx deferrals */ inb(ioaddr + 8); + /* Don't bother with register 9, an extension of registers 6&7. + If we do use the 6&7 values the atomic update assumption above + is invalid. */ + inw(ioaddr + 10); /* Total Rx and Tx octets. */ + inw(ioaddr + 12); + /* New: On the Vortex we must also clear the BadSSD counter. */ + EL3WINDOW(4); + inb(ioaddr + 12); + + /* We change back to window 7 (not 1) with the Vortex. */ + EL3WINDOW(7); + return; +} + +/* This new version of set_rx_mode() supports v1.4 kernels. + The Vortex chip has no documented multicast filter, so the only + multicast setting is to receive all multicast frames. At least + the chip has a very clean way to set the mode, unlike many others. */ +static void +set_rx_mode(struct device *dev) +{ + int ioaddr = dev->base_addr; + short new_mode; + + if (dev->flags & IFF_PROMISC) { + if (vortex_debug > 3) + printk("%s: Setting promiscuous mode.\n", dev->name); + new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast|RxProm; + } else if ((dev->mc_list) || (dev->flags & IFF_ALLMULTI)) { + new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast; + } else + new_mode = SetRxFilter | RxStation | RxBroadcast; + + outw(new_mode, ioaddr + EL3_CMD); +} + +#ifdef MODULE +void +cleanup_module(void) +{ + struct device *next_dev; + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_vortex_dev) { + next_dev = ((struct vortex_private *)root_vortex_dev->priv)->next_module; + if (root_vortex_dev->dma) + free_dma(root_vortex_dev->dma); + unregister_netdev(root_vortex_dev); + outw(TotalReset, root_vortex_dev->base_addr + EL3_CMD); + release_region(root_vortex_dev->base_addr, CORKSCREW_TOTAL_SIZE); + kfree(root_vortex_dev); + root_vortex_dev = next_dev; + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c 3c515.c" + * c-indent-level: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/3c59x.c b/linux/src/drivers/net/3c59x.c new file mode 100644 index 00000000..c17e34b6 --- /dev/null +++ b/linux/src/drivers/net/3c59x.c @@ -0,0 +1,2174 @@ +/* EtherLinkXL.c: A 3Com EtherLink PCI III/XL ethernet driver for linux. */ +/* + Written 1996-1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + This driver is for the 3Com "Vortex" and "Boomerang" series ethercards. + Members of the series include Fast EtherLink 3c590/3c592/3c595/3c597 + and the EtherLink XL 3c900 and 3c905 cards. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 +*/ + +static char *version = +"3c59x.c:v0.99E 5/12/98 Donald Becker http://cesdis.gsfc.nasa.gov/linux/drivers/vortex.html\n"; + +/* "Knobs" that adjust features and parameters. */ +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + Setting to > 1512 effectively disables this feature. */ +static const rx_copybreak = 200; +/* Allow setting MTU to a larger size, bypassing the normal ethernet setup. */ +static const mtu = 1500; +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 20; + +/* Put out somewhat more debugging messages. (0: no msg, 1 minimal .. 6). */ +#ifdef VORTEX_DEBUG +static int vortex_debug = VORTEX_DEBUG; +#else +static int vortex_debug = 1; +#endif + +/* Some values here only for performance evaluation and path-coverage + debugging. */ +static int rx_nocopy = 0, rx_copy = 0, queued_packet = 0, rx_csumhits; + +/* Enable the automatic media selection code -- usually set. */ +#define AUTOMEDIA 1 + +/* Allow the use of fragment bus master transfers instead of only + programmed-I/O for Vortex cards. Full-bus-master transfers are always + enabled by default on Boomerang cards. If VORTEX_BUS_MASTER is defined, + the feature may be turned on using 'options'. */ +#define VORTEX_BUS_MASTER + +/* A few values that may be tweaked. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((400*HZ)/1000) + +/* Keep the ring sizes a power of two for efficiency. */ +#define TX_RING_SIZE 16 +#define RX_RING_SIZE 32 +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +#include <linux/config.h> +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <linux/timer.h> +#include <asm/irq.h> /* For NR_IRQS only. */ +#include <asm/bitops.h> +#include <asm/io.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* Kernel compatibility defines, common to David Hind's PCMCIA package. + This is only in the support-all-kernels source code. */ +#ifndef LINUX_VERSION_CODE +#include <linux/version.h> /* Redundant above, here for easy clean-up. */ +#endif +#if LINUX_VERSION_CODE < 0x10300 +#define RUN_AT(x) (x) /* What to put in timer->expires. */ +#define DEV_ALLOC_SKB(len) alloc_skb(len, GFP_ATOMIC) +#if defined(__alpha) +#error "The Alpha architecture is only support with kernel version 2.0." +#endif +#define virt_to_bus(addr) ((unsigned long)addr) +#define bus_to_virt(addr) ((void*)addr) +#define NR_IRQS 16 +#else /* 1.3.0 and later */ +#define RUN_AT(x) (jiffies + (x)) +#define DEV_ALLOC_SKB(len) dev_alloc_skb(len) +#endif +#if LINUX_VERSION_CODE < 0x20159 +#define DEV_FREE_SKB(skb) dev_kfree_skb (skb, FREE_WRITE); +#else /* Grrr, unneeded incompatible change. */ +#define DEV_FREE_SKB(skb) dev_kfree_skb(skb); +#endif + +#ifdef SA_SHIRQ +#define FREE_IRQ(irqnum, dev) free_irq(irqnum, dev) +#define REQUEST_IRQ(i,h,f,n, instance) request_irq(i,h,f,n, instance) +#define IRQ(irq, dev_id, pt_regs) (irq, dev_id, pt_regs) +#else +#define FREE_IRQ(irqnum, dev) free_irq(irqnum) +#define REQUEST_IRQ(i,h,f,n, instance) request_irq(i,h,f,n) +#define IRQ(irq, dev_id, pt_regs) (irq, pt_regs) +#endif + +#if (LINUX_VERSION_CODE >= 0x10344) +#define NEW_MULTICAST +#include <linux/delay.h> +#else +#define udelay(microsec) do { int _i = 4*microsec; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0) +#endif + +#if LINUX_VERSION_CODE < 0x20138 +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif +#if defined(MODULE) && (LINUX_VERSION_CODE >= 0x20115) +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("3Com 3c590/3c900 series Vortex/Boomerang driver"); +MODULE_PARM(debug, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(max_interrupt_work, "i"); +MODULE_PARM(compaq_ioaddr, "i"); +MODULE_PARM(compaq_irq, "i"); +MODULE_PARM(compaq_prod_id, "i"); +#endif + +/* Operational parameter that usually are not changed. */ + +/* The Vortex size is twice that of the original EtherLinkIII series: the + runtime register window, window 1, is now always mapped in. + The Boomerang size is twice as large as the Vortex -- it has additional + bus master control registers. */ +#define VORTEX_TOTAL_SIZE 0x20 +#define BOOMERANG_TOTAL_SIZE 0x40 + +#ifdef HAVE_DEVLIST +struct netdev_entry tc59x_drv = +{"Vortex", vortex_pci_probe, VORTEX_TOTAL_SIZE, NULL}; +#endif + +/* Set iff a MII transceiver on any interface requires mdio preamble. + This only set with the original DP83840 on older 3c905 boards, so the extra + code size of a per-interface flag is not worthwhile. */ +static char mii_preamble_required = 0; + +/* Caution! These entries must be consistent. */ +static const int product_ids[] = { + 0x5900, 0x5920, 0x5970, 0x5950, 0x5951, 0x5952, 0x9000, 0x9001, + 0x9050, 0x9051, 0x9055, 0x5057, 0 }; +static const char *product_names[] = { + "3c590 Vortex 10Mbps", + "3c592 EISA 10mbps Demon/Vortex", + "3c597 EISA Fast Demon/Vortex", + "3c595 Vortex 100baseTX", + "3c595 Vortex 100baseT4", + "3c595 Vortex 100base-MII", + "3c900 Boomerang 10baseT", + "3c900 Boomerang 10Mbps/Combo", + "3c905 Boomerang 100baseTx", + "3c905 Boomerang 100baseT4", + "3c905B Cyclone 100baseTx", + "3c575", /* Cardbus Boomerang */ +}; + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the 3Com FastEtherLink and FastEtherLink +XL, 3Com's PCI to 10/100baseT adapters. It also works with the 10Mbs +versions of the FastEtherLink cards. The supported product IDs are + 3c590, 3c592, 3c595, 3c597, 3c900, 3c905 + +The ISA 3c515 is supported with a separate driver, 3c515.c, included with +the kernel source or available from + cesdis.gsfc.nasa.gov:/pub/linux/drivers/3c515.html + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board. The system BIOS should be set to assign the +PCI INTA signal to an otherwise unused system IRQ line. While it's +physically possible to shared PCI interrupt lines, the 1.2.0 kernel doesn't +support it. + +III. Driver operation + +The 3c59x series use an interface that's very similar to the previous 3c5x9 +series. The primary interface is two programmed-I/O FIFOs, with an +alternate single-contiguous-region bus-master transfer (see next). + +The 3c900 "Boomerang" series uses a full-bus-master interface with separate +lists of transmit and receive descriptors, similar to the AMD LANCE/PCnet, +DEC Tulip and Intel Speedo3. The first chip version retains a compatible +programmed-I/O interface that will be removed in the 'B' and subsequent +revisions. + +One extension that is advertised in a very large font is that the adapters +are capable of being bus masters. On the Vortex chip this capability was +only for a single contiguous region making it far less useful than the full +bus master capability. There is a significant performance impact of taking +an extra interrupt or polling for the completion of each transfer, as well +as difficulty sharing the single transfer engine between the transmit and +receive threads. Using DMA transfers is a win only with large blocks or +with the flawed versions of the Intel Orion motherboard PCI controller. + +The Boomerang chip's full-bus-master interface is useful, and has the +currently-unused advantages over other similar chips that queued transmit +packets may be reordered and receive buffer groups are associated with a +single frame. + +With full-bus-master support, this driver uses a "RX_COPYBREAK" scheme. +Tather than a fixed intermediate receive buffer, this scheme allocates +full-sized skbuffs as receive buffers. The value RX_COPYBREAK is used as +the copying breakpoint: it is chosen to trade-off the memory wasted by +passing the full-sized skbuff to the queue layer for all frames vs. the +copying cost of copying a frame to a correctly-sized skbuff. + + +IIIC. Synchronization +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +IV. Notes + +Thanks to Cameron Spitzer and Terry Murphy of 3Com for providing development +3c590, 3c595, and 3c900 boards. +The name "Vortex" is the internal 3Com project name for the PCI ASIC, and +the EISA version is called "Demon". According to Terry these names come +from rides at the local amusement park. + +The new chips support both ethernet (1.5K) and FDDI (4.5K) packet sizes! +This driver only supports ethernet packets because of the skbuff allocation +limit of 4K. +*/ + +#define TCOM_VENDOR_ID 0x10B7 /* 3Com's manufacturer's ID. */ + +/* Operational definitions. + These are not used by other compilation units and thus are not + exported in a ".h" file. + + First the windows. There are eight register windows, with the command + and status registers available in each. + */ +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. + Note that 11 parameters bits was fine for ethernet, but the new chip + can handle FDDI length frames (~4500 octets) and now parameters count + 32-bit 'Dwords' rather than octets. */ + +enum vortex_cmd { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, + UpStall = 6<<11, UpUnstall = (6<<11)+1, + DownStall = (6<<11)+2, DownUnstall = (6<<11)+3, + RxDiscard = 8<<11, TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, + StartDMAUp = 20<<11, StartDMADown = (20<<11)+1, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11,}; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 }; + +/* Bits in the general status register. */ +enum vortex_status { + IntLatch = 0x0001, HostError = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, + DMADone = 1<<8, DownComplete = 1<<9, UpComplete = 1<<10, + DMAInProgress = 1<<11, /* DMA controller is still busy.*/ + CmdInProgress = 1<<12, /* EL3_CMD is still busy.*/ +}; + +/* Register window 1 offsets, the window used in normal operation. + On the Vortex this window is always mapped at offsets 0x10-0x1f. */ +enum Window1 { + TX_FIFO = 0x10, RX_FIFO = 0x10, RxErrors = 0x14, + RxStatus = 0x18, Timer=0x1A, TxStatus = 0x1B, + TxFree = 0x1C, /* Remaining free bytes in Tx buffer. */ +}; +enum Window0 { + Wn0EepromCmd = 10, /* Window 0: EEPROM command register. */ + Wn0EepromData = 12, /* Window 0: EEPROM results register. */ + IntrStatus=0x0E, /* Valid in all windows. */ +}; +enum Win0_EEPROM_bits { + EEPROM_Read = 0x80, EEPROM_WRITE = 0x40, EEPROM_ERASE = 0xC0, + EEPROM_EWENB = 0x30, /* Enable erasing/writing for 10 msec. */ + EEPROM_EWDIS = 0x00, /* Disable EWENB before 10 msec timeout. */ +}; +/* EEPROM locations. */ +enum eeprom_offset { + PhysAddr01=0, PhysAddr23=1, PhysAddr45=2, ModelID=3, + EtherLink3ID=7, IFXcvrIO=8, IRQLine=9, + NodeAddr01=10, NodeAddr23=11, NodeAddr45=12, + DriverTune=13, Checksum=15}; + +enum Window3 { /* Window 3: MAC/config bits. */ + Wn3_Config=0, Wn3_MAC_Ctrl=6, Wn3_Options=8, +}; +union wn3_config { + int i; + struct w3_config_fields { + unsigned int ram_size:3, ram_width:1, ram_speed:2, rom_size:2; + int pad8:8; + unsigned int ram_split:2, pad18:2, xcvr:4, autoselect:1; + int pad24:7; + } u; +}; + +enum Window4 { /* Window 4: Xcvr/media bits. */ + Wn4_FIFODiag = 4, Wn4_NetDiag = 6, Wn4_PhysicalMgmt=8, Wn4_Media = 10, +}; +enum Win4_Media_bits { + Media_SQE = 0x0008, /* Enable SQE error counting for AUI. */ + Media_10TP = 0x00C0, /* Enable link beat and jabber for 10baseT. */ + Media_Lnk = 0x0080, /* Enable just link beat for 100TX/100FX. */ + Media_LnkBeat = 0x0800, +}; +enum Window7 { /* Window 7: Bus Master control. */ + Wn7_MasterAddr = 0, Wn7_MasterLen = 6, Wn7_MasterStatus = 12, +}; +/* Boomerang bus master control registers. */ +enum MasterCtrl { + PktStatus = 0x20, DownListPtr = 0x24, FragAddr = 0x28, FragLen = 0x2c, + TxFreeThreshold = 0x2f, UpPktStatus = 0x30, UpListPtr = 0x38, +}; + +/* The Rx and Tx descriptor lists. + Caution Alpha hackers: these types are 32 bits! Note also the 8 byte + alignment contraint on tx_ring[] and rx_ring[]. */ +#define LAST_FRAG 0x80000000 /* Last Addr/Len pair in descriptor. */ +struct boom_rx_desc { + u32 next; /* Last entry points to 0. */ + s32 status; + u32 addr; /* Up to 63 addr/len pairs possible. */ + s32 length; /* Set LAST_FRAG to indicate last pair. */ +}; +/* Values for the Rx status entry. */ +enum rx_desc_status { + RxDComplete=0x00008000, RxDError=0x4000, + /* See boomerang_rx() for actual error bits */ + IPChksumErr=1<<25, TCPChksumErr=1<<26, UDPChksumErr=1<<27, + IPChksumValid=1<<29, TCPChksumValid=1<<30, UDPChksumValid=1<<31, +}; + +struct boom_tx_desc { + u32 next; /* Last entry points to 0. */ + s32 status; /* bits 0:12 length, others see below. */ + u32 addr; + s32 length; +}; + +/* Values for the Tx status entry. */ +enum tx_desc_status { + CRCDisable=0x2000, TxDComplete=0x8000, + AddIPChksum=0x02000000, AddTCPChksum=0x04000000, AddUDPChksum=0x08000000, + TxIntrUploaded=0x80000000, /* IRQ when in FIFO, but maybe not sent. */ +}; + +/* Chip features we care about in vp->capabilities, read from the EEPROM. */ +enum ChipCaps { CapBusMaster=0x20 }; + +struct vortex_private { + char devname[8]; /* "ethN" string, also for kernel debug. */ + const char *product_name; + struct device *next_module; + /* The Rx and Tx rings are here to keep them quad-word-aligned. */ + struct boom_rx_desc rx_ring[RX_RING_SIZE]; + struct boom_tx_desc tx_ring[TX_RING_SIZE]; + /* The addresses of transmit- and receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + unsigned int cur_rx, cur_tx; /* The next free ring entry */ + unsigned int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + struct enet_statistics stats; + struct sk_buff *tx_skb; /* Packet being eaten by bus master ctrl. */ + + /* PCI configuration space information. */ + u8 pci_bus, pci_dev_fn; /* PCI bus location, for power management. */ + u16 pci_device_id; + + /* The remainder are related to chip state, mostly media selection. */ + int in_interrupt; + struct timer_list timer; /* Media selection timer. */ + int options; /* User-settable misc. driver options. */ + unsigned int + media_override:3, /* Passed-in media type. */ + default_media:3, /* Read from the EEPROM/Wn3_Config. */ + full_duplex:1, autoselect:1, + bus_master:1, /* Vortex can only do a fragment bus-m. */ + full_bus_master_tx:1, full_bus_master_rx:2, /* Boomerang */ + hw_csums:1, /* Has hardware checksums. */ + tx_full:1; + u16 status_enable; + u16 available_media; /* From Wn3_Options. */ + u16 capabilities, info1, info2; /* Various, from EEPROM. */ + u16 advertising; /* NWay media advertisement */ + unsigned char phys[2]; /* MII device addresses. */ +}; + +/* The action to take with a media selection timer tick. + Note that we deviate from the 3Com order by checking 10base2 before AUI. + */ +enum xcvr_types { + XCVR_10baseT=0, XCVR_AUI, XCVR_10baseTOnly, XCVR_10base2, XCVR_100baseTx, + XCVR_100baseFx, XCVR_MII=6, XCVR_NWAY=8, XCVR_ExtMII=9, XCVR_Default=10, +}; + +static struct media_table { + char *name; + unsigned int media_bits:16, /* Bits to set in Wn4_Media register. */ + mask:8, /* The transceiver-present bit in Wn3_Config.*/ + next:8; /* The media type to try next. */ + int wait; /* Time before we check media status. */ +} media_tbl[] = { + { "10baseT", Media_10TP,0x08, XCVR_10base2, (14*HZ)/10}, + { "10Mbs AUI", Media_SQE, 0x20, XCVR_Default, (1*HZ)/10}, + { "undefined", 0, 0x80, XCVR_10baseT, 10000}, + { "10base2", 0, 0x10, XCVR_AUI, (1*HZ)/10}, + { "100baseTX", Media_Lnk, 0x02, XCVR_100baseFx, (14*HZ)/10}, + { "100baseFX", Media_Lnk, 0x04, XCVR_MII, (14*HZ)/10}, + { "MII", 0, 0x41, XCVR_10baseT, 3*HZ }, + { "undefined", 0, 0x01, XCVR_10baseT, 10000}, + { "Autonegotiate", 0, 0x41, XCVR_10baseT, 3*HZ}, + { "MII-External", 0, 0x41, XCVR_10baseT, 3*HZ }, + { "Default", 0, 0xFF, XCVR_10baseT, 10000}, +}; + +static int vortex_scan(struct device *dev); +static struct device *vortex_found_device(struct device *dev, int ioaddr, + int irq, int device_id, + int options, int card_idx); +static int vortex_probe1(struct device *dev); +static int vortex_open(struct device *dev); +static void mdio_sync(int ioaddr, int bits); +static int mdio_read(int ioaddr, int phy_id, int location); +#ifdef HAVE_PRIVATE_IOCTL +static void mdio_write(int ioaddr, int phy_id, int location, int value); +#endif +static void vortex_timer(unsigned long arg); +static int vortex_start_xmit(struct sk_buff *skb, struct device *dev); +static int boomerang_start_xmit(struct sk_buff *skb, struct device *dev); +static int vortex_rx(struct device *dev); +static int boomerang_rx(struct device *dev); +static void vortex_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs); +static int vortex_close(struct device *dev); +static void update_stats(int addr, struct device *dev); +static struct enet_statistics *vortex_get_stats(struct device *dev); +static void set_rx_mode(struct device *dev); +#ifdef HAVE_PRIVATE_IOCTL +static int vortex_ioctl(struct device *dev, struct ifreq *rq, int cmd); +#endif +#ifndef NEW_MULTICAST +static void set_multicast_list(struct device *dev, int num_addrs, void *addrs); +#endif + + +/* Unlike the other PCI cards the 59x cards don't need a large contiguous + memory region, so making the driver a loadable module is feasible. + + Unfortunately maximizing the shared code between the integrated and + module version of the driver results in a complicated set of initialization + procedures. + init_module() -- modules / tc59x_init() -- built-in + The wrappers for vortex_scan() + vortex_scan() The common routine that scans for PCI and EISA cards + vortex_found_device() Allocate a device structure when we find a card. + Different versions exist for modules and built-in. + vortex_probe1() Fill in the device structure -- this is separated + so that the modules code can put it in dev->init. +*/ +/* This driver uses 'options' to pass the media type, full-duplex flag, etc. */ +/* Note: this is the only limit on the number of cards supported!! */ +static int options[8] = { -1, -1, -1, -1, -1, -1, -1, -1,}; +static int full_duplex[8] = {-1, -1, -1, -1, -1, -1, -1, -1}; +/* A list of all installed Vortex devices, for removing the driver module. */ +static struct device *root_vortex_dev = NULL; + +#ifdef MODULE +/* Variables to work-around the Compaq PCI BIOS32 problem. */ +static int compaq_ioaddr = 0, compaq_irq = 0, compaq_device_id = 0x5900; + +static int debug = -1; + +#ifdef CARDBUS + +#include <pcmcia/driver_ops.h> + +static dev_node_t *vortex_attach(dev_locator_t *loc) +{ + u16 dev_id; + u32 io; + u8 bus, devfn, irq; + struct device *dev; + + if (loc->bus != LOC_PCI) return NULL; + bus = loc->b.pci.bus; devfn = loc->b.pci.devfn; + printk(KERN_INFO "vortex_attach(bus %d, function %d)\n", bus, devfn); + pcibios_read_config_dword(bus, devfn, PCI_BASE_ADDRESS_0, &io); + pcibios_read_config_byte(bus, devfn, PCI_INTERRUPT_LINE, &irq); + pcibios_read_config_word(bus, devfn, PCI_DEVICE_ID, &dev_id); + io &= ~3; + dev = vortex_found_device(NULL, io, irq, dev_id, 0, -1); + if (dev) { + dev_node_t *node = kmalloc(sizeof(dev_node_t), GFP_KERNEL); + strcpy(node->dev_name, dev->name); + node->major = node->minor = 0; + node->next = NULL; + MOD_INC_USE_COUNT; + return node; + } + return NULL; +} + +static void vortex_detach(dev_node_t *node) +{ + struct device **devp, **next; + printk(KERN_INFO "vortex_detach(%s)\n", node->dev_name); + for (devp = &root_vortex_dev; *devp; devp = next) { + next = &((struct vortex_private *)(*devp)->priv)->next_module; + if (strcmp((*devp)->name, node->dev_name) == 0) break; + } + if (*devp) { + struct device *dev = *devp; + if (dev->flags & IFF_UP) + vortex_close(dev); + dev->flags &= ~(IFF_UP|IFF_RUNNING); + unregister_netdev(dev); + kfree(dev); + *devp = *next; + kfree(node); + MOD_DEC_USE_COUNT; + } +} + +struct driver_operations vortex_ops = { + "3c59x_cb", vortex_attach, NULL, NULL, vortex_detach +}; + +#endif /* Cardbus support */ + + +int +init_module(void) +{ + if (debug >= 0) + vortex_debug = debug; + if (vortex_debug) + printk(version); + + root_vortex_dev = NULL; +#ifdef CARDBUS + register_driver(&vortex_ops); + return 0; +#else + { + int cards_found = vortex_scan(0); + if (cards_found == 0) + printk("No 3Com Vortex/Boomerang cards found.\n"); + return cards_found ? 0 : -ENODEV; + } +#endif +} + +#else +int tc59x_probe(struct device *dev) +{ + int cards_found = 0; + + cards_found = vortex_scan(dev); + + if (vortex_debug > 0 && cards_found) + printk(version); + + return cards_found ? 0 : -ENODEV; +} +#endif /* not MODULE */ + +static int vortex_scan(struct device *dev) +{ + int cards_found = 0; + + /* Allow an EISA-only driver. */ +#if defined(CONFIG_PCI) || (defined(MODULE) && !defined(NO_PCI)) + /* Ideally we would detect all cards in slot order. That would + be best done a central PCI probe dispatch, which wouldn't work + well with the current structure. So instead we detect 3Com cards + in slot order. */ + if (pcibios_present()) { + static int pci_index = 0; + unsigned char pci_bus, pci_device_fn; + + for (;pci_index < 0xff; pci_index++) { + u8 pci_latency; + u16 pci_command, new_command, vendor, device; + int irq; + long ioaddr; + + if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8, + pci_index, &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + break; + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + { +#if LINUX_VERSION_CODE >= 0x20155 + struct pci_dev *pdev = pci_find_slot(pci_bus, pci_device_fn); + ioaddr = pdev->base_address[0]; + irq = pdev->irq; +#else + u32 pci_ioaddr; + u8 pci_irq_line; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + ioaddr = pci_ioaddr; + irq = pci_irq_line; +#endif + } + /* Remove I/O space marker in bit 0. */ + ioaddr &= ~3; + + if (vendor != TCOM_VENDOR_ID) + continue; + + if (ioaddr == 0) { + printk(KERN_WARNING " A 3Com network adapter has been found, " + "however it has not been assigned an I/O address.\n" + " You may need to power-cycle the machine for this " + "device to work!\n"); + continue; + } + + if (check_region(ioaddr, VORTEX_TOTAL_SIZE)) + continue; + + /* Activate the card. */ + new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled this" + " device! Updating PCI command %4.4x->%4.4x.\n", + pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + + dev = vortex_found_device(dev, ioaddr, irq, + device, dev && dev->mem_start + ? dev->mem_start : options[cards_found], + cards_found); + + if (dev) { + struct vortex_private *vp = (struct vortex_private *)dev->priv; + /* Get and check the latency values. On the 3c590 series + the latency timer must be set to the maximum value to avoid + data corruption that occurs when the timer expires during + a transfer -- a bug in the Vortex chip only. */ + u8 new_latency = (device&0xff00) == 0x5900 ? 248 : 32; + vp->pci_bus = pci_bus; + vp->pci_dev_fn = pci_device_fn; + vp->pci_device_id = device; + + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < new_latency) { + printk(KERN_INFO "%s: Overriding PCI latency" + " timer (CFLT) setting of %d, new value is %d.\n", + dev->name, pci_latency, new_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, new_latency); + } + dev = 0; + cards_found++; + } + } + } +#endif /* NO_PCI */ + + /* Now check all slots of the EISA bus. */ + if (EISA_bus) { + static int ioaddr = 0x1000; + for ( ; ioaddr < 0x9000; ioaddr += 0x1000) { + int device_id; + if (check_region(ioaddr, VORTEX_TOTAL_SIZE)) + continue; + /* Check the standard EISA ID register for an encoded '3Com'. */ + if (inw(ioaddr + 0xC80) != 0x6d50) + continue; + /* Check for a product that we support, 3c59{2,7} any rev. */ + device_id = (inb(ioaddr + 0xC82)<<8) + inb(ioaddr + 0xC83); + if ((device_id & 0xFF00) != 0x5900) + continue; + vortex_found_device(dev, ioaddr, inw(ioaddr + 0xC88) >> 12, + device_id, dev && dev->mem_start + ? dev->mem_start : options[cards_found], + cards_found); + dev = 0; + cards_found++; + } + } + +#ifdef MODULE + /* Special code to work-around the Compaq PCI BIOS32 problem. */ + if (compaq_ioaddr) { + vortex_found_device(dev, compaq_ioaddr, compaq_irq, compaq_device_id, + dev && dev->mem_start ? dev->mem_start + : options[cards_found], cards_found); + cards_found++; + dev = 0; + } +#endif + + /* 3c515 cards are now supported by the 3c515.c driver. */ + + return cards_found; +} + +static struct device * +vortex_found_device(struct device *dev, int ioaddr, int irq, + int device_id, int option, int card_idx) +{ + struct vortex_private *vp; + const char *product_name; + int board_index = 0; + + for (board_index = 0; product_ids[board_index]; board_index++) { + if (device_id == product_ids[board_index]) + break; + } + /* Handle products we don't recognize, but might still work with. */ + if (product_ids[board_index]) + product_name = product_names[board_index]; + else if ((device_id & 0xff00) == 0x5900) + product_name = "3c590 Vortex"; + else if ((device_id & 0xfff0) == 0x9000) + product_name = "3c900"; + else if ((device_id & 0xfff0) == 0x9050) + product_name = "3c905"; + else { + printk(KERN_WARNING "Unknown 3Com PCI ethernet adapter type %4.4x detected:" + " not configured.\n", device_id); + return 0; + } + +#ifdef MODULE + /* Allocate and fill new device structure. */ + { + int dev_size = sizeof(struct device) + + sizeof(struct vortex_private) + 15; /* Pad for alignment */ + + dev = (struct device *) kmalloc(dev_size, GFP_KERNEL); + memset(dev, 0, dev_size); + } + /* Align the Rx and Tx ring entries. */ + dev->priv = (void *)(((long)dev + sizeof(struct device) + 15) & ~15); + vp = (struct vortex_private *)dev->priv; + dev->name = vp->devname; /* An empty string. */ + dev->base_addr = ioaddr; + dev->irq = irq; + dev->init = vortex_probe1; + vp->product_name = product_name; + vp->options = option; + if (card_idx >= 0) { + if (full_duplex[card_idx] >= 0) + vp->full_duplex = full_duplex[card_idx]; + } else + vp->full_duplex = (option > 0 && (option & 0x10) ? 1 : 0); + + if (option > 0) { + vp->media_override = ((option & 7) == XCVR_10baseTOnly) ? + XCVR_10baseT : option & 7; + vp->bus_master = (option & 16) ? 1 : 0; + } else { + vp->media_override = 7; + vp->bus_master = 0; + } + ether_setup(dev); + vp->next_module = root_vortex_dev; + root_vortex_dev = dev; + if (register_netdev(dev) != 0) + return 0; +#else /* not a MODULE */ + if (dev) { + /* Caution: quad-word alignment required for rings! */ + dev->priv = kmalloc(sizeof (struct vortex_private), GFP_KERNEL); + memset(dev->priv, 0, sizeof (struct vortex_private)); + } + dev = init_etherdev(dev, sizeof(struct vortex_private)); + dev->base_addr = ioaddr; + dev->irq = irq; + dev->mtu = mtu; + + vp = (struct vortex_private *)dev->priv; + vp->product_name = product_name; + vp->options = option; + if (option >= 0) { + vp->media_override = ((option & 7) == 2) ? 0 : option & 7; + vp->full_duplex = (option & 8) ? 1 : 0; + vp->bus_master = (option & 16) ? 1 : 0; + } else { + vp->media_override = 7; + vp->full_duplex = 0; + vp->bus_master = 0; + } + + vortex_probe1(dev); +#endif /* MODULE */ + return dev; +} + +static int vortex_probe1(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct vortex_private *vp = (struct vortex_private *)dev->priv; + u16 *ether_addr = (u16 *)dev->dev_addr; + unsigned int eeprom[0x40], checksum = 0; /* EEPROM contents */ + int i; + + printk(KERN_INFO "%s: 3Com %s at %#3x,", + dev->name, vp->product_name, ioaddr); + + /* Read the station address from the EEPROM. */ + EL3WINDOW(0); + for (i = 0; i < 0x40; i++) { + int timer; +#ifdef CARDBUS + outw(0x230 + i, ioaddr + Wn0EepromCmd); +#else + outw(EEPROM_Read + i, ioaddr + Wn0EepromCmd); +#endif + /* Pause for at least 162 us. for the read to take place. */ + for (timer = 10; timer >= 0; timer--) { + udelay(162); + if ((inw(ioaddr + Wn0EepromCmd) & 0x8000) == 0) + break; + } + eeprom[i] = inw(ioaddr + Wn0EepromData); + } + for (i = 0; i < 0x18; i++) + checksum ^= eeprom[i]; + checksum = (checksum ^ (checksum >> 8)) & 0xff; + if (checksum != 0x00) { /* Grrr, needless incompatible change 3Com. */ + while (i < 0x21) + checksum ^= eeprom[i++]; + checksum = (checksum ^ (checksum >> 8)) & 0xff; + } + if (checksum != 0x00) + printk(" ***INVALID CHECKSUM %4.4x*** ", checksum); + + for (i = 0; i < 3; i++) + ether_addr[i] = htons(eeprom[i + 10]); + for (i = 0; i < 6; i++) + printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i]); + printk(", IRQ %d\n", dev->irq); + /* Tell them about an invalid IRQ. */ + if (vortex_debug && (dev->irq <= 0 || dev->irq >= NR_IRQS)) + printk(KERN_WARNING " *** Warning: IRQ %d is unlikely to work! ***\n", + dev->irq); + + /* Extract our information from the EEPROM data. */ + vp->info1 = eeprom[13]; + vp->info2 = eeprom[15]; + vp->capabilities = eeprom[16]; + + if (vp->info1 & 0x8000) + vp->full_duplex = 1; + + { + char *ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; + union wn3_config config; + EL3WINDOW(3); + vp->available_media = inw(ioaddr + Wn3_Options); + if ((vp->available_media & 0xff) == 0) /* Broken 3c916 */ + vp->available_media = 0x40; + config.i = inl(ioaddr + Wn3_Config); + if (vortex_debug > 1) + printk(KERN_DEBUG " Internal config register is %4.4x, " + "transceivers %#x.\n", config.i, inw(ioaddr + Wn3_Options)); + printk(KERN_INFO " %dK %s-wide RAM %s Rx:Tx split, %s%s interface.\n", + 8 << config.u.ram_size, + config.u.ram_width ? "word" : "byte", + ram_split[config.u.ram_split], + config.u.autoselect ? "autoselect/" : "", + config.u.xcvr ? "NWay Autonegotiation" : + media_tbl[config.u.xcvr].name); + vp->default_media = config.u.xcvr; + vp->autoselect = config.u.autoselect; + } + + if (vp->media_override != 7) { + printk(KERN_INFO " Media override to transceiver type %d (%s).\n", + vp->media_override, media_tbl[vp->media_override].name); + dev->if_port = vp->media_override; + } else + dev->if_port = vp->default_media; + + if (dev->if_port == XCVR_MII) { + int phy, phy_idx = 0; + EL3WINDOW(4); + for (phy = 0; phy < 32 && phy_idx < sizeof(vp->phys); phy++) { + int mii_status; + mdio_sync(ioaddr, 32); + mii_status = mdio_read(ioaddr, phy, 1); + if (mii_status && mii_status != 0xffff) { + vp->phys[phy_idx++] = phy; + printk(KERN_INFO " MII transceiver found at address %d, status %4x.\n", + phy, mii_status); + mdio_sync(ioaddr, 32); + if ((mdio_read(ioaddr, phy, 1) & 0x0040) == 0) + mii_preamble_required = 1; + } + } + if (phy_idx == 0) { + printk(KERN_WARNING" ***WARNING*** No MII transceivers found!\n"); + vp->phys[0] = 24; + } else { + vp->advertising = mdio_read(ioaddr, vp->phys[0], 4); + if (vp->full_duplex) { + /* Only advertise the FD media types. */ + vp->advertising &= 0x015F; + mdio_write(ioaddr, vp->phys[0], 4, vp->advertising); + } + } + } + + if (vp->capabilities & CapBusMaster) { + vp->full_bus_master_tx = 1; + printk(KERN_INFO" Enabling bus-master transmits and %s receives.\n", + (vp->info2 & 1) ? "early" : "whole-frame" ); + vp->full_bus_master_rx = (vp->info2 & 1) ? 1 : 2; + } + + /* We do a request_region() to register /proc/ioports info. */ + request_region(ioaddr, VORTEX_TOTAL_SIZE, vp->product_name); + + /* The 3c59x-specific entries in the device structure. */ + dev->open = &vortex_open; + dev->hard_start_xmit = &vortex_start_xmit; + dev->stop = &vortex_close; + dev->get_stats = &vortex_get_stats; +#ifdef HAVE_PRIVATE_IOCTL + dev->do_ioctl = &vortex_ioctl; +#endif +#ifdef NEW_MULTICAST + dev->set_multicast_list = &set_rx_mode; +#else + dev->set_multicast_list = &set_multicast_list; +#endif + + return 0; +} + + +static int +vortex_open(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct vortex_private *vp = (struct vortex_private *)dev->priv; + union wn3_config config; + int i; + + /* Before initializing select the active media port. */ + EL3WINDOW(3); + config.i = inl(ioaddr + Wn3_Config); + + if (vp->media_override != 7) { + if (vortex_debug > 1) + printk(KERN_INFO "%s: Media override to transceiver %d (%s).\n", + dev->name, vp->media_override, + media_tbl[vp->media_override].name); + dev->if_port = vp->media_override; + } else if (vp->autoselect) { + /* Find first available media type, starting with 100baseTx. */ + dev->if_port = XCVR_100baseTx; + while (! (vp->available_media & media_tbl[dev->if_port].mask)) + dev->if_port = media_tbl[dev->if_port].next; + + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Initial media type %s.\n", + dev->name, media_tbl[dev->if_port].name); + + init_timer(&vp->timer); + vp->timer.expires = RUN_AT(media_tbl[dev->if_port].wait); + vp->timer.data = (unsigned long)dev; + vp->timer.function = &vortex_timer; /* timer handler */ + add_timer(&vp->timer); + } else + dev->if_port = vp->default_media; + + config.u.xcvr = dev->if_port; + outl(config.i, ioaddr + Wn3_Config); + + if (dev->if_port == XCVR_MII) { + int mii_reg1, mii_reg5; + EL3WINDOW(4); + /* Read BMSR (reg1) only to clear old status. */ + mii_reg1 = mdio_read(ioaddr, vp->phys[0], 1); + mii_reg5 = mdio_read(ioaddr, vp->phys[0], 5); + if (mii_reg5 == 0xffff || mii_reg5 == 0x0000) + ; /* No MII device or no link partner report */ + else if ((mii_reg5 & 0x0100) != 0 /* 100baseTx-FD */ + || (mii_reg5 & 0x00C0) == 0x0040) /* 10T-FD, but not 100-HD */ + vp->full_duplex = 1; + if (vortex_debug > 1) + printk(KERN_INFO "%s: MII #%d status %4.4x, link partner capability %4.4x," + " setting %s-duplex.\n", dev->name, vp->phys[0], + mii_reg1, mii_reg5, vp->full_duplex ? "full" : "half"); + EL3WINDOW(3); + } + + /* Set the full-duplex bit. */ + outb(((vp->info1 & 0x8000) || vp->full_duplex ? 0x20 : 0) | + (dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); + + if (vortex_debug > 1) { + printk(KERN_DEBUG "%s: vortex_open() InternalConfig %8.8x.\n", + dev->name, config.i); + } + + outw(TxReset, ioaddr + EL3_CMD); + for (i = 2000; i >= 0 ; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + + outw(RxReset, ioaddr + EL3_CMD); + /* Wait a few ticks for the RxReset command to complete. */ + for (i = 2000; i >= 0 ; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + + outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + +#ifdef SA_SHIRQ + /* Use the now-standard shared IRQ implementation. */ + if (request_irq(dev->irq, &vortex_interrupt, SA_SHIRQ, dev->name, dev)) { + return -EAGAIN; + } +#else + if (dev->irq == 0 || irq2dev_map[dev->irq] != NULL) + return -EAGAIN; + irq2dev_map[dev->irq] = dev; + if (request_irq(dev->irq, &vortex_interrupt, 0, vp->product_name)) { + irq2dev_map[dev->irq] = NULL; + return -EAGAIN; + } +#endif + + if (vortex_debug > 1) { + EL3WINDOW(4); + printk(KERN_DEBUG "%s: vortex_open() irq %d media status %4.4x.\n", + dev->name, dev->irq, inw(ioaddr + Wn4_Media)); + } + + /* Set the station address and mask in window 2 each time opened. */ + EL3WINDOW(2); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + for (; i < 12; i+=2) + outw(0, ioaddr + i); + + if (dev->if_port == XCVR_10base2) + /* Start the thinnet transceiver. We should really wait 50ms...*/ + outw(StartCoax, ioaddr + EL3_CMD); + EL3WINDOW(4); + outw((inw(ioaddr + Wn4_Media) & ~(Media_10TP|Media_SQE)) | + media_tbl[dev->if_port].media_bits, ioaddr + Wn4_Media); + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 10; i++) + inb(ioaddr + i); + inw(ioaddr + 10); + inw(ioaddr + 12); + /* New: On the Vortex we must also clear the BadSSD counter. */ + EL3WINDOW(4); + inb(ioaddr + 12); + /* ..and on the Boomerang we enable the extra statistics bits. */ + outw(0x0040, ioaddr + Wn4_NetDiag); + + /* Switch to register set 7 for normal use. */ + EL3WINDOW(7); + + if (vp->full_bus_master_rx) { /* Boomerang bus master. */ + vp->cur_rx = vp->dirty_rx = 0; + /* Initialize the RxEarly register as recommended. */ + outw(SetRxThreshold + (1536>>2), ioaddr + EL3_CMD); + outl(0x0020, ioaddr + PktStatus); + if (vortex_debug > 2) + printk(KERN_DEBUG "%s: Filling in the Rx ring.\n", dev->name); + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb; + vp->rx_ring[i].next = virt_to_bus(&vp->rx_ring[i+1]); + vp->rx_ring[i].status = 0; /* Clear complete bit. */ + vp->rx_ring[i].length = PKT_BUF_SZ | LAST_FRAG; + skb = DEV_ALLOC_SKB(PKT_BUF_SZ); + vp->rx_skbuff[i] = skb; + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ +#if LINUX_VERSION_CODE >= 0x10300 + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + vp->rx_ring[i].addr = virt_to_bus(skb->tail); +#else + vp->rx_ring[i].addr = virt_to_bus(skb->data); +#endif + } + vp->rx_ring[i-1].next = virt_to_bus(&vp->rx_ring[0]); /* Wrap the ring. */ + outl(virt_to_bus(&vp->rx_ring[0]), ioaddr + UpListPtr); + } + if (vp->full_bus_master_tx) { /* Boomerang bus master Tx. */ + dev->hard_start_xmit = &boomerang_start_xmit; + vp->cur_tx = vp->dirty_tx = 0; + outb(PKT_BUF_SZ>>8, ioaddr + TxFreeThreshold); /* Room for a packet. */ + /* Clear the Tx ring. */ + for (i = 0; i < TX_RING_SIZE; i++) + vp->tx_skbuff[i] = 0; + outl(0, ioaddr + DownListPtr); + } + /* Set reciever mode: presumably accept b-case and phys addr only. */ + set_rx_mode(dev); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + + vp->in_interrupt = 0; + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + vp->status_enable = SetStatusEnb | HostError|IntReq|StatsFull|TxComplete| + (vp->full_bus_master_tx ? DownComplete : TxAvailable) | + (vp->full_bus_master_rx ? UpComplete : RxComplete) | + (vp->bus_master ? DMADone : 0); + outw(vp->status_enable, ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull + | HostError | TxComplete + | (vp->bus_master ? DMADone : 0) | UpComplete | DownComplete, + ioaddr + EL3_CMD); + + MOD_INC_USE_COUNT; + + return 0; +} + +static void vortex_timer(unsigned long data) +{ +#ifdef AUTOMEDIA + struct device *dev = (struct device *)data; + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + unsigned long flags; + int ok = 0; + + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Media selection timer tick happened, %s.\n", + dev->name, media_tbl[dev->if_port].name); + + save_flags(flags); cli(); { + int old_window = inw(ioaddr + EL3_CMD) >> 13; + int media_status; + EL3WINDOW(4); + media_status = inw(ioaddr + Wn4_Media); + switch (dev->if_port) { + case XCVR_10baseT: case XCVR_100baseTx: case XCVR_100baseFx: + if (media_status & Media_LnkBeat) { + ok = 1; + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Media %s has link beat, %x.\n", + dev->name, media_tbl[dev->if_port].name, media_status); + } else if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Media %s is has no link beat, %x.\n", + dev->name, media_tbl[dev->if_port].name, media_status); + + break; + case XCVR_MII: + { + int mii_reg1 = mdio_read(ioaddr, vp->phys[0], 1); + int mii_reg5 = mdio_read(ioaddr, vp->phys[0], 5); + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: MII #%d status register is %4.4x, " + "link partner capability %4.4x.\n", + dev->name, vp->phys[0], mii_reg1, mii_reg5); + if (mii_reg1 & 0x0004) + ok = 1; + break; + } + default: /* Other media types handled by Tx timeouts. */ + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Media %s is has no indication, %x.\n", + dev->name, media_tbl[dev->if_port].name, media_status); + ok = 1; + } + if ( ! ok) { + union wn3_config config; + + do { + dev->if_port = media_tbl[dev->if_port].next; + } while ( ! (vp->available_media & media_tbl[dev->if_port].mask)); + if (dev->if_port == XCVR_Default) { /* Go back to default. */ + dev->if_port = vp->default_media; + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Media selection failing, using default " + "%s port.\n", + dev->name, media_tbl[dev->if_port].name); + } else { + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Media selection failed, now trying " + "%s port.\n", + dev->name, media_tbl[dev->if_port].name); + vp->timer.expires = RUN_AT(media_tbl[dev->if_port].wait); + add_timer(&vp->timer); + } + outw((media_status & ~(Media_10TP|Media_SQE)) | + media_tbl[dev->if_port].media_bits, ioaddr + Wn4_Media); + + EL3WINDOW(3); + config.i = inl(ioaddr + Wn3_Config); + config.u.xcvr = dev->if_port; + outl(config.i, ioaddr + Wn3_Config); + + outw(dev->if_port == XCVR_10base2 ? StartCoax : StopCoax, + ioaddr + EL3_CMD); + } + EL3WINDOW(old_window); + } restore_flags(flags); + if (vortex_debug > 1) + printk(KERN_DEBUG "%s: Media selection timer finished, %s.\n", + dev->name, media_tbl[dev->if_port].name); + +#endif /* AUTOMEDIA*/ + return; +} + +static void vortex_tx_timeout(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + int j; + + printk(KERN_ERR "%s: transmit timed out, tx_status %2.2x status %4.4x.\n", + dev->name, inb(ioaddr + TxStatus), + inw(ioaddr + EL3_STATUS)); + /* Slight code bloat to be user friendly. */ + if ((inb(ioaddr + TxStatus) & 0x88) == 0x88) + printk(KERN_ERR "%s: Transmitter encountered 16 collisions --" + " network cable problem?\n", dev->name); + if (inw(ioaddr + EL3_STATUS) & IntLatch) { + printk(KERN_ERR "%s: Interrupt posted but not delivered --" + " IRQ blocked by another device?\n", dev->name); + /* Bad idea here.. but we might as well handle a few events. */ + vortex_interrupt IRQ(dev->irq, dev, 0); + } + outw(TxReset, ioaddr + EL3_CMD); + for (j = 200; j >= 0 ; j--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + +#if ! defined(final_version) && LINUX_VERSION_CODE >= 0x10300 + if (vp->full_bus_master_tx) { + int i; + printk(KERN_DEBUG " Flags; bus-master %d, full %d; dirty %d " + "current %d.\n", + vp->full_bus_master_tx, vp->tx_full, vp->dirty_tx, vp->cur_tx); + printk(KERN_DEBUG " Transmit list %8.8x vs. %p.\n", + inl(ioaddr + DownListPtr), + &vp->tx_ring[vp->dirty_tx % TX_RING_SIZE]); + for (i = 0; i < TX_RING_SIZE; i++) { + printk(KERN_DEBUG " %d: @%p length %8.8x status %8.8x\n", i, + &vp->tx_ring[i], + vp->tx_ring[i].length, + vp->tx_ring[i].status); + } + } +#endif + vp->stats.tx_errors++; + if (vp->full_bus_master_tx) { + if (vortex_debug > 0) + printk(KERN_DEBUG "%s: Resetting the Tx ring pointer.\n", + dev->name); + if (vp->cur_tx - vp->dirty_tx > 0 && inl(ioaddr + DownListPtr) == 0) + outl(virt_to_bus(&vp->tx_ring[vp->dirty_tx % TX_RING_SIZE]), + ioaddr + DownListPtr); + if (vp->tx_full && (vp->cur_tx - vp->dirty_tx <= TX_RING_SIZE - 1)) { + vp->tx_full = 0; + clear_bit(0, (void*)&dev->tbusy); + } + outb(PKT_BUF_SZ>>8, ioaddr + TxFreeThreshold); + outw(DownUnstall, ioaddr + EL3_CMD); + } else + vp->stats.tx_dropped++; + + /* Issue Tx Enable */ + outw(TxEnable, ioaddr + EL3_CMD); + dev->trans_start = jiffies; + + /* Switch to register set 7 for normal use. */ + EL3WINDOW(7); +} + +/* + * Handle uncommon interrupt sources. This is a separate routine to minimize + * the cache impact. + */ +static void +vortex_error(struct device *dev, int status) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + int do_tx_reset = 0; + int i; + + if (status & TxComplete) { /* Really "TxError" for us. */ + unsigned char tx_status = inb(ioaddr + TxStatus); + /* Presumably a tx-timeout. We must merely re-enable. */ + if (vortex_debug > 2 + || (tx_status != 0x88 && vortex_debug > 0)) + printk(KERN_DEBUG"%s: Transmit error, Tx status register %2.2x.\n", + dev->name, tx_status); + if (tx_status & 0x14) vp->stats.tx_fifo_errors++; + if (tx_status & 0x38) vp->stats.tx_aborted_errors++; + outb(0, ioaddr + TxStatus); + if (tx_status & 0x30) + do_tx_reset = 1; + else /* Merely re-enable the transmitter. */ + outw(TxEnable, ioaddr + EL3_CMD); + } + if (status & RxEarly) { /* Rx early is unused. */ + vortex_rx(dev); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & StatsFull) { /* Empty statistics. */ + static int DoneDidThat = 0; + if (vortex_debug > 4) + printk(KERN_DEBUG "%s: Updating stats.\n", dev->name); + update_stats(ioaddr, dev); + /* HACK: Disable statistics as an interrupt source. */ + /* This occurs when we have the wrong media type! */ + if (DoneDidThat == 0 && + inw(ioaddr + EL3_STATUS) & StatsFull) { + printk(KERN_WARNING "%s: Updating statistics failed, disabling " + "stats as an interrupt source.\n", dev->name); + EL3WINDOW(5); + outw(SetIntrEnb | (inw(ioaddr + 10) & ~StatsFull), ioaddr + EL3_CMD); + EL3WINDOW(7); + DoneDidThat++; + } + } + if (status & IntReq) /* Restore all interrupt sources. */ + outw(ioaddr + EL3_CMD, vp->status_enable); + if (status & HostError) { + u16 fifo_diag; + EL3WINDOW(4); + fifo_diag = inw(ioaddr + Wn4_FIFODiag); + if (vortex_debug > 0) + printk(KERN_ERR "%s: Host error, FIFO diagnostic register %4.4x.\n", + dev->name, fifo_diag); + /* Adapter failure requires Tx/Rx reset and reinit. */ + if (vp->full_bus_master_tx) { + outw(TotalReset | 0xff, ioaddr + EL3_CMD); + for (i = 2000; i >= 0 ; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + /* Re-enable the receiver. */ + outw(RxEnable, ioaddr + EL3_CMD); + outw(TxEnable, ioaddr + EL3_CMD); + } else if (fifo_diag & 0x0400) + do_tx_reset = 1; + if (fifo_diag & 0x3000) { + outw(RxReset, ioaddr + EL3_CMD); + for (i = 2000; i >= 0 ; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + /* Set the Rx filter to the current state. */ + set_rx_mode(dev); + outw(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */ + outw(AckIntr | HostError, ioaddr + EL3_CMD); + } + } + if (do_tx_reset) { + int j; + outw(TxReset, ioaddr + EL3_CMD); + for (j = 200; j >= 0 ; j--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + outw(TxEnable, ioaddr + EL3_CMD); + } + +} + + +static int +vortex_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start >= TX_TIMEOUT) + vortex_tx_timeout(dev); + return 1; + } + + /* Put out the doubleword header... */ + outl(skb->len, ioaddr + TX_FIFO); +#ifdef VORTEX_BUS_MASTER + if (vp->bus_master) { + /* Set the bus-master controller to transfer the packet. */ + outl(virt_to_bus(skb->data), ioaddr + Wn7_MasterAddr); + outw((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen); + vp->tx_skb = skb; + outw(StartDMADown, ioaddr + EL3_CMD); + /* dev->tbusy will be cleared at the DMADone interrupt. */ + } else { + /* ... and the packet rounded to a doubleword. */ + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + DEV_FREE_SKB(skb); + if (inw(ioaddr + TxFree) > 1536) { + clear_bit(0, (void*)&dev->tbusy); + } else + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); + } +#else + /* ... and the packet rounded to a doubleword. */ + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + DEV_FREE_SKB(skb); + if (inw(ioaddr + TxFree) > 1536) { + clear_bit(0, (void*)&dev->tbusy); + } else + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); +#endif /* bus master */ + + dev->trans_start = jiffies; + + /* Clear the Tx status stack. */ + { + int tx_status; + int i = 32; + + while (--i > 0 && (tx_status = inb(ioaddr + TxStatus)) > 0) { + if (tx_status & 0x3C) { /* A Tx-disabling error occurred. */ + if (vortex_debug > 2) + printk(KERN_DEBUG "%s: Tx error, status %2.2x.\n", + dev->name, tx_status); + if (tx_status & 0x04) vp->stats.tx_fifo_errors++; + if (tx_status & 0x38) vp->stats.tx_aborted_errors++; + if (tx_status & 0x30) { + int j; + outw(TxReset, ioaddr + EL3_CMD); + for (j = 200; j >= 0 ; j--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + } + outw(TxEnable, ioaddr + EL3_CMD); + } + outb(0x00, ioaddr + TxStatus); /* Pop the status stack. */ + } + } + return 0; +} + +static int +boomerang_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start >= TX_TIMEOUT) + vortex_tx_timeout(dev); + return 1; + } else { + /* Calculate the next Tx descriptor entry. */ + int entry = vp->cur_tx % TX_RING_SIZE; + struct boom_tx_desc *prev_entry = + &vp->tx_ring[(vp->cur_tx-1) % TX_RING_SIZE]; + unsigned long flags; + int i; + + if (vortex_debug > 3) + printk(KERN_DEBUG "%s: Trying to send a packet, Tx index %d.\n", + dev->name, vp->cur_tx); + if (vp->tx_full) { + if (vortex_debug >0) + printk(KERN_WARNING "%s: Tx Ring full, refusing to send buffer.\n", + dev->name); + return 1; + } + /* end change 06/25/97 M. Sievers */ + vp->tx_skbuff[entry] = skb; + vp->tx_ring[entry].next = 0; + vp->tx_ring[entry].addr = virt_to_bus(skb->data); + vp->tx_ring[entry].length = skb->len | LAST_FRAG; + vp->tx_ring[entry].status = skb->len | TxIntrUploaded; + + save_flags(flags); + cli(); + outw(DownStall, ioaddr + EL3_CMD); + /* Wait for the stall to complete. */ + for (i = 600; i >= 0 ; i--) + if ( (inw(ioaddr + EL3_STATUS) & CmdInProgress) == 0) + break; + prev_entry->next = virt_to_bus(&vp->tx_ring[entry]); + if (inl(ioaddr + DownListPtr) == 0) { + outl(virt_to_bus(&vp->tx_ring[entry]), ioaddr + DownListPtr); + queued_packet++; + } + outw(DownUnstall, ioaddr + EL3_CMD); + restore_flags(flags); + + vp->cur_tx++; + if (vp->cur_tx - vp->dirty_tx > TX_RING_SIZE - 1) + vp->tx_full = 1; + else { /* Clear previous interrupt enable. */ + prev_entry->status &= ~TxIntrUploaded; + clear_bit(0, (void*)&dev->tbusy); + } + dev->trans_start = jiffies; + return 0; + } +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void vortex_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs) +{ +#ifdef SA_SHIRQ /* Use the now-standard shared IRQ implementation. */ + struct device *dev = dev_id; +#else + struct device *dev = (struct device *)(irq2dev_map[irq]); +#endif + struct vortex_private *vp; + int ioaddr, status; + int latency; + int work_done = max_interrupt_work; + + vp = (struct vortex_private *)dev->priv; + if (test_and_set_bit(0, (void*)&vp->in_interrupt)) { + printk(KERN_ERR "%s: Re-entering the interrupt handler.\n", dev->name); + return; + } + + dev->interrupt = 1; + ioaddr = dev->base_addr; + latency = inb(ioaddr + Timer); + + status = inw(ioaddr + EL3_STATUS); + + if (vortex_debug > 4) + printk(KERN_DEBUG "%s: interrupt, status %4.4x, latency %d ticks.\n", + dev->name, status, latency); + do { + if (vortex_debug > 5) + printk(KERN_DEBUG "%s: In interrupt loop, status %4.4x.\n", + dev->name, status); + if (status & RxComplete) + vortex_rx(dev); + if (status & UpComplete) { + outw(AckIntr | UpComplete, ioaddr + EL3_CMD); + boomerang_rx(dev); + } + + if (status & TxAvailable) { + if (vortex_debug > 5) + printk(KERN_DEBUG " TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + + if (status & DownComplete) { + unsigned int dirty_tx = vp->dirty_tx; + + while (vp->cur_tx - dirty_tx > 0) { + int entry = dirty_tx % TX_RING_SIZE; + if (inl(ioaddr + DownListPtr) == + virt_to_bus(&vp->tx_ring[entry])) + break; /* It still hasn't been processed. */ + if (vp->tx_skbuff[entry]) { + DEV_FREE_SKB(vp->tx_skbuff[entry]); + vp->tx_skbuff[entry] = 0; + } + /* vp->stats.tx_packets++; Counted below. */ + dirty_tx++; + } + vp->dirty_tx = dirty_tx; + outw(AckIntr | DownComplete, ioaddr + EL3_CMD); + if (vp->tx_full && (vp->cur_tx - dirty_tx <= TX_RING_SIZE - 1)) { + vp->tx_full= 0; + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + } +#ifdef VORTEX_BUS_MASTER + if (status & DMADone) { + outw(0x1000, ioaddr + Wn7_MasterStatus); /* Ack the event. */ + clear_bit(0, (void*)&dev->tbusy); + DEV_FREE_SKB(vp->tx_skb); /* Release the transfered buffer */ + mark_bh(NET_BH); + } +#endif + /* Check for all uncommon interrupts at once. */ + if (status & (HostError | RxEarly | StatsFull | TxComplete | IntReq)) + vortex_error(dev, status); + + if (--work_done < 0) { + if ((status & (0x7fe - (UpComplete | DownComplete))) == 0) { + /* Just ack these and return. */ + outw(AckIntr | UpComplete | DownComplete, ioaddr + EL3_CMD); + } else { + printk(KERN_WARNING "%s: Too much work in interrupt, status " + "%4.4x. Temporarily disabling functions (%4.4x).\n", + dev->name, status, SetStatusEnb | ((~status) & 0x7FE)); + /* Disable all pending interrupts. */ + outw(SetStatusEnb | ((~status) & 0x7FE), ioaddr + EL3_CMD); + outw(AckIntr | 0x7FF, ioaddr + EL3_CMD); + /* Set a timer to reenable interrupts. */ + + break; + } + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); + + } while ((status = inw(ioaddr + EL3_STATUS)) & (IntLatch | RxComplete)); + + if (vortex_debug > 4) + printk(KERN_DEBUG "%s: exiting interrupt, status %4.4x.\n", + dev->name, status); + + dev->interrupt = 0; + clear_bit(0, (void*)&vp->in_interrupt); + return; +} + +static int +vortex_rx(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + short rx_status; + + if (vortex_debug > 5) + printk(KERN_DEBUG" In rx_packet(), status %4.4x, rx_status %4.4x.\n", + inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); + while ((rx_status = inw(ioaddr + RxStatus)) > 0) { + if (rx_status & 0x4000) { /* Error, update stats. */ + unsigned char rx_error = inb(ioaddr + RxErrors); + if (vortex_debug > 2) + printk(KERN_DEBUG " Rx error: status %2.2x.\n", rx_error); + vp->stats.rx_errors++; + if (rx_error & 0x01) vp->stats.rx_over_errors++; + if (rx_error & 0x02) vp->stats.rx_length_errors++; + if (rx_error & 0x04) vp->stats.rx_frame_errors++; + if (rx_error & 0x08) vp->stats.rx_crc_errors++; + if (rx_error & 0x10) vp->stats.rx_length_errors++; + } else { + /* The packet length: up to 4.5K!. */ + int pkt_len = rx_status & 0x1fff; + struct sk_buff *skb; + + skb = DEV_ALLOC_SKB(pkt_len + 5); + if (vortex_debug > 4) + printk(KERN_DEBUG "Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb->dev = dev; +#if LINUX_VERSION_CODE >= 0x10300 + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + /* 'skb_put()' points to the start of sk_buff data area. */ + insl(ioaddr + RX_FIFO, skb_put(skb, pkt_len), + (pkt_len + 3) >> 2); + outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ + skb->protocol = eth_type_trans(skb, dev); +#else + skb->len = pkt_len; + /* 'skb->data' points to the start of sk_buff data area. */ + insl(ioaddr + RX_FIFO, skb->data, (pkt_len + 3) >> 2); + outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ +#endif /* KERNEL_1_3_0 */ + netif_rx(skb); + dev->last_rx = jiffies; + vp->stats.rx_packets++; + /* Wait a limited time to go to next packet. */ + for (i = 200; i >= 0; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + continue; + } else if (vortex_debug) + printk(KERN_NOTICE "%s: No memory to allocate a sk_buff of " + "size %d.\n", dev->name, pkt_len); + } + outw(RxDiscard, ioaddr + EL3_CMD); + vp->stats.rx_dropped++; + /* Wait a limited time to skip this packet. */ + for (i = 200; i >= 0; i--) + if ( ! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) + break; + } + + return 0; +} + +static int +boomerang_rx(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int entry = vp->cur_rx % RX_RING_SIZE; + int ioaddr = dev->base_addr; + int rx_status; + int rx_work_limit = vp->dirty_rx + RX_RING_SIZE - vp->cur_rx; + + if (vortex_debug > 5) + printk(KERN_DEBUG " In boomerang_rx(), status %4.4x, rx_status " + "%4.4x.\n", + inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); + while ((--rx_work_limit >= 0) && + ((rx_status = vp->rx_ring[entry].status) & RxDComplete)) { + if (rx_status & RxDError) { /* Error, update stats. */ + unsigned char rx_error = rx_status >> 16; + if (vortex_debug > 2) + printk(KERN_DEBUG " Rx error: status %2.2x.\n", rx_error); + vp->stats.rx_errors++; + if (rx_error & 0x01) vp->stats.rx_over_errors++; + if (rx_error & 0x02) vp->stats.rx_length_errors++; + if (rx_error & 0x04) vp->stats.rx_frame_errors++; + if (rx_error & 0x08) vp->stats.rx_crc_errors++; + if (rx_error & 0x10) vp->stats.rx_length_errors++; + } else { + /* The packet length: up to 4.5K!. */ + int pkt_len = rx_status & 0x1fff; + struct sk_buff *skb; + + if (vortex_debug > 4) + printk(KERN_DEBUG "Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + + /* Check if the packet is long enough to just accept without + copying to a properly sized skbuff. */ + if (pkt_len < rx_copybreak + && (skb = DEV_ALLOC_SKB(pkt_len + 2)) != 0) { + skb->dev = dev; +#if LINUX_VERSION_CODE >= 0x10300 + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + /* 'skb_put()' points to the start of sk_buff data area. */ + memcpy(skb_put(skb, pkt_len), + bus_to_virt(vp->rx_ring[entry].addr), + pkt_len); +#else + memcpy(skb->data, bus_to_virt(vp->rx_ring[entry].addr), pkt_len); + skb->len = pkt_len; +#endif + rx_copy++; + } else{ + void *temp; + /* Pass up the skbuff already on the Rx ring. */ + skb = vp->rx_skbuff[entry]; + if (skb == NULL) { + printk(KERN_WARNING "%s: in boomerang_rx -- attempt to use NULL skb caught\n", dev->name); + break; + } + vp->rx_skbuff[entry] = NULL; +#if LINUX_VERSION_CODE >= 0x10300 + temp = skb_put(skb, pkt_len); +#else + temp = skb->data; +#endif + /* Remove this checking code for final release. */ + if (bus_to_virt(vp->rx_ring[entry].addr) != temp) + printk(KERN_ERR "%s: Warning -- the skbuff addresses do not match" + " in boomerang_rx: %p vs. %p.\n", dev->name, + bus_to_virt(vp->rx_ring[entry].addr), temp); + rx_nocopy++; + } +#if LINUX_VERSION_CODE > 0x10300 + skb->protocol = eth_type_trans(skb, dev); + { /* Use hardware checksum info. */ + int csum_bits = rx_status & 0xee000000; + if (csum_bits && + (csum_bits == (IPChksumValid | TCPChksumValid) || + csum_bits == (IPChksumValid | UDPChksumValid))) { + skb->ip_summed = CHECKSUM_UNNECESSARY; + rx_csumhits++; + } + } +#else + skb->len = pkt_len; +#endif + netif_rx(skb); + dev->last_rx = jiffies; + vp->stats.rx_packets++; + } + entry = (++vp->cur_rx) % RX_RING_SIZE; + } + /* Refill the Rx ring buffers. */ + for (; vp->dirty_rx < vp->cur_rx; vp->dirty_rx++) { + struct sk_buff *skb; + entry = vp->dirty_rx % RX_RING_SIZE; + if (vp->rx_skbuff[entry] == NULL) { + skb = DEV_ALLOC_SKB(PKT_BUF_SZ); + if (skb == NULL) { + printk(KERN_DEBUG "%s: in boomerang_rx -- could not allocate skbuff\n", dev->name); + break; /* Bad news! */ + } + skb->dev = dev; /* Mark as being used by this device. */ +#if LINUX_VERSION_CODE > 0x10300 + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + vp->rx_ring[entry].addr = virt_to_bus(skb->tail); +#else + vp->rx_ring[entry].addr = virt_to_bus(skb->data); +#endif + vp->rx_skbuff[entry] = skb; + } + vp->rx_ring[entry].status = 0; /* Clear complete bit. */ + outw(UpUnstall, ioaddr + EL3_CMD); + } + + if (vp->dirty_rx >= RX_RING_SIZE ) { + vp->cur_rx -= RX_RING_SIZE; + vp->dirty_rx -= RX_RING_SIZE; + } + + return 0; +} + +static int +vortex_close(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (vortex_debug > 1) { + printk(KERN_DEBUG"%s: vortex_close() status %4.4x, Tx status %2.2x.\n", + dev->name, inw(ioaddr + EL3_STATUS), inb(ioaddr + TxStatus)); + printk(KERN_DEBUG "%s: vortex close stats: rx_nocopy %d rx_copy %d" + " tx_queued %d Rx pre-checksummed %d.\n", + dev->name, rx_nocopy, rx_copy, queued_packet, rx_csumhits); + } + + del_timer(&vp->timer); + + /* Turn off statistics ASAP. We update vp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + if (dev->if_port == XCVR_10base2) + /* Turn off thinnet power. Green! */ + outw(StopCoax, ioaddr + EL3_CMD); + +#ifdef SA_SHIRQ + free_irq(dev->irq, dev); +#else + free_irq(dev->irq); + irq2dev_map[dev->irq] = 0; +#endif + + outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); + + update_stats(ioaddr, dev); + if (vp->full_bus_master_rx) { /* Free Boomerang bus master Rx buffers. */ + outl(0, ioaddr + UpListPtr); + for (i = 0; i < RX_RING_SIZE; i++) + if (vp->rx_skbuff[i]) { +#if LINUX_VERSION_CODE < 0x20100 + vp->rx_skbuff[i]->free = 1; +#endif + DEV_FREE_SKB(vp->rx_skbuff[i]); + vp->rx_skbuff[i] = 0; + } + } + if (vp->full_bus_master_tx) { /* Free Boomerang bus master Tx buffers. */ + outl(0, ioaddr + DownListPtr); + for (i = 0; i < TX_RING_SIZE; i++) + if (vp->tx_skbuff[i]) { + DEV_FREE_SKB(vp->tx_skbuff[i]); + vp->tx_skbuff[i] = 0; + } + } + + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics * +vortex_get_stats(struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + unsigned long flags; + + if (dev->start) { + save_flags(flags); + cli(); + update_stats(dev->base_addr, dev); + restore_flags(flags); + } + return &vp->stats; +} + +/* Update statistics. + Unlike with the EL3 we need not worry about interrupts changing + the window setting from underneath us, but we must still guard + against a race condition with a StatsUpdate interrupt updating the + table. This is done by checking that the ASM (!) code generated uses + atomic updates with '+='. + */ +static void update_stats(int ioaddr, struct device *dev) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + + /* Unlike the 3c5x9 we need not turn off stats updates while reading. */ + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + vp->stats.tx_carrier_errors += inb(ioaddr + 0); + vp->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + vp->stats.collisions += inb(ioaddr + 3); + vp->stats.tx_window_errors += inb(ioaddr + 4); + vp->stats.rx_fifo_errors += inb(ioaddr + 5); + vp->stats.tx_packets += inb(ioaddr + 6); + vp->stats.tx_packets += (inb(ioaddr + 9)&0x30) << 4; + /* Rx packets */ inb(ioaddr + 7); /* Must read to clear */ + /* Tx deferrals */ inb(ioaddr + 8); + /* Don't bother with register 9, an extension of registers 6&7. + If we do use the 6&7 values the atomic update assumption above + is invalid. */ + inw(ioaddr + 10); /* Total Rx and Tx octets. */ + inw(ioaddr + 12); + /* New: On the Vortex we must also clear the BadSSD counter. */ + EL3WINDOW(4); + inb(ioaddr + 12); + + /* We change back to window 7 (not 1) with the Vortex. */ + EL3WINDOW(7); + return; +} + +#ifdef HAVE_PRIVATE_IOCTL +static int vortex_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct vortex_private *vp = (struct vortex_private *)dev->priv; + int ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_data; + int phy = vp->phys[0] & 0x1f; + + if (vortex_debug > 2) + printk(KERN_DEBUG "%s: In ioct(%-.6s, %#4.4x) %4.4x %4.4x %4.4x %4.4x.\n", + dev->name, rq->ifr_ifrn.ifrn_name, cmd, + data[0], data[1], data[2], data[3]); + + switch(cmd) { + case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */ + data[0] = phy; + case SIOCDEVPRIVATE+1: /* Read the specified MII register. */ + EL3WINDOW(4); + data[3] = mdio_read(ioaddr, data[0] & 0x1f, data[1] & 0x1f); + return 0; + case SIOCDEVPRIVATE+2: /* Write the specified MII register */ + if (!suser()) + return -EPERM; + mdio_write(ioaddr, data[0] & 0x1f, data[1] & 0x1f, data[2]); + return 0; + default: + return -EOPNOTSUPP; + } +} +#endif /* HAVE_PRIVATE_IOCTL */ + +/* This new version of set_rx_mode() supports v1.4 kernels. + The Vortex chip has no documented multicast filter, so the only + multicast setting is to receive all multicast frames. At least + the chip has a very clean way to set the mode, unlike many others. */ +static void +set_rx_mode(struct device *dev) +{ + int ioaddr = dev->base_addr; + int new_mode; + + if (dev->flags & IFF_PROMISC) { + if (vortex_debug > 0) + printk(KERN_NOTICE "%s: Setting promiscuous mode.\n", dev->name); + new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast|RxProm; + } else if ((dev->mc_list) || (dev->flags & IFF_ALLMULTI)) { + new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast; + } else + new_mode = SetRxFilter | RxStation | RxBroadcast; + + outw(new_mode, ioaddr + EL3_CMD); +} +#ifndef NEW_MULTICAST +/* The old interface to set the Rx mode. */ +static void +set_multicast_list(struct device *dev, int num_addrs, void *addrs) +{ + set_rx_mode(dev); +} +#endif + + +/* MII transceiver control section. + Read and write the MII registers using software-generated serial + MDIO protocol. See the MII specifications or DP83840A data sheet + for details. */ + +/* The maximum data clock rate is 2.5 Mhz. The minimum timing is usually + met by back-to-back PCI I/O cycles, but we insert a delay to avoid + "overclocking" issues. */ +#define mdio_delay() udelay(1) + +#define MDIO_SHIFT_CLK 0x01 +#define MDIO_DIR_WRITE 0x04 +#define MDIO_DATA_WRITE0 (0x00 | MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (0x02 | MDIO_DIR_WRITE) +#define MDIO_DATA_READ 0x02 +#define MDIO_ENB_IN 0x00 + +/* Generate the preamble required for initial synchronization and + a few older transceivers. */ +static void mdio_sync(int ioaddr, int bits) +{ + int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + + /* Establish sync by sending at least 32 logic ones. */ + while (-- bits >= 0) { + outw(MDIO_DATA_WRITE1, mdio_addr); + mdio_delay(); + outw(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } +} + +static int mdio_read(int ioaddr, int phy_id, int location) +{ + int i; + int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; + unsigned int retval = 0; + int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + + if (mii_preamble_required) + mdio_sync(ioaddr, 32); + + /* Shift the read command bits out. */ + for (i = 14; i >= 0; i--) { + int dataval = (read_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outw(dataval, mdio_addr); + mdio_delay(); + outw(dataval | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + /* Read the two transition, 16 data, and wire-idle bits. */ + for (i = 19; i > 0; i--) { + outw(MDIO_ENB_IN, mdio_addr); + mdio_delay(); + retval = (retval << 1) | ((inw(mdio_addr) & MDIO_DATA_READ) ? 1 : 0); + outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + return retval>>1 & 0xffff; +} + +static void mdio_write(int ioaddr, int phy_id, int location, int value) +{ + int write_cmd = 0x50020000 | (phy_id << 23) | (location << 18) | value; + int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + int i; + + if (mii_preamble_required) + mdio_sync(ioaddr, 32); + + /* Shift the command bits out. */ + for (i = 31; i >= 0; i--) { + int dataval = (write_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outw(dataval, mdio_addr); + mdio_delay(); + outw(dataval | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + /* Leave the interface idle. */ + for (i = 1; i >= 0; i--) { + outw(MDIO_ENB_IN, mdio_addr); + mdio_delay(); + outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + + return; +} + + +#ifdef MODULE +void +cleanup_module(void) +{ + struct device *next_dev; + +#ifdef CARDBUS + unregister_driver(&vortex_ops); +#endif + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_vortex_dev) { + next_dev = ((struct vortex_private *)root_vortex_dev->priv)->next_module; + unregister_netdev(root_vortex_dev); + outw(TotalReset, root_vortex_dev->base_addr + EL3_CMD); + release_region(root_vortex_dev->base_addr, VORTEX_TOTAL_SIZE); + kfree(root_vortex_dev); + root_vortex_dev = next_dev; + } +} + +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c 3c59x.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * SMP-compile-command: "gcc -D__SMP__ -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c 3c59x.c" + * compile-command-alt1: "gcc -DCARDBUS -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c 3c59x.c -o 3c59x_cb.o" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/8390.c b/linux/src/drivers/net/8390.c new file mode 100644 index 00000000..9864106b --- /dev/null +++ b/linux/src/drivers/net/8390.c @@ -0,0 +1,832 @@ +/* 8390.c: A general NS8390 ethernet driver core for linux. */ +/* + Written 1992-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This is the chip-specific code for many 8390-based ethernet adaptors. + This is not a complete driver, it must be combined with board-specific + code such as ne.c, wd.c, 3c503.c, etc. + + Seeing how at least eight drivers use this code, (not counting the + PCMCIA ones either) it is easy to break some card by what seems like + a simple innocent change. Please contact me or Donald if you think + you have found something that needs changing. -- PG + + + Changelog: + + Paul Gortmaker : remove set_bit lock, other cleanups. + Paul Gortmaker : add ei_get_8390_hdr() so we can pass skb's to + ei_block_input() for eth_io_copy_and_sum(). + Paul Gortmaker : exchange static int ei_pingpong for a #define, + also add better Tx error handling. + Paul Gortmaker : rewrite Rx overrun handling as per NS specs. + + + Sources: + The National Semiconductor LAN Databook, and the 3Com 3c503 databook. + + */ + +static const char *version = + "8390.c:v1.10 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/in.h> +#include <linux/interrupt.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include "8390.h" + +/* These are the operational function interfaces to board-specific + routines. + void reset_8390(struct device *dev) + Resets the board associated with DEV, including a hardware reset of + the 8390. This is only called when there is a transmit timeout, and + it is always followed by 8390_init(). + void block_output(struct device *dev, int count, const unsigned char *buf, + int start_page) + Write the COUNT bytes of BUF to the packet buffer at START_PAGE. The + "page" value uses the 8390's 256-byte pages. + void get_8390_hdr(struct device *dev, struct e8390_hdr *hdr, int ring_page) + Read the 4 byte, page aligned 8390 header. *If* there is a + subsequent read, it will be of the rest of the packet. + void block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) + Read COUNT bytes from the packet buffer into the skb data area. Start + reading from RING_OFFSET, the address as the 8390 sees it. This will always + follow the read of the 8390 header. +*/ +#define ei_reset_8390 (ei_local->reset_8390) +#define ei_block_output (ei_local->block_output) +#define ei_block_input (ei_local->block_input) +#define ei_get_8390_hdr (ei_local->get_8390_hdr) + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifdef EI_DEBUG +int ei_debug = EI_DEBUG; +#else +int ei_debug = 1; +#endif + +/* Index to functions. */ +static void ei_tx_intr(struct device *dev); +static void ei_tx_err(struct device *dev); +static void ei_receive(struct device *dev); +static void ei_rx_overrun(struct device *dev); + +/* Routines generic to NS8390-based boards. */ +static void NS8390_trigger_send(struct device *dev, unsigned int length, + int start_page); +static void set_multicast_list(struct device *dev); + + +/* Open/initialize the board. This routine goes all-out, setting everything + up anew at each open, even though many of these registers should only + need to be set once at boot. + */ +int ei_open(struct device *dev) +{ + struct ei_device *ei_local = (struct ei_device *) dev->priv; + + /* This can't happen unless somebody forgot to call ethdev_init(). */ + if (ei_local == NULL) { + printk(KERN_EMERG "%s: ei_open passed a non-existent device!\n", dev->name); + return -ENXIO; + } + + irq2dev_map[dev->irq] = dev; + NS8390_init(dev, 1); + dev->start = 1; + ei_local->irqlock = 0; + return 0; +} + +/* Opposite of above. Only used when "ifconfig <devname> down" is done. */ +int ei_close(struct device *dev) +{ + NS8390_init(dev, 0); + dev->start = 0; + return 0; +} + +static int ei_start_xmit(struct sk_buff *skb, struct device *dev) +{ + int e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + int length, send_length, output_page; + +/* + * We normally shouldn't be called if dev->tbusy is set, but the + * existing code does anyway. If it has been too long since the + * last Tx, we assume the board has died and kick it. + */ + + if (dev->tbusy) { /* Do timeouts, just like the 8003 driver. */ + int txsr = inb(e8390_base+EN0_TSR), isr; + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < TX_TIMEOUT || (tickssofar < (TX_TIMEOUT+5) && ! (txsr & ENTSR_PTX))) { + return 1; + } + isr = inb(e8390_base+EN0_ISR); + if (dev->start == 0) { + printk("%s: xmit on stopped card\n", dev->name); + return 1; + } + + /* + * Note that if the Tx posted a TX_ERR interrupt, then the + * error will have been handled from the interrupt handler. + * and not here. + */ + + printk(KERN_DEBUG "%s: Tx timed out, %s TSR=%#2x, ISR=%#2x, t=%d.\n", + dev->name, (txsr & ENTSR_ABT) ? "excess collisions." : + (isr) ? "lost interrupt?" : "cable problem?", txsr, isr, tickssofar); + + if (!isr && !ei_local->stat.tx_packets) { + /* The 8390 probably hasn't gotten on the cable yet. */ + ei_local->interface_num ^= 1; /* Try a different xcvr. */ + } + + /* Try to restart the card. Perhaps the user has fixed something. */ + ei_reset_8390(dev); + NS8390_init(dev, 1); + dev->trans_start = jiffies; + } + + /* Sending a NULL skb means some higher layer thinks we've missed an + tx-done interrupt. Caution: dev_tint() handles the cli()/sti() + itself. */ + if (skb == NULL) { + dev_tint(dev); + return 0; + } + + length = skb->len; + if (skb->len <= 0) + return 0; + + /* Mask interrupts from the ethercard. */ + outb_p(0x00, e8390_base + EN0_IMR); + if (dev->interrupt) { + printk("%s: Tx request while isr active.\n",dev->name); + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + return 1; + } + ei_local->irqlock = 1; + + send_length = ETH_ZLEN < length ? length : ETH_ZLEN; + +#ifdef EI_PINGPONG + + /* + * We have two Tx slots available for use. Find the first free + * slot, and then perform some sanity checks. With two Tx bufs, + * you get very close to transmitting back-to-back packets. With + * only one Tx buf, the transmitter sits idle while you reload the + * card, leaving a substantial gap between each transmitted packet. + */ + + if (ei_local->tx1 == 0) { + output_page = ei_local->tx_start_page; + ei_local->tx1 = send_length; + if (ei_debug && ei_local->tx2 > 0) + printk("%s: idle transmitter tx2=%d, lasttx=%d, txing=%d.\n", + dev->name, ei_local->tx2, ei_local->lasttx, ei_local->txing); + } else if (ei_local->tx2 == 0) { + output_page = ei_local->tx_start_page + TX_1X_PAGES; + ei_local->tx2 = send_length; + if (ei_debug && ei_local->tx1 > 0) + printk("%s: idle transmitter, tx1=%d, lasttx=%d, txing=%d.\n", + dev->name, ei_local->tx1, ei_local->lasttx, ei_local->txing); + } else { /* We should never get here. */ + if (ei_debug) + printk("%s: No Tx buffers free! irq=%d tx1=%d tx2=%d last=%d\n", + dev->name, dev->interrupt, ei_local->tx1, ei_local->tx2, ei_local->lasttx); + ei_local->irqlock = 0; + dev->tbusy = 1; + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + return 1; + } + + /* + * Okay, now upload the packet and trigger a send if the transmitter + * isn't already sending. If it is busy, the interrupt handler will + * trigger the send later, upon receiving a Tx done interrupt. + */ + + ei_block_output(dev, length, skb->data, output_page); + if (! ei_local->txing) { + ei_local->txing = 1; + NS8390_trigger_send(dev, send_length, output_page); + dev->trans_start = jiffies; + if (output_page == ei_local->tx_start_page) { + ei_local->tx1 = -1; + ei_local->lasttx = -1; + } else { + ei_local->tx2 = -1; + ei_local->lasttx = -2; + } + } else + ei_local->txqueue++; + + dev->tbusy = (ei_local->tx1 && ei_local->tx2); + +#else /* EI_PINGPONG */ + + /* + * Only one Tx buffer in use. You need two Tx bufs to come close to + * back-to-back transmits. Expect a 20 -> 25% performance hit on + * reasonable hardware if you only use one Tx buffer. + */ + + ei_block_output(dev, length, skb->data, ei_local->tx_start_page); + ei_local->txing = 1; + NS8390_trigger_send(dev, send_length, ei_local->tx_start_page); + dev->trans_start = jiffies; + dev->tbusy = 1; + +#endif /* EI_PINGPONG */ + + /* Turn 8390 interrupts back on. */ + ei_local->irqlock = 0; + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + + dev_kfree_skb (skb, FREE_WRITE); + + return 0; +} + +/* The typical workload of the driver: + Handle the ether interface interrupts. */ +void ei_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + int e8390_base; + int interrupts, nr_serviced = 0; + struct ei_device *ei_local; + + if (dev == NULL) { + printk ("net_interrupt(): irq %d for unknown device.\n", irq); + return; + } + e8390_base = dev->base_addr; + ei_local = (struct ei_device *) dev->priv; + if (dev->interrupt || ei_local->irqlock) { + /* The "irqlock" check is only for testing. */ + printk(ei_local->irqlock + ? "%s: Interrupted while interrupts are masked! isr=%#2x imr=%#2x.\n" + : "%s: Reentering the interrupt handler! isr=%#2x imr=%#2x.\n", + dev->name, inb_p(e8390_base + EN0_ISR), + inb_p(e8390_base + EN0_IMR)); + return; + } + + dev->interrupt = 1; + + /* Change to page 0 and read the intr status reg. */ + outb_p(E8390_NODMA+E8390_PAGE0, e8390_base + E8390_CMD); + if (ei_debug > 3) + printk("%s: interrupt(isr=%#2.2x).\n", dev->name, + inb_p(e8390_base + EN0_ISR)); + + /* !!Assumption!! -- we stay in page 0. Don't break this. */ + while ((interrupts = inb_p(e8390_base + EN0_ISR)) != 0 + && ++nr_serviced < MAX_SERVICE) { + if (dev->start == 0) { + printk("%s: interrupt from stopped card\n", dev->name); + interrupts = 0; + break; + } + if (interrupts & ENISR_OVER) { + ei_rx_overrun(dev); + } else if (interrupts & (ENISR_RX+ENISR_RX_ERR)) { + /* Got a good (?) packet. */ + ei_receive(dev); + } + /* Push the next to-transmit packet through. */ + if (interrupts & ENISR_TX) { + ei_tx_intr(dev); + } else if (interrupts & ENISR_TX_ERR) { + ei_tx_err(dev); + } + + if (interrupts & ENISR_COUNTERS) { + ei_local->stat.rx_frame_errors += inb_p(e8390_base + EN0_COUNTER0); + ei_local->stat.rx_crc_errors += inb_p(e8390_base + EN0_COUNTER1); + ei_local->stat.rx_missed_errors+= inb_p(e8390_base + EN0_COUNTER2); + outb_p(ENISR_COUNTERS, e8390_base + EN0_ISR); /* Ack intr. */ + } + + /* Ignore any RDC interrupts that make it back to here. */ + if (interrupts & ENISR_RDC) { + outb_p(ENISR_RDC, e8390_base + EN0_ISR); + } + + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, e8390_base + E8390_CMD); + } + + if (interrupts && ei_debug) { + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, e8390_base + E8390_CMD); + if (nr_serviced >= MAX_SERVICE) { + printk("%s: Too much work at interrupt, status %#2.2x\n", + dev->name, interrupts); + outb_p(ENISR_ALL, e8390_base + EN0_ISR); /* Ack. most intrs. */ + } else { + printk("%s: unknown interrupt %#2x\n", dev->name, interrupts); + outb_p(0xff, e8390_base + EN0_ISR); /* Ack. all intrs. */ + } + } + dev->interrupt = 0; + return; +} + +/* + * A transmitter error has happened. Most likely excess collisions (which + * is a fairly normal condition). If the error is one where the Tx will + * have been aborted, we try and send another one right away, instead of + * letting the failed packet sit and collect dust in the Tx buffer. This + * is a much better solution as it avoids kernel based Tx timeouts, and + * an unnecessary card reset. + */ + +static void ei_tx_err(struct device *dev) +{ + int e8390_base = dev->base_addr; + unsigned char txsr = inb_p(e8390_base+EN0_TSR); + unsigned char tx_was_aborted = txsr & (ENTSR_ABT+ENTSR_FU); + struct ei_device *ei_local = (struct ei_device *) dev->priv; + +#ifdef VERBOSE_ERROR_DUMP + printk(KERN_DEBUG "%s: transmitter error (%#2x): ", dev->name, txsr); + if (txsr & ENTSR_ABT) + printk("excess-collisions "); + if (txsr & ENTSR_ND) + printk("non-deferral "); + if (txsr & ENTSR_CRS) + printk("lost-carrier "); + if (txsr & ENTSR_FU) + printk("FIFO-underrun "); + if (txsr & ENTSR_CDH) + printk("lost-heartbeat "); + printk("\n"); +#endif + + outb_p(ENISR_TX_ERR, e8390_base + EN0_ISR); /* Ack intr. */ + + if (tx_was_aborted) + ei_tx_intr(dev); + + /* + * Note: NCR reads zero on 16 collisions so we add them + * in by hand. Somebody might care... + */ + if (txsr & ENTSR_ABT) + ei_local->stat.collisions += 16; + +} + +/* We have finished a transmit: check for errors and then trigger the next + packet to be sent. */ +static void ei_tx_intr(struct device *dev) +{ + int e8390_base = dev->base_addr; + int status = inb(e8390_base + EN0_TSR); + struct ei_device *ei_local = (struct ei_device *) dev->priv; + + outb_p(ENISR_TX, e8390_base + EN0_ISR); /* Ack intr. */ + +#ifdef EI_PINGPONG + + /* + * There are two Tx buffers, see which one finished, and trigger + * the send of another one if it exists. + */ + ei_local->txqueue--; + if (ei_local->tx1 < 0) { + if (ei_local->lasttx != 1 && ei_local->lasttx != -1) + printk("%s: bogus last_tx_buffer %d, tx1=%d.\n", + ei_local->name, ei_local->lasttx, ei_local->tx1); + ei_local->tx1 = 0; + dev->tbusy = 0; + if (ei_local->tx2 > 0) { + ei_local->txing = 1; + NS8390_trigger_send(dev, ei_local->tx2, ei_local->tx_start_page + 6); + dev->trans_start = jiffies; + ei_local->tx2 = -1, + ei_local->lasttx = 2; + } else + ei_local->lasttx = 20, ei_local->txing = 0; + } else if (ei_local->tx2 < 0) { + if (ei_local->lasttx != 2 && ei_local->lasttx != -2) + printk("%s: bogus last_tx_buffer %d, tx2=%d.\n", + ei_local->name, ei_local->lasttx, ei_local->tx2); + ei_local->tx2 = 0; + dev->tbusy = 0; + if (ei_local->tx1 > 0) { + ei_local->txing = 1; + NS8390_trigger_send(dev, ei_local->tx1, ei_local->tx_start_page); + dev->trans_start = jiffies; + ei_local->tx1 = -1; + ei_local->lasttx = 1; + } else + ei_local->lasttx = 10, ei_local->txing = 0; + } else + printk("%s: unexpected TX-done interrupt, lasttx=%d.\n", + dev->name, ei_local->lasttx); + +#else /* EI_PINGPONG */ + /* + * Single Tx buffer: mark it free so another packet can be loaded. + */ + ei_local->txing = 0; + dev->tbusy = 0; +#endif + + /* Minimize Tx latency: update the statistics after we restart TXing. */ + if (status & ENTSR_COL) + ei_local->stat.collisions++; + if (status & ENTSR_PTX) + ei_local->stat.tx_packets++; + else { + ei_local->stat.tx_errors++; + if (status & ENTSR_ABT) ei_local->stat.tx_aborted_errors++; + if (status & ENTSR_CRS) ei_local->stat.tx_carrier_errors++; + if (status & ENTSR_FU) ei_local->stat.tx_fifo_errors++; + if (status & ENTSR_CDH) ei_local->stat.tx_heartbeat_errors++; + if (status & ENTSR_OWC) ei_local->stat.tx_window_errors++; + } + + mark_bh (NET_BH); +} + +/* We have a good packet(s), get it/them out of the buffers. */ + +static void ei_receive(struct device *dev) +{ + int e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + unsigned char rxing_page, this_frame, next_frame; + unsigned short current_offset; + int rx_pkt_count = 0; + struct e8390_pkt_hdr rx_frame; + int num_rx_pages = ei_local->stop_page-ei_local->rx_start_page; + + while (++rx_pkt_count < 10) { + int pkt_len; + + /* Get the rx page (incoming packet pointer). */ + outb_p(E8390_NODMA+E8390_PAGE1, e8390_base + E8390_CMD); + rxing_page = inb_p(e8390_base + EN1_CURPAG); + outb_p(E8390_NODMA+E8390_PAGE0, e8390_base + E8390_CMD); + + /* Remove one frame from the ring. Boundary is always a page behind. */ + this_frame = inb_p(e8390_base + EN0_BOUNDARY) + 1; + if (this_frame >= ei_local->stop_page) + this_frame = ei_local->rx_start_page; + + /* Someday we'll omit the previous, iff we never get this message. + (There is at least one clone claimed to have a problem.) */ + if (ei_debug > 0 && this_frame != ei_local->current_page) + printk("%s: mismatched read page pointers %2x vs %2x.\n", + dev->name, this_frame, ei_local->current_page); + + if (this_frame == rxing_page) /* Read all the frames? */ + break; /* Done for now */ + + current_offset = this_frame << 8; + ei_get_8390_hdr(dev, &rx_frame, this_frame); + + pkt_len = rx_frame.count - sizeof(struct e8390_pkt_hdr); + + next_frame = this_frame + 1 + ((pkt_len+4)>>8); + + /* Check for bogosity warned by 3c503 book: the status byte is never + written. This happened a lot during testing! This code should be + cleaned up someday. */ + if (rx_frame.next != next_frame + && rx_frame.next != next_frame + 1 + && rx_frame.next != next_frame - num_rx_pages + && rx_frame.next != next_frame + 1 - num_rx_pages) { + ei_local->current_page = rxing_page; + outb(ei_local->current_page-1, e8390_base+EN0_BOUNDARY); + ei_local->stat.rx_errors++; + continue; + } + + if (pkt_len < 60 || pkt_len > 1518) { + if (ei_debug) + printk("%s: bogus packet size: %d, status=%#2x nxpg=%#2x.\n", + dev->name, rx_frame.count, rx_frame.status, + rx_frame.next); + ei_local->stat.rx_errors++; + } else if ((rx_frame.status & 0x0F) == ENRSR_RXOK) { + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+2); + if (skb == NULL) { + if (ei_debug > 1) + printk("%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, pkt_len); + ei_local->stat.rx_dropped++; + break; + } else { + skb_reserve(skb,2); /* IP headers on 16 byte boundaries */ + skb->dev = dev; + skb_put(skb, pkt_len); /* Make room */ + ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame)); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + ei_local->stat.rx_packets++; + } + } else { + int errs = rx_frame.status; + if (ei_debug) + printk("%s: bogus packet: status=%#2x nxpg=%#2x size=%d\n", + dev->name, rx_frame.status, rx_frame.next, + rx_frame.count); + if (errs & ENRSR_FO) + ei_local->stat.rx_fifo_errors++; + } + next_frame = rx_frame.next; + + /* This _should_ never happen: it's here for avoiding bad clones. */ + if (next_frame >= ei_local->stop_page) { + printk("%s: next frame inconsistency, %#2x\n", dev->name, + next_frame); + next_frame = ei_local->rx_start_page; + } + ei_local->current_page = next_frame; + outb_p(next_frame-1, e8390_base+EN0_BOUNDARY); + } + + /* We used to also ack ENISR_OVER here, but that would sometimes mask + a real overrun, leaving the 8390 in a stopped state with rec'vr off. */ + outb_p(ENISR_RX+ENISR_RX_ERR, e8390_base+EN0_ISR); + return; +} + +/* + * We have a receiver overrun: we have to kick the 8390 to get it started + * again. Problem is that you have to kick it exactly as NS prescribes in + * the updated datasheets, or "the NIC may act in an unpredictable manner." + * This includes causing "the NIC to defer indefinitely when it is stopped + * on a busy network." Ugh. + */ +static void ei_rx_overrun(struct device *dev) +{ + int e8390_base = dev->base_addr; + unsigned long wait_start_time; + unsigned char was_txing, must_resend = 0; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + + /* + * Record whether a Tx was in progress and then issue the + * stop command. + */ + was_txing = inb_p(e8390_base+E8390_CMD) & E8390_TRANS; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD); + + if (ei_debug > 1) + printk("%s: Receiver overrun.\n", dev->name); + ei_local->stat.rx_over_errors++; + + /* + * Wait a full Tx time (1.2ms) + some guard time, NS says 1.6ms total. + * Early datasheets said to poll the reset bit, but now they say that + * it "is not a reliable indicator and subsequently should be ignored." + * We wait at least 10ms. + */ + wait_start_time = jiffies; + while (jiffies - wait_start_time <= 1*HZ/100) + barrier(); + + /* + * Reset RBCR[01] back to zero as per magic incantation. + */ + outb_p(0x00, e8390_base+EN0_RCNTLO); + outb_p(0x00, e8390_base+EN0_RCNTHI); + + /* + * See if any Tx was interrupted or not. According to NS, this + * step is vital, and skipping it will cause no end of havoc. + */ + if (was_txing) { + unsigned char tx_completed = inb_p(e8390_base+EN0_ISR) & (ENISR_TX+ENISR_TX_ERR); + if (!tx_completed) must_resend = 1; + } + + /* + * Have to enter loopback mode and then restart the NIC before + * you are allowed to slurp packets up off the ring. + */ + outb_p(E8390_TXOFF, e8390_base + EN0_TXCR); + outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START, e8390_base + E8390_CMD); + + /* + * Clear the Rx ring of all the debris, and ack the interrupt. + */ + ei_receive(dev); + outb_p(ENISR_OVER, e8390_base+EN0_ISR); + + /* + * Leave loopback mode, and resend any packet that got stopped. + */ + outb_p(E8390_TXCONFIG, e8390_base + EN0_TXCR); + if (must_resend) + outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START + E8390_TRANS, e8390_base + E8390_CMD); + +} + +static struct enet_statistics *get_stats(struct device *dev) +{ + short ioaddr = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + + /* If the card is stopped, just return the present stats. */ + if (dev->start == 0) return &ei_local->stat; + + /* Read the counter registers, assuming we are in page 0. */ + ei_local->stat.rx_frame_errors += inb_p(ioaddr + EN0_COUNTER0); + ei_local->stat.rx_crc_errors += inb_p(ioaddr + EN0_COUNTER1); + ei_local->stat.rx_missed_errors+= inb_p(ioaddr + EN0_COUNTER2); + + return &ei_local->stat; +} + +/* + * Set or clear the multicast filter for this adaptor. + */ + +static void set_multicast_list(struct device *dev) +{ + short ioaddr = dev->base_addr; + + if(dev->flags&IFF_PROMISC) + { + outb_p(E8390_RXCONFIG | 0x18, ioaddr + EN0_RXCR); + } + else if((dev->flags&IFF_ALLMULTI)||dev->mc_list) + { + /* The multicast-accept list is initialized to accept-all, and we + rely on higher-level filtering for now. */ + outb_p(E8390_RXCONFIG | 0x08, ioaddr + EN0_RXCR); + } + else + outb_p(E8390_RXCONFIG, ioaddr + EN0_RXCR); +} + +/* Initialize the rest of the 8390 device structure. */ +int ethdev_init(struct device *dev) +{ + if (ei_debug > 1) + printk(version); + + if (dev->priv == NULL) { + struct ei_device *ei_local; + + dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + memset(dev->priv, 0, sizeof(struct ei_device)); + ei_local = (struct ei_device *)dev->priv; + } + + dev->hard_start_xmit = &ei_start_xmit; + dev->get_stats = get_stats; + dev->set_multicast_list = &set_multicast_list; + + ether_setup(dev); + + return 0; +} + + +/* This page of functions should be 8390 generic */ +/* Follow National Semi's recommendations for initializing the "NIC". */ +void NS8390_init(struct device *dev, int startp) +{ + int e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + int i; + int endcfg = ei_local->word16 ? (0x48 | ENDCFG_WTS) : 0x48; + unsigned long flags; + + /* Follow National Semi's recommendations for initing the DP83902. */ + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base); /* 0x21 */ + outb_p(endcfg, e8390_base + EN0_DCFG); /* 0x48 or 0x49 */ + /* Clear the remote byte count registers. */ + outb_p(0x00, e8390_base + EN0_RCNTLO); + outb_p(0x00, e8390_base + EN0_RCNTHI); + /* Set to monitor and loopback mode -- this is vital!. */ + outb_p(E8390_RXOFF, e8390_base + EN0_RXCR); /* 0x20 */ + outb_p(E8390_TXOFF, e8390_base + EN0_TXCR); /* 0x02 */ + /* Set the transmit page and receive ring. */ + outb_p(ei_local->tx_start_page, e8390_base + EN0_TPSR); + ei_local->tx1 = ei_local->tx2 = 0; + outb_p(ei_local->rx_start_page, e8390_base + EN0_STARTPG); + outb_p(ei_local->stop_page-1, e8390_base + EN0_BOUNDARY); /* 3c503 says 0x3f,NS0x26*/ + ei_local->current_page = ei_local->rx_start_page; /* assert boundary+1 */ + outb_p(ei_local->stop_page, e8390_base + EN0_STOPPG); + /* Clear the pending interrupts and mask. */ + outb_p(0xFF, e8390_base + EN0_ISR); + outb_p(0x00, e8390_base + EN0_IMR); + + /* Copy the station address into the DS8390 registers, + and set the multicast hash bitmap to receive all multicasts. */ + save_flags(flags); + cli(); + outb_p(E8390_NODMA + E8390_PAGE1 + E8390_STOP, e8390_base); /* 0x61 */ + for(i = 0; i < 6; i++) { + outb_p(dev->dev_addr[i], e8390_base + EN1_PHYS + i); + } + /* Initialize the multicast list to accept-all. If we enable multicast + the higher levels can do the filtering. */ + for(i = 0; i < 8; i++) + outb_p(0xff, e8390_base + EN1_MULT + i); + + outb_p(ei_local->rx_start_page, e8390_base + EN1_CURPAG); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base); + restore_flags(flags); + dev->tbusy = 0; + dev->interrupt = 0; + ei_local->tx1 = ei_local->tx2 = 0; + ei_local->txing = 0; + if (startp) { + outb_p(0xff, e8390_base + EN0_ISR); + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, e8390_base); + outb_p(E8390_TXCONFIG, e8390_base + EN0_TXCR); /* xmit on. */ + /* 3c503 TechMan says rxconfig only after the NIC is started. */ + outb_p(E8390_RXCONFIG, e8390_base + EN0_RXCR); /* rx on, */ + dev->set_multicast_list(dev); /* Get the multicast status right if this + was a reset. */ + } + return; +} + +/* Trigger a transmit start, assuming the length is valid. */ +static void NS8390_trigger_send(struct device *dev, unsigned int length, + int start_page) +{ + int e8390_base = dev->base_addr; + + outb_p(E8390_NODMA+E8390_PAGE0, e8390_base); + + if (inb_p(e8390_base) & E8390_TRANS) { + printk("%s: trigger_send() called with the transmitter busy.\n", + dev->name); + return; + } + outb_p(length & 0xff, e8390_base + EN0_TCNTLO); + outb_p(length >> 8, e8390_base + EN0_TCNTHI); + outb_p(start_page, e8390_base + EN0_TPSR); + outb_p(E8390_NODMA+E8390_TRANS+E8390_START, e8390_base); + return; +} + +#ifdef MODULE + +int init_module(void) +{ + return 0; +} + +void +cleanup_module(void) +{ +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c 8390.c" + * version-control: t + * kept-new-versions: 5 + * c-indent-level: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/8390.h b/linux/src/drivers/net/8390.h new file mode 100644 index 00000000..9cc0ddc5 --- /dev/null +++ b/linux/src/drivers/net/8390.h @@ -0,0 +1,175 @@ +/* Generic NS8390 register definitions. */ +/* This file is part of Donald Becker's 8390 drivers, and is distributed + under the same license. + Some of these names and comments originated from the Crynwr + packet drivers, which are distributed under the GPL. */ + +#ifndef _8390_h +#define _8390_h + +#include <linux/if_ether.h> +#include <linux/ioport.h> +#include <linux/skbuff.h> + +#define TX_2X_PAGES 12 +#define TX_1X_PAGES 6 + +/* Should always use two Tx slots to get back-to-back transmits. */ +#define EI_PINGPONG + +#ifdef EI_PINGPONG +#define TX_PAGES TX_2X_PAGES +#else +#define TX_PAGES TX_1X_PAGES +#endif + +#define ETHER_ADDR_LEN 6 + +/* The 8390 specific per-packet-header format. */ +struct e8390_pkt_hdr { + unsigned char status; /* status */ + unsigned char next; /* pointer to next packet. */ + unsigned short count; /* header + packet length in bytes */ +}; + +/* From 8390.c */ +extern int ei_debug; +extern struct sigaction ei_sigaction; + +extern int ethif_init(struct device *dev); +extern int ethdev_init(struct device *dev); +extern void NS8390_init(struct device *dev, int startp); +extern int ei_open(struct device *dev); +extern int ei_close(struct device *dev); +extern void ei_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +#ifndef HAVE_AUTOIRQ +/* From auto_irq.c */ +extern struct device *irq2dev_map[16]; +extern int autoirq_setup(int waittime); +extern int autoirq_report(int waittime); +#endif + +/* Most of these entries should be in 'struct device' (or most of the + things in there should be here!) */ +/* You have one of these per-board */ +struct ei_device { + const char *name; + void (*reset_8390)(struct device *); + void (*get_8390_hdr)(struct device *, struct e8390_pkt_hdr *, int); + void (*block_output)(struct device *, int, const unsigned char *, int); + void (*block_input)(struct device *, int, struct sk_buff *, int); + unsigned open:1; + unsigned word16:1; /* We have the 16-bit (vs 8-bit) version of the card. */ + unsigned txing:1; /* Transmit Active */ + unsigned irqlock:1; /* 8390's intrs disabled when '1'. */ + unsigned dmaing:1; /* Remote DMA Active */ + unsigned char tx_start_page, rx_start_page, stop_page; + unsigned char current_page; /* Read pointer in buffer */ + unsigned char interface_num; /* Net port (AUI, 10bT.) to use. */ + unsigned char txqueue; /* Tx Packet buffer queue length. */ + short tx1, tx2; /* Packet lengths for ping-pong tx. */ + short lasttx; /* Alpha version consistency check. */ + unsigned char reg0; /* Register '0' in a WD8013 */ + unsigned char reg5; /* Register '5' in a WD8013 */ + unsigned char saved_irq; /* Original dev->irq value. */ + /* The new statistics table. */ + struct enet_statistics stat; +}; + +/* The maximum number of 8390 interrupt service routines called per IRQ. */ +#define MAX_SERVICE 12 + +/* The maximum time waited (in jiffies) before assuming a Tx failed. (20ms) */ +#define TX_TIMEOUT (20*HZ/100) + +#define ei_status (*(struct ei_device *)(dev->priv)) + +/* Some generic ethernet register configurations. */ +#define E8390_TX_IRQ_MASK 0xa /* For register EN0_ISR */ +#define E8390_RX_IRQ_MASK 0x5 +#define E8390_RXCONFIG 0x4 /* EN0_RXCR: broadcasts, no multicast,errors */ +#define E8390_RXOFF 0x20 /* EN0_RXCR: Accept no packets */ +#define E8390_TXCONFIG 0x00 /* EN0_TXCR: Normal transmit mode */ +#define E8390_TXOFF 0x02 /* EN0_TXCR: Transmitter off */ + +/* Register accessed at EN_CMD, the 8390 base addr. */ +#define E8390_STOP 0x01 /* Stop and reset the chip */ +#define E8390_START 0x02 /* Start the chip, clear reset */ +#define E8390_TRANS 0x04 /* Transmit a frame */ +#define E8390_RREAD 0x08 /* Remote read */ +#define E8390_RWRITE 0x10 /* Remote write */ +#define E8390_NODMA 0x20 /* Remote DMA */ +#define E8390_PAGE0 0x00 /* Select page chip registers */ +#define E8390_PAGE1 0x40 /* using the two high-order bits */ +#define E8390_PAGE2 0x80 /* Page 3 is invalid. */ + +#define E8390_CMD 0x00 /* The command register (for all pages) */ +/* Page 0 register offsets. */ +#define EN0_CLDALO 0x01 /* Low byte of current local dma addr RD */ +#define EN0_STARTPG 0x01 /* Starting page of ring bfr WR */ +#define EN0_CLDAHI 0x02 /* High byte of current local dma addr RD */ +#define EN0_STOPPG 0x02 /* Ending page +1 of ring bfr WR */ +#define EN0_BOUNDARY 0x03 /* Boundary page of ring bfr RD WR */ +#define EN0_TSR 0x04 /* Transmit status reg RD */ +#define EN0_TPSR 0x04 /* Transmit starting page WR */ +#define EN0_NCR 0x05 /* Number of collision reg RD */ +#define EN0_TCNTLO 0x05 /* Low byte of tx byte count WR */ +#define EN0_FIFO 0x06 /* FIFO RD */ +#define EN0_TCNTHI 0x06 /* High byte of tx byte count WR */ +#define EN0_ISR 0x07 /* Interrupt status reg RD WR */ +#define EN0_CRDALO 0x08 /* low byte of current remote dma address RD */ +#define EN0_RSARLO 0x08 /* Remote start address reg 0 */ +#define EN0_CRDAHI 0x09 /* high byte, current remote dma address RD */ +#define EN0_RSARHI 0x09 /* Remote start address reg 1 */ +#define EN0_RCNTLO 0x0a /* Remote byte count reg WR */ +#define EN0_RCNTHI 0x0b /* Remote byte count reg WR */ +#define EN0_RSR 0x0c /* rx status reg RD */ +#define EN0_RXCR 0x0c /* RX configuration reg WR */ +#define EN0_TXCR 0x0d /* TX configuration reg WR */ +#define EN0_COUNTER0 0x0d /* Rcv alignment error counter RD */ +#define EN0_DCFG 0x0e /* Data configuration reg WR */ +#define EN0_COUNTER1 0x0e /* Rcv CRC error counter RD */ +#define EN0_IMR 0x0f /* Interrupt mask reg WR */ +#define EN0_COUNTER2 0x0f /* Rcv missed frame error counter RD */ + +/* Bits in EN0_ISR - Interrupt status register */ +#define ENISR_RX 0x01 /* Receiver, no error */ +#define ENISR_TX 0x02 /* Transmitter, no error */ +#define ENISR_RX_ERR 0x04 /* Receiver, with error */ +#define ENISR_TX_ERR 0x08 /* Transmitter, with error */ +#define ENISR_OVER 0x10 /* Receiver overwrote the ring */ +#define ENISR_COUNTERS 0x20 /* Counters need emptying */ +#define ENISR_RDC 0x40 /* remote dma complete */ +#define ENISR_RESET 0x80 /* Reset completed */ +#define ENISR_ALL 0x3f /* Interrupts we will enable */ + +/* Bits in EN0_DCFG - Data config register */ +#define ENDCFG_WTS 0x01 /* word transfer mode selection */ + +/* Page 1 register offsets. */ +#define EN1_PHYS 0x01 /* This board's physical enet addr RD WR */ +#define EN1_CURPAG 0x07 /* Current memory page RD WR */ +#define EN1_MULT 0x08 /* Multicast filter mask array (8 bytes) RD WR */ + +/* Bits in received packet status byte and EN0_RSR*/ +#define ENRSR_RXOK 0x01 /* Received a good packet */ +#define ENRSR_CRC 0x02 /* CRC error */ +#define ENRSR_FAE 0x04 /* frame alignment error */ +#define ENRSR_FO 0x08 /* FIFO overrun */ +#define ENRSR_MPA 0x10 /* missed pkt */ +#define ENRSR_PHY 0x20 /* physical/multicase address */ +#define ENRSR_DIS 0x40 /* receiver disable. set in monitor mode */ +#define ENRSR_DEF 0x80 /* deferring */ + +/* Transmitted packet status, EN0_TSR. */ +#define ENTSR_PTX 0x01 /* Packet transmitted without error */ +#define ENTSR_ND 0x02 /* The transmit wasn't deferred. */ +#define ENTSR_COL 0x04 /* The transmit collided at least once. */ +#define ENTSR_ABT 0x08 /* The transmit collided 16 times, and was deferred. */ +#define ENTSR_CRS 0x10 /* The carrier sense was lost. */ +#define ENTSR_FU 0x20 /* A "FIFO underrun" occurred during transmit. */ +#define ENTSR_CDH 0x40 /* The collision detect "heartbeat" signal was lost. */ +#define ENTSR_OWC 0x80 /* There was an out-of-window collision. */ + +#endif /* _8390_h */ diff --git a/linux/src/drivers/net/Space.c b/linux/src/drivers/net/Space.c new file mode 100644 index 00000000..083cdeb7 --- /dev/null +++ b/linux/src/drivers/net/Space.c @@ -0,0 +1,541 @@ +/* + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * Holds initial configuration information for devices. + * + * NOTE: This file is a nice idea, but its current format does not work + * well for drivers that support multiple units, like the SLIP + * driver. We should actually have only one pointer to a driver + * here, with the driver knowing how many units it supports. + * Currently, the SLIP driver abuses the "base_addr" integer + * field of the 'device' structure to store the unit number... + * -FvK + * + * Version: @(#)Space.c 1.0.8 07/31/96 + * + * Authors: Ross Biro, <bir7@leland.Stanford.Edu> + * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> + * Donald J. Becker, <becker@super.org> + * + * FIXME: + * Sort the device chain fastest first. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <linux/config.h> +#include <linux/netdevice.h> +#include <linux/errno.h> + +#define NEXT_DEV NULL + + +/* A unified ethernet device probe. This is the easiest way to have every + ethernet adaptor have the name "eth[0123...]". + */ + +extern int tulip_probe(struct device *dev); +extern int hp100_probe(struct device *dev); +extern int ultra_probe(struct device *dev); +extern int ultra32_probe(struct device *dev); +extern int wd_probe(struct device *dev); +extern int el2_probe(struct device *dev); +extern int ne_probe(struct device *dev); +extern int ne2k_pci_probe(struct device *dev); +extern int hp_probe(struct device *dev); +extern int hp_plus_probe(struct device *dev); +extern int znet_probe(struct device *); +extern int express_probe(struct device *); +extern int eepro_probe(struct device *); +extern int el3_probe(struct device *); +extern int at1500_probe(struct device *); +extern int at1700_probe(struct device *); +extern int fmv18x_probe(struct device *); +extern int eth16i_probe(struct device *); +extern int depca_probe(struct device *); +extern int apricot_probe(struct device *); +extern int ewrk3_probe(struct device *); +extern int de4x5_probe(struct device *); +extern int el1_probe(struct device *); +extern int via_rhine_probe(struct device *); +#if defined(CONFIG_WAVELAN) +extern int wavelan_probe(struct device *); +#endif /* defined(CONFIG_WAVELAN) */ +extern int el16_probe(struct device *); +extern int elplus_probe(struct device *); +extern int ac3200_probe(struct device *); +extern int e2100_probe(struct device *); +extern int ni52_probe(struct device *); +extern int ni65_probe(struct device *); +extern int SK_init(struct device *); +extern int seeq8005_probe(struct device *); +extern int tc59x_probe(struct device *); +extern int dgrs_probe(struct device *); +extern int smc_init( struct device * ); +extern int sparc_lance_probe(struct device *); +extern int atarilance_probe(struct device *); +extern int a2065_probe(struct device *); +extern int ariadne_probe(struct device *); +extern int hydra_probe(struct device *); +extern int yellowfin_probe(struct device *); +extern int eepro100_probe(struct device *); +extern int epic100_probe(struct device *); +extern int rtl8139_probe(struct device *); +extern int tlan_probe(struct device *); +extern int isa515_probe(struct device *); +extern int pcnet32_probe(struct device *); +extern int lance_probe(struct device *); +/* Detachable devices ("pocket adaptors") */ +extern int atp_init(struct device *); +extern int de600_probe(struct device *); +extern int de620_probe(struct device *); +/* The shaper hook */ +extern int shaper_probe(struct device *); +/* Red Creek PCI hook */ +extern int rcpci_probe(struct device *); + +static int +ethif_probe(struct device *dev) +{ + u_long base_addr = dev->base_addr; + + if ((base_addr == 0xffe0) || (base_addr == 1)) + return 1; /* ENXIO */ + + if (1 + /* All PCI probes are safe, and thus should be first. */ +#ifdef CONFIG_DE4X5 /* DEC DE425, DE434, DE435 adapters */ + && de4x5_probe(dev) +#endif +#ifdef CONFIG_DGRS + && dgrs_probe(dev) +#endif +#ifdef CONFIG_EEXPRESS_PRO100B /* Intel EtherExpress Pro100B */ + && eepro100_probe(dev) +#endif +#ifdef CONFIG_EPIC + && epic100_probe(dev) +#endif +#if defined(CONFIG_HP100) + && hp100_probe(dev) +#endif +#if defined(CONFIG_NE2K_PCI) + && ne2k_pci_probe(dev) +#endif +#ifdef CONFIG_PCNET32 + && pcnet32_probe(dev) +#endif +#ifdef CONFIG_RTL8139 + && rtl8139_probe(dev) +#endif +#if defined(CONFIG_VIA_RHINE) + && via_rhine_probe(dev) +#endif +#if defined(CONFIG_VORTEX) + && tc59x_probe(dev) +#endif +#if defined(CONFIG_DEC_ELCP) + && tulip_probe(dev) +#endif +#ifdef CONFIG_YELLOWFIN + && yellowfin_probe(dev) +#endif + /* Next mostly-safe EISA-only drivers. */ +#ifdef CONFIG_AC3200 /* Ansel Communications EISA 3200. */ + && ac3200_probe(dev) +#endif +#if defined(CONFIG_ULTRA32) + && ultra32_probe(dev) +#endif + /* Third, sensitive ISA boards. */ +#ifdef CONFIG_AT1700 + && at1700_probe(dev) +#endif +#if defined(CONFIG_ULTRA) + && ultra_probe(dev) +#endif +#if defined(CONFIG_SMC9194) + && smc_init(dev) +#endif +#if defined(CONFIG_WD80x3) + && wd_probe(dev) +#endif +#if defined(CONFIG_EL2) /* 3c503 */ + && el2_probe(dev) +#endif +#if defined(CONFIG_HPLAN) + && hp_probe(dev) +#endif +#if defined(CONFIG_HPLAN_PLUS) + && hp_plus_probe(dev) +#endif +#if defined(CONFIG_SEEQ8005) + && seeq8005_probe(dev) +#endif +#ifdef CONFIG_E2100 /* Cabletron E21xx series. */ + && e2100_probe(dev) +#endif +#if defined(CONFIG_NE2000) + && ne_probe(dev) +#endif +#ifdef CONFIG_AT1500 + && at1500_probe(dev) +#endif +#ifdef CONFIG_FMV18X /* Fujitsu FMV-181/182 */ + && fmv18x_probe(dev) +#endif +#ifdef CONFIG_ETH16I + && eth16i_probe(dev) /* ICL EtherTeam 16i/32 */ +#endif +#ifdef CONFIG_EL3 /* 3c509 */ + && el3_probe(dev) +#endif +#ifdef CONFIG_3C515 /* 3c515 */ + && tc515_probe(dev) +#endif +#ifdef CONFIG_ZNET /* Zenith Z-Note and some IBM Thinkpads. */ + && znet_probe(dev) +#endif +#ifdef CONFIG_EEXPRESS /* Intel EtherExpress */ + && express_probe(dev) +#endif +#ifdef CONFIG_EEXPRESS_PRO /* Intel EtherExpress Pro/10 */ + && eepro_probe(dev) +#endif +#ifdef CONFIG_DEPCA /* DEC DEPCA */ + && depca_probe(dev) +#endif +#ifdef CONFIG_EWRK3 /* DEC EtherWORKS 3 */ + && ewrk3_probe(dev) +#endif +#ifdef CONFIG_APRICOT /* Apricot I82596 */ + && apricot_probe(dev) +#endif +#ifdef CONFIG_EL1 /* 3c501 */ + && el1_probe(dev) +#endif +#if defined(CONFIG_WAVELAN) /* WaveLAN */ + && wavelan_probe(dev) +#endif /* defined(CONFIG_WAVELAN) */ +#ifdef CONFIG_EL16 /* 3c507 */ + && el16_probe(dev) +#endif +#ifdef CONFIG_ELPLUS /* 3c505 */ + && elplus_probe(dev) +#endif +#ifdef CONFIG_DE600 /* D-Link DE-600 adapter */ + && de600_probe(dev) +#endif +#ifdef CONFIG_DE620 /* D-Link DE-620 adapter */ + && de620_probe(dev) +#endif +#if defined(CONFIG_SK_G16) + && SK_init(dev) +#endif +#ifdef CONFIG_NI52 + && ni52_probe(dev) +#endif +#ifdef CONFIG_NI65 + && ni65_probe(dev) +#endif +#ifdef CONFIG_LANCE /* ISA LANCE boards */ + && lance_probe(dev) +#endif +#ifdef CONFIG_ATARILANCE /* Lance-based Atari ethernet boards */ + && atarilance_probe(dev) +#endif +#ifdef CONFIG_A2065 /* Commodore/Ameristar A2065 Ethernet Board */ + && a2065_probe(dev) +#endif +#ifdef CONFIG_ARIADNE /* Village Tronic Ariadne Ethernet Board */ + && ariadne_probe(dev) +#endif +#ifdef CONFIG_HYDRA /* Hydra Systems Amiganet Ethernet board */ + && hydra_probe(dev) +#endif +#ifdef CONFIG_SUNLANCE + && sparc_lance_probe(dev) +#endif +#ifdef CONFIG_TLAN + && tlan_probe(dev) +#endif +#ifdef CONFIG_LANCE + && lance_probe(dev) +#endif + && 1 ) { + return 1; /* -ENODEV or -EAGAIN would be more accurate. */ + } + return 0; +} + +#ifdef CONFIG_SDLA + extern int sdla_init(struct device *); + static struct device sdla0_dev = { "sdla0", 0, 0, 0, 0, 0, 0, 0, 0, 0, NEXT_DEV, sdla_init, }; + +# undef NEXT_DEV +# define NEXT_DEV (&sdla0_dev) +#endif + +/* Run-time ATtachable (Pocket) devices have a different (not "eth#") name. */ +#ifdef CONFIG_ATP /* AT-LAN-TEC (RealTek) pocket adaptor. */ +static struct device atp_dev = { + "atp0", 0, 0, 0, 0, 0, 0, 0, 0, 0, NEXT_DEV, atp_init, /* ... */ }; +# undef NEXT_DEV +# define NEXT_DEV (&atp_dev) +#endif + +#ifdef CONFIG_ARCNET + extern int arcnet_probe(struct device *dev); + static struct device arcnet_dev = { + "arc0", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, arcnet_probe, }; +# undef NEXT_DEV +# define NEXT_DEV (&arcnet_dev) +#endif + +/* The first device defaults to I/O base '0', which means autoprobe. */ +#ifndef ETH0_ADDR +# define ETH0_ADDR 0 +#endif +#ifndef ETH0_IRQ +# define ETH0_IRQ 0 +#endif +/* "eth0" defaults to autoprobe (== 0), other use a base of 0xffe0 (== -0x20), + which means "don't probe". These entries exist to only to provide empty + slots which may be enabled at boot-time. */ + +static struct device eth7_dev = { + "eth7", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, NEXT_DEV, ethif_probe }; +static struct device eth6_dev = { + "eth6", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð7_dev, ethif_probe }; +static struct device eth5_dev = { + "eth5", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð6_dev, ethif_probe }; +static struct device eth4_dev = { + "eth4", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð5_dev, ethif_probe }; +static struct device eth3_dev = { + "eth3", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð4_dev, ethif_probe }; +static struct device eth2_dev = { + "eth2", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð3_dev, ethif_probe }; +static struct device eth1_dev = { + "eth1", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð2_dev, ethif_probe }; + +static struct device eth0_dev = { + "eth0", 0, 0, 0, 0, ETH0_ADDR, ETH0_IRQ, 0, 0, 0, ð1_dev, ethif_probe }; + +# undef NEXT_DEV +# define NEXT_DEV (ð0_dev) + +#if defined(PLIP) || defined(CONFIG_PLIP) + extern int plip_init(struct device *); + static struct device plip2_dev = { + "plip2", 0, 0, 0, 0, 0x278, 2, 0, 0, 0, NEXT_DEV, plip_init, }; + static struct device plip1_dev = { + "plip1", 0, 0, 0, 0, 0x378, 7, 0, 0, 0, &plip2_dev, plip_init, }; + static struct device plip0_dev = { + "plip0", 0, 0, 0, 0, 0x3BC, 5, 0, 0, 0, &plip1_dev, plip_init, }; +# undef NEXT_DEV +# define NEXT_DEV (&plip0_dev) +#endif /* PLIP */ + +#if defined(SLIP) || defined(CONFIG_SLIP) + /* To be exact, this node just hooks the initialization + routines to the device structures. */ +extern int slip_init_ctrl_dev(struct device *); +static struct device slip_bootstrap = { + "slip_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, slip_init_ctrl_dev, }; +#undef NEXT_DEV +#define NEXT_DEV (&slip_bootstrap) +#endif /* SLIP */ + +#if defined(CONFIG_MKISS) + /* To be exact, this node just hooks the initialization + routines to the device structures. */ +extern int mkiss_init_ctrl_dev(struct device *); +static struct device mkiss_bootstrap = { + "mkiss_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, mkiss_init_ctrl_dev, }; +#undef NEXT_DEV +#define NEXT_DEV (&mkiss_bootstrap) +#endif /* MKISS */ + +#if defined(CONFIG_STRIP) +extern int strip_init_ctrl_dev(struct device *); +static struct device strip_bootstrap = { + "strip_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, strip_init_ctrl_dev, }; +#undef NEXT_DEV +#define NEXT_DEV (&strip_bootstrap) +#endif /* STRIP */ + +#if defined(CONFIG_SHAPER) +static struct device shaper_bootstrap = { + "shaper", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, shaper_probe, }; +#undef NEXT_DEV +#define NEXT_DEV (&shaper_bootstrap) +#endif /* SHAPER */ + +#if defined(CONFIG_RCPCI) +static struct device rcpci_bootstrap = { + "rcpci", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, rcpci_probe, }; +#undef NEXT_DEV +#define NEXT_DEV (&rcpci_bootstrap) +#endif /* RCPCI */ + +#if defined(CONFIG_PPP) +extern int ppp_init(struct device *); +static struct device ppp_bootstrap = { + "ppp_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, ppp_init, }; +#undef NEXT_DEV +#define NEXT_DEV (&ppp_bootstrap) +#endif /* PPP */ + +#ifdef CONFIG_DUMMY + extern int dummy_init(struct device *dev); + static struct device dummy_dev = { + "dummy", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, dummy_init, }; +# undef NEXT_DEV +# define NEXT_DEV (&dummy_dev) +#endif + +#ifdef CONFIG_EQUALIZER +extern int eql_init(struct device *dev); +struct device eql_dev = { + "eql", /* Master device for IP traffic load + balancing */ + 0x0, 0x0, 0x0, 0x0, /* recv end/start; mem end/start */ + 0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + eql_init /* set up the rest */ +}; +# undef NEXT_DEV +# define NEXT_DEV (&eql_dev) +#endif + +#ifdef CONFIG_IBMTR + + extern int tok_probe(struct device *dev); + static struct device ibmtr_dev1 = { + "tr1", /* IBM Token Ring (Non-DMA) Interface */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0xa24, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + tok_probe /* ??? Token_init should set up the rest */ + }; +# undef NEXT_DEV +# define NEXT_DEV (&ibmtr_dev1) + + + static struct device ibmtr_dev0 = { + "tr0", /* IBM Token Ring (Non-DMA) Interface */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0xa20, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + tok_probe /* ??? Token_init should set up the rest */ + }; +# undef NEXT_DEV +# define NEXT_DEV (&ibmtr_dev0) + +#endif + +#ifdef CONFIG_DEFXX + extern int dfx_probe(struct device *dev); + static struct device fddi7_dev = + {"fddi7", 0, 0, 0, 0, 0, 0, 0, 0, 0, NEXT_DEV, dfx_probe}; + static struct device fddi6_dev = + {"fddi6", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi7_dev, dfx_probe}; + static struct device fddi5_dev = + {"fddi5", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi6_dev, dfx_probe}; + static struct device fddi4_dev = + {"fddi4", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi5_dev, dfx_probe}; + static struct device fddi3_dev = + {"fddi3", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi4_dev, dfx_probe}; + static struct device fddi2_dev = + {"fddi2", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi3_dev, dfx_probe}; + static struct device fddi1_dev = + {"fddi1", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi2_dev, dfx_probe}; + static struct device fddi0_dev = + {"fddi0", 0, 0, 0, 0, 0, 0, 0, 0, 0, &fddi1_dev, dfx_probe}; + +#undef NEXT_DEV +#define NEXT_DEV (&fddi0_dev) +#endif + +#ifdef CONFIG_NET_IPIP + extern int tunnel_init(struct device *); + + static struct device tunnel_dev1 = + { + "tunl1", /* IPIP tunnel */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0x0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + tunnel_init /* Fill in the details */ + }; + + static struct device tunnel_dev0 = + { + "tunl0", /* IPIP tunnel */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0x0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + &tunnel_dev1, /* next device */ + tunnel_init /* Fill in the details */ + }; +# undef NEXT_DEV +# define NEXT_DEV (&tunnel_dev0) + +#endif + +#ifdef CONFIG_APFDDI + extern int apfddi_init(struct device *dev); + static struct device fddi_dev = { + "fddi", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, apfddi_init }; +# undef NEXT_DEV +# define NEXT_DEV (&fddi_dev) +#endif + +#ifdef CONFIG_APBIF + extern int bif_init(struct device *dev); + static struct device bif_dev = { + "bif", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, bif_init }; +# undef NEXT_DEV +# define NEXT_DEV (&bif_dev) +#endif + +extern int loopback_init(struct device *dev); +struct device loopback_dev = { + "lo", /* Software Loopback interface */ + 0x0, /* recv memory end */ + 0x0, /* recv memory start */ + 0x0, /* memory end */ + 0x0, /* memory start */ + 0, /* base I/O address */ + 0, /* IRQ */ + 0, 0, 0, /* flags */ + NEXT_DEV, /* next device */ + loopback_init /* loopback_init should set up the rest */ +}; + +struct device *dev_base = &loopback_dev; diff --git a/linux/src/drivers/net/ac3200.c b/linux/src/drivers/net/ac3200.c new file mode 100644 index 00000000..0337bab7 --- /dev/null +++ b/linux/src/drivers/net/ac3200.c @@ -0,0 +1,385 @@ +/* ac3200.c: A driver for the Ansel Communications EISA ethernet adaptor. */ +/* + Written 1993, 1994 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@cesdis.gsfc.nasa.gov, or + C/O Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This is driver for the Ansel Communications Model 3200 EISA Ethernet LAN + Adapter. The programming information is from the users manual, as related + by glee@ardnassak.math.clemson.edu. + */ + +static const char *version = + "ac3200.c:v1.01 7/1/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include <asm/system.h> +#include <asm/io.h> + +#include "8390.h" + +/* Offsets from the base address. */ +#define AC_NIC_BASE 0x00 +#define AC_SA_PROM 0x16 /* The station address PROM. */ +#define AC_ADDR0 0x00 /* Prefix station address values. */ +#define AC_ADDR1 0x40 /* !!!!These are just guesses!!!! */ +#define AC_ADDR2 0x90 +#define AC_ID_PORT 0xC80 +#define AC_EISA_ID 0x0110d305 +#define AC_RESET_PORT 0xC84 +#define AC_RESET 0x00 +#define AC_ENABLE 0x01 +#define AC_CONFIG 0xC90 /* The configuration port. */ + +#define AC_IO_EXTENT 0x10 /* IS THIS REALLY TRUE ??? */ + /* Actually accessed is: + * AC_NIC_BASE (0-15) + * AC_SA_PROM (0-5) + * AC_ID_PORT (0-3) + * AC_RESET_PORT + * AC_CONFIG + */ + +/* Decoding of the configuration register. */ +static unsigned char config2irqmap[8] = {15, 12, 11, 10, 9, 7, 5, 3}; +static int addrmap[8] = +{0xFF0000, 0xFE0000, 0xFD0000, 0xFFF0000, 0xFFE0000, 0xFFC0000, 0xD0000, 0 }; +static const char *port_name[4] = { "10baseT", "invalid", "AUI", "10base2"}; + +#define config2irq(configval) config2irqmap[((configval) >> 3) & 7] +#define config2mem(configval) addrmap[(configval) & 7] +#define config2name(configval) port_name[((configval) >> 6) & 3] + +/* First and last 8390 pages. */ +#define AC_START_PG 0x00 /* First page of 8390 TX buffer */ +#define AC_STOP_PG 0x80 /* Last page +1 of the 8390 RX ring */ + +int ac3200_probe(struct device *dev); +static int ac_probe1(int ioaddr, struct device *dev); + +static int ac_open(struct device *dev); +static void ac_reset_8390(struct device *dev); +static void ac_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void ac_block_output(struct device *dev, const int count, + const unsigned char *buf, const int start_page); +static void ac_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); + +static int ac_close_card(struct device *dev); + + +/* Probe for the AC3200. + + The AC3200 can be identified by either the EISA configuration registers, + or the unique value in the station address PROM. + */ + +int ac3200_probe(struct device *dev) +{ + unsigned short ioaddr = dev->base_addr; + + if (ioaddr > 0x1ff) /* Check a single specified location. */ + return ac_probe1(ioaddr, dev); + else if (ioaddr > 0) /* Don't probe at all. */ + return ENXIO; + + /* If you have a pre 0.99pl15 machine you should delete this line. */ + if ( ! EISA_bus) + return ENXIO; + + for (ioaddr = 0x1000; ioaddr < 0x9000; ioaddr += 0x1000) { + if (check_region(ioaddr, AC_IO_EXTENT)) + continue; + if (ac_probe1(ioaddr, dev) == 0) + return 0; + } + + return ENODEV; +} + +static int ac_probe1(int ioaddr, struct device *dev) +{ + int i; + +#ifndef final_version + printk("AC3200 ethercard probe at %#3x:", ioaddr); + + for(i = 0; i < 6; i++) + printk(" %02x", inb(ioaddr + AC_SA_PROM + i)); +#endif + + /* !!!!The values of AC_ADDRn (see above) should be corrected when we + find out the correct station address prefix!!!! */ + if (inb(ioaddr + AC_SA_PROM + 0) != AC_ADDR0 + || inb(ioaddr + AC_SA_PROM + 1) != AC_ADDR1 + || inb(ioaddr + AC_SA_PROM + 2) != AC_ADDR2 ) { +#ifndef final_version + printk(" not found (invalid prefix).\n"); +#endif + return ENODEV; + } + + /* The correct probe method is to check the EISA ID. */ + for (i = 0; i < 4; i++) + if (inl(ioaddr + AC_ID_PORT) != AC_EISA_ID) { + printk("EISA ID mismatch, %8x vs %8x.\n", + inl(ioaddr + AC_ID_PORT), AC_EISA_ID); + return ENODEV; + } + + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk("ac3200.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + for(i = 0; i < ETHER_ADDR_LEN; i++) + dev->dev_addr[i] = inb(ioaddr + AC_SA_PROM + i); + +#ifndef final_version + printk("\nAC3200 ethercard configuration register is %#02x," + " EISA ID %02x %02x %02x %02x.\n", inb(ioaddr + AC_CONFIG), + inb(ioaddr + AC_ID_PORT + 0), inb(ioaddr + AC_ID_PORT + 1), + inb(ioaddr + AC_ID_PORT + 2), inb(ioaddr + AC_ID_PORT + 3)); +#endif + + /* Assign and allocate the interrupt now. */ + if (dev->irq == 0) + dev->irq = config2irq(inb(ioaddr + AC_CONFIG)); + else if (dev->irq == 2) + dev->irq = 9; + + if (request_irq(dev->irq, ei_interrupt, 0, "ac3200", NULL)) { + printk (" unable to get IRQ %d.\n", dev->irq); + return EAGAIN; + } + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk (" unable to allocate memory for dev->priv.\n"); + free_irq(dev->irq, NULL); + return -ENOMEM; + } + + request_region(ioaddr, AC_IO_EXTENT, "ac3200"); + + dev->base_addr = ioaddr; + +#ifdef notyet + if (dev->mem_start) { /* Override the value from the board. */ + for (i = 0; i < 7; i++) + if (addrmap[i] == dev->mem_start) + break; + if (i >= 7) + i = 0; + outb((inb(ioaddr + AC_CONFIG) & ~7) | i, ioaddr + AC_CONFIG); + } +#endif + + dev->if_port = inb(ioaddr + AC_CONFIG) >> 6; + dev->mem_start = config2mem(inb(ioaddr + AC_CONFIG)); + dev->rmem_start = dev->mem_start + TX_PAGES*256; + dev->mem_end = dev->rmem_end = dev->mem_start + + (AC_STOP_PG - AC_START_PG)*256; + + ei_status.name = "AC3200"; + ei_status.tx_start_page = AC_START_PG; + ei_status.rx_start_page = AC_START_PG + TX_PAGES; + ei_status.stop_page = AC_STOP_PG; + ei_status.word16 = 1; + + printk("\n%s: AC3200 at %#x, IRQ %d, %s port, shared memory %#lx-%#lx.\n", + dev->name, ioaddr, dev->irq, port_name[dev->if_port], + dev->mem_start, dev->mem_end-1); + + if (ei_debug > 0) + printk(version); + + ei_status.reset_8390 = &ac_reset_8390; + ei_status.block_input = &ac_block_input; + ei_status.block_output = &ac_block_output; + ei_status.get_8390_hdr = &ac_get_8390_hdr; + + dev->open = &ac_open; + dev->stop = &ac_close_card; + NS8390_init(dev, 0); + return 0; +} + +static int ac_open(struct device *dev) +{ +#ifdef notyet + /* Someday we may enable the IRQ and shared memory here. */ + int ioaddr = dev->base_addr; + + if (request_irq(dev->irq, ei_interrupt, 0, "ac3200", NULL)) + return -EAGAIN; +#endif + + ei_open(dev); + + MOD_INC_USE_COUNT; + + return 0; +} + +static void ac_reset_8390(struct device *dev) +{ + ushort ioaddr = dev->base_addr; + + outb(AC_RESET, ioaddr + AC_RESET_PORT); + if (ei_debug > 1) printk("resetting AC3200, t=%ld...", jiffies); + + ei_status.txing = 0; + outb(AC_ENABLE, ioaddr + AC_RESET_PORT); + if (ei_debug > 1) printk("reset done\n"); + + return; +} + +/* Grab the 8390 specific header. Similar to the block_input routine, but + we don't need to be concerned with ring wrap as the header will be at + the start of a page, so we optimize accordingly. */ + +static void +ac_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + unsigned long hdr_start = dev->mem_start + ((ring_page - AC_START_PG)<<8); + memcpy_fromio(hdr, hdr_start, sizeof(struct e8390_pkt_hdr)); +} + +/* Block input and output are easy on shared memory ethercards, the only + complication is when the ring buffer wraps. */ + +static void ac_block_input(struct device *dev, int count, struct sk_buff *skb, + int ring_offset) +{ + unsigned long xfer_start = dev->mem_start + ring_offset - (AC_START_PG<<8); + + if (xfer_start + count > dev->rmem_end) { + /* We must wrap the input move. */ + int semi_count = dev->rmem_end - xfer_start; + memcpy_fromio(skb->data, xfer_start, semi_count); + count -= semi_count; + memcpy_fromio(skb->data + semi_count, dev->rmem_start, count); + } else { + /* Packet is in one chunk -- we can copy + cksum. */ + eth_io_copy_and_sum(skb, xfer_start, count, 0); + } +} + +static void ac_block_output(struct device *dev, int count, + const unsigned char *buf, int start_page) +{ + unsigned long shmem = dev->mem_start + ((start_page - AC_START_PG)<<8); + + memcpy_toio(shmem, buf, count); +} + +static int ac_close_card(struct device *dev) +{ + dev->start = 0; + dev->tbusy = 1; + + if (ei_debug > 1) + printk("%s: Shutting down ethercard.\n", dev->name); + +#ifdef notyet + /* We should someday disable shared memory and interrupts. */ + outb(0x00, ioaddr + 6); /* Disable interrupts. */ + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = 0; +#endif + + ei_close(dev); + + MOD_DEC_USE_COUNT; + + return 0; +} + +#ifdef MODULE +#define MAX_AC32_CARDS 4 /* Max number of AC32 cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_AC32_CARDS] = { 0, }; +static struct device dev_ac32[MAX_AC32_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_AC32_CARDS] = { 0, }; +static int irq[MAX_AC32_CARDS] = { 0, }; +static int mem[MAX_AC32_CARDS] = { 0, }; + +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_AC32_CARDS; this_dev++) { + struct device *dev = &dev_ac32[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->mem_start = mem[this_dev]; /* Currently ignored by driver */ + dev->init = ac3200_probe; + /* Default is to only install one card. */ + if (io[this_dev] == 0 && this_dev != 0) break; + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "ac3200.c: No ac3200 card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_AC32_CARDS; this_dev++) { + struct device *dev = &dev_ac32[this_dev]; + if (dev->priv != NULL) { + kfree(dev->priv); + dev->priv = NULL; + /* Someday free_irq + irq2dev may be in ac_close_card() */ + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = NULL; + release_region(dev->base_addr, AC_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c ac3200.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/apricot.c b/linux/src/drivers/net/apricot.c new file mode 100644 index 00000000..12db88ba --- /dev/null +++ b/linux/src/drivers/net/apricot.c @@ -0,0 +1,1046 @@ +/* apricot.c: An Apricot 82596 ethernet driver for linux. */ +/* + Apricot + Written 1994 by Mark Evans. + This driver is for the Apricot 82596 bus-master interface + + Modularised 12/94 Mark Evans + + Driver skeleton + 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 + + +*/ + +static const char *version = "apricot.c:v0.2 05/12/94\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#ifndef HAVE_PORTRESERVE +#define check_region(addr, size) 0 +#define request_region(addr, size,name) do ; while(0) +#endif + +#ifndef HAVE_ALLOC_SKB +#define alloc_skb(size, priority) (struct sk_buff *) kmalloc(size,priority) +#define kfree_skbmem(buff, size) kfree_s(buff,size) +#endif + +#define APRICOT_DEBUG 1 + +#ifdef APRICOT_DEBUG +int i596_debug = APRICOT_DEBUG; +#else +int i596_debug = 1; +#endif + +#define APRICOT_TOTAL_SIZE 17 + +#define I596_NULL -1 + +#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. */ + +#define CMD_FLEX 0x0008 /* Enable flexible memory model */ + +enum commands { + CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMulticastList = 3, + CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7}; + +#define STAT_C 0x8000 /* Set to 0 after execution */ +#define STAT_B 0x4000 /* Command being executed */ +#define STAT_OK 0x2000 /* Command executed ok */ +#define STAT_A 0x1000 /* Command aborted */ + +#define CUC_START 0x0100 +#define CUC_RESUME 0x0200 +#define CUC_SUSPEND 0x0300 +#define CUC_ABORT 0x0400 +#define RX_START 0x0010 +#define RX_RESUME 0x0020 +#define RX_SUSPEND 0x0030 +#define RX_ABORT 0x0040 + +struct i596_cmd { + unsigned short status; + unsigned short command; + struct i596_cmd *next; +}; + +#define EOF 0x8000 +#define SIZE_MASK 0x3fff + +struct i596_tbd { + unsigned short size; + unsigned short pad; + struct i596_tbd *next; + char *data; +}; + +struct tx_cmd { + struct i596_cmd cmd; + struct i596_tbd *tbd; + unsigned short size; + unsigned short pad; +}; + +struct i596_rfd { + unsigned short stat; + unsigned short cmd; + struct i596_rfd *next; + long rbd; + unsigned short count; + unsigned short size; + char data[1532]; +}; + +#define RX_RING_SIZE 8 + +struct i596_scb { + unsigned short status; + unsigned short command; + struct i596_cmd *cmd; + struct i596_rfd *rfd; + unsigned long crc_err; + unsigned long align_err; + unsigned long resource_err; + unsigned long over_err; + unsigned long rcvdt_err; + unsigned long short_err; + unsigned short t_on; + unsigned short t_off; +}; + +struct i596_iscp { + unsigned long stat; + struct i596_scb *scb; +}; + +struct i596_scp { + unsigned long sysbus; + unsigned long pad; + struct i596_iscp *iscp; +}; + +struct i596_private { + volatile struct i596_scp scp; + volatile struct i596_iscp iscp; + volatile struct i596_scb scb; + volatile struct i596_cmd set_add; + char eth_addr[8]; + volatile struct i596_cmd set_conf; + char i596_config[16]; + volatile struct i596_cmd tdr; + unsigned long stat; + int last_restart; + struct i596_rfd *rx_tail; + struct i596_cmd *cmd_tail; + struct i596_cmd *cmd_head; + int cmd_backlog; + unsigned long last_cmd; + struct enet_statistics stats; +}; + +char init_setup[] = { + 0x8E, /* length, prefetch on */ + 0xC8, /* fifo to 8, monitor off */ + 0x80, /* don't save bad frames */ + 0x2E, /* No source address insertion, 8 byte preamble */ + 0x00, /* priority and backoff defaults */ + 0x60, /* interframe spacing */ + 0x00, /* slot time LSB */ + 0xf2, /* slot time and retries */ + 0x00, /* promiscuous mode */ + 0x00, /* collision detect */ + 0x40, /* minimum frame length */ + 0xff, + 0x00, + 0x7f /* *multi IA */ }; + +static int i596_open(struct device *dev); +static int i596_start_xmit(struct sk_buff *skb, struct device *dev); +static void i596_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int i596_close(struct device *dev); +static struct enet_statistics *i596_get_stats(struct device *dev); +static void i596_add_cmd(struct device *dev, struct i596_cmd *cmd); +static void print_eth(char *); +static void set_multicast_list(struct device *dev); + + +static inline int +init_rx_bufs(struct device *dev, int num) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + int i; + struct i596_rfd *rfd; + + lp->scb.rfd = (struct i596_rfd *)I596_NULL; + + if (i596_debug > 1) printk ("%s: init_rx_bufs %d.\n", dev->name, num); + + for (i = 0; i < num; i++) + { + if (!(rfd = (struct i596_rfd *)kmalloc(sizeof(struct i596_rfd), GFP_KERNEL))) + break; + + rfd->stat = 0x0000; + rfd->rbd = I596_NULL; + rfd->count = 0; + rfd->size = 1532; + if (i == 0) + { + rfd->cmd = CMD_EOL; + lp->rx_tail = rfd; + } + else + rfd->cmd = 0x0000; + + rfd->next = lp->scb.rfd; + lp->scb.rfd = rfd; + } + + if (i != 0) + lp->rx_tail->next = lp->scb.rfd; + + return (i); +} + +static inline void +remove_rx_bufs(struct device *dev) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + struct i596_rfd *rfd = lp->scb.rfd; + + lp->rx_tail->next = (struct i596_rfd *)I596_NULL; + + do + { + lp->scb.rfd = rfd->next; + kfree_s(rfd, sizeof(struct i596_rfd)); + rfd = lp->scb.rfd; + } + while (rfd != lp->rx_tail); +} + +static inline void +init_i596_mem(struct device *dev) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + short ioaddr = dev->base_addr; + int boguscnt = 100; + + /* change the scp address */ + outw(0, ioaddr); + outw(0, ioaddr); + outb(4, ioaddr+0xf); + outw(((((int)&lp->scp) & 0xffff) | 2), ioaddr); + outw((((int)&lp->scp)>>16) & 0xffff, ioaddr); + + lp->last_cmd = jiffies; + + lp->scp.sysbus = 0x00440000; + lp->scp.iscp = &(lp->iscp); + lp->iscp.scb = &(lp->scb); + lp->iscp.stat = 0x0001; + lp->cmd_backlog = 0; + + lp->cmd_head = lp->scb.cmd = (struct i596_cmd *) I596_NULL; + + if (i596_debug > 2) printk("%s: starting i82596.\n", dev->name); + + (void) inb (ioaddr+0x10); + outb(4, ioaddr+0xf); + outw(0, ioaddr+4); + + while (lp->iscp.stat) + if (--boguscnt == 0) + { + printk("%s: i82596 initialization timed out with status %4.4x, cmd %4.4x.\n", + dev->name, lp->scb.status, lp->scb.command); + break; + } + + lp->scb.command = 0; + + memcpy (lp->i596_config, init_setup, 14); + lp->set_conf.command = CmdConfigure; + i596_add_cmd(dev, &lp->set_conf); + + memcpy (lp->eth_addr, dev->dev_addr, 6); + lp->set_add.command = CmdSASetup; + i596_add_cmd(dev, &lp->set_add); + + lp->tdr.command = CmdTDR; + i596_add_cmd(dev, &lp->tdr); + + boguscnt = 200; + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("%s: receive unit start timed out with status %4.4x, cmd %4.4x.\n", + dev->name, lp->scb.status, lp->scb.command); + break; + } + + lp->scb.command = RX_START; + outw(0, ioaddr+4); + + boguscnt = 200; + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("i82596 init timed out with status %4.4x, cmd %4.4x.\n", + lp->scb.status, lp->scb.command); + break; + } + + return; +} + +static inline int +i596_rx(struct device *dev) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + int frames = 0; + + if (i596_debug > 3) printk ("i596_rx()\n"); + + while ((lp->scb.rfd->stat) & STAT_C) + { + if (i596_debug >2) print_eth(lp->scb.rfd->data); + + if ((lp->scb.rfd->stat) & STAT_OK) + { + /* a good frame */ + int pkt_len = lp->scb.rfd->count & 0x3fff; + struct sk_buff *skb = dev_alloc_skb(pkt_len); + + frames++; + + if (skb == NULL) + { + printk ("%s: i596_rx Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + break; + } + + skb->dev = dev; + memcpy(skb_put(skb,pkt_len), lp->scb.rfd->data, pkt_len); + + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + + if (i596_debug > 4) print_eth(skb->data); + } + else + { + lp->stats.rx_errors++; + if ((lp->scb.rfd->stat) & 0x0001) lp->stats.collisions++; + if ((lp->scb.rfd->stat) & 0x0080) lp->stats.rx_length_errors++; + if ((lp->scb.rfd->stat) & 0x0100) lp->stats.rx_over_errors++; + if ((lp->scb.rfd->stat) & 0x0200) lp->stats.rx_fifo_errors++; + if ((lp->scb.rfd->stat) & 0x0400) lp->stats.rx_frame_errors++; + if ((lp->scb.rfd->stat) & 0x0800) lp->stats.rx_crc_errors++; + if ((lp->scb.rfd->stat) & 0x1000) lp->stats.rx_length_errors++; + } + + lp->scb.rfd->stat = 0; + lp->rx_tail->cmd = 0; + lp->rx_tail = lp->scb.rfd; + lp->scb.rfd = lp->scb.rfd->next; + lp->rx_tail->count = 0; + lp->rx_tail->cmd = CMD_EOL; + + } + + if (i596_debug > 3) printk ("frames %d\n", frames); + + return 0; +} + +static inline void +i596_cleanup_cmd(struct i596_private *lp) +{ + struct i596_cmd *ptr; + int boguscnt = 100; + + if (i596_debug > 4) printk ("i596_cleanup_cmd\n"); + + while (lp->cmd_head != (struct i596_cmd *) I596_NULL) + { + ptr = lp->cmd_head; + + lp->cmd_head = lp->cmd_head->next; + lp->cmd_backlog--; + + switch ((ptr->command) & 0x7) + { + case CmdTx: + { + struct tx_cmd *tx_cmd = (struct tx_cmd *) ptr; + struct sk_buff *skb = ((struct sk_buff *)(tx_cmd->tbd->data)) -1; + + dev_kfree_skb(skb, FREE_WRITE); + + lp->stats.tx_errors++; + lp->stats.tx_aborted_errors++; + + ptr->next = (struct i596_cmd * ) I596_NULL; + kfree_s((unsigned char *)tx_cmd, (sizeof (struct tx_cmd) + sizeof (struct i596_tbd))); + break; + } + case CmdMulticastList: + { + unsigned short count = *((unsigned short *) (ptr + 1)); + + ptr->next = (struct i596_cmd * ) I596_NULL; + kfree_s((unsigned char *)ptr, (sizeof (struct i596_cmd) + count + 2)); + break; + } + default: + ptr->next = (struct i596_cmd * ) I596_NULL; + } + } + + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("i596_cleanup_cmd timed out with status %4.4x, cmd %4.4x.\n", + lp->scb.status, lp->scb.command); + break; + } + + lp->scb.cmd = lp->cmd_head; +} + +static inline void +i596_reset(struct device *dev, struct i596_private *lp, int ioaddr) +{ + int boguscnt = 100; + + if (i596_debug > 4) printk ("i596_reset\n"); + + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("i596_reset timed out with status %4.4x, cmd %4.4x.\n", + lp->scb.status, lp->scb.command); + break; + } + + dev->start = 0; + dev->tbusy = 1; + + lp->scb.command = CUC_ABORT|RX_ABORT; + outw(0, ioaddr+4); + + /* wait for shutdown */ + boguscnt = 400; + + while ((lp->scb.status, lp->scb.command) || lp->scb.command) + if (--boguscnt == 0) + { + printk("i596_reset 2 timed out with status %4.4x, cmd %4.4x.\n", + lp->scb.status, lp->scb.command); + break; + } + + i596_cleanup_cmd(lp); + i596_rx(dev); + + dev->start = 1; + dev->tbusy = 0; + dev->interrupt = 0; + init_i596_mem(dev); +} + +static void i596_add_cmd(struct device *dev, struct i596_cmd *cmd) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + int ioaddr = dev->base_addr; + unsigned long flags; + int boguscnt = 100; + + if (i596_debug > 4) printk ("i596_add_cmd\n"); + + cmd->status = 0; + cmd->command |= (CMD_EOL|CMD_INTR); + cmd->next = (struct i596_cmd *) I596_NULL; + + save_flags(flags); + cli(); + if (lp->cmd_head != (struct i596_cmd *) I596_NULL) + lp->cmd_tail->next = cmd; + else + { + lp->cmd_head = cmd; + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("i596_add_cmd timed out with status %4.4x, cmd %4.4x.\n", + lp->scb.status, lp->scb.command); + break; + } + + lp->scb.cmd = cmd; + lp->scb.command = CUC_START; + outw (0, ioaddr+4); + } + lp->cmd_tail = cmd; + lp->cmd_backlog++; + + lp->cmd_head = lp->scb.cmd; + restore_flags(flags); + + if (lp->cmd_backlog > 16) + { + int tickssofar = jiffies - lp->last_cmd; + + if (tickssofar < 25) return; + + printk("%s: command unit timed out, status resetting.\n", dev->name); + + i596_reset(dev, lp, ioaddr); + } +} + +static int +i596_open(struct device *dev) +{ + int i; + + if (i596_debug > 1) + printk("%s: i596_open() irq %d.\n", dev->name, dev->irq); + + if (request_irq(dev->irq, &i596_interrupt, 0, "apricot", NULL)) + return -EAGAIN; + + irq2dev_map[dev->irq] = dev; + + i = init_rx_bufs(dev, RX_RING_SIZE); + + if ((i = init_rx_bufs(dev, RX_RING_SIZE)) < RX_RING_SIZE) + printk("%s: only able to allocate %d receive buffers\n", dev->name, i); + + if (i < 4) + { + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = 0; + return -EAGAIN; + } + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + MOD_INC_USE_COUNT; + + /* Initialize the 82596 memory */ + init_i596_mem(dev); + + return 0; /* Always succeed */ +} + +static int +i596_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + int ioaddr = dev->base_addr; + struct tx_cmd *tx_cmd; + + if (i596_debug > 2) printk ("%s: Apricot start xmit\n", dev->name); + + /* Transmitter timeout, serious problems. */ + if (dev->tbusy) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 5) + return 1; + printk("%s: transmit timed out, status resetting.\n", + dev->name); + lp->stats.tx_errors++; + /* Try to restart the adaptor */ + if (lp->last_restart == lp->stats.tx_packets) { + if (i596_debug > 1) printk ("Resetting board.\n"); + + /* Shutdown and restart */ + i596_reset(dev,lp, ioaddr); + } else { + /* Issue a channel attention signal */ + if (i596_debug > 1) printk ("Kicking board.\n"); + + lp->scb.command = CUC_START|RX_START; + outw(0, ioaddr+4); + + lp->last_restart = lp->stats.tx_packets; + } + dev->tbusy = 0; + dev->trans_start = jiffies; + } + + /* If some higher level thinks we've misses a tx-done interrupt + we are passed NULL. n.b. dev_tint handles the cli()/sti() + itself. */ + if (skb == NULL) { + dev_tint(dev); + return 0; + } + + /* shouldn't happen */ + if (skb->len <= 0) return 0; + + if (i596_debug > 3) printk("%s: i596_start_xmit() called\n", dev->name); + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + 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; + dev->trans_start = jiffies; + + tx_cmd = (struct tx_cmd *) kmalloc ((sizeof (struct tx_cmd) + sizeof (struct i596_tbd)), GFP_ATOMIC); + if (tx_cmd == NULL) + { + printk ("%s: i596_xmit Memory squeeze, dropping packet.\n", dev->name); + lp->stats.tx_dropped++; + + dev_kfree_skb(skb, FREE_WRITE); + } + else + { + tx_cmd->tbd = (struct i596_tbd *) (tx_cmd + 1); + tx_cmd->tbd->next = (struct i596_tbd *) I596_NULL; + + tx_cmd->cmd.command = CMD_FLEX|CmdTx; + + tx_cmd->pad = 0; + tx_cmd->size = 0; + tx_cmd->tbd->pad = 0; + tx_cmd->tbd->size = EOF | length; + + tx_cmd->tbd->data = skb->data; + + if (i596_debug > 3) print_eth(skb->data); + + i596_add_cmd(dev, (struct i596_cmd *)tx_cmd); + + lp->stats.tx_packets++; + } + } + + dev->tbusy = 0; + + return 0; +} + + +static void print_eth(char *add) +{ + int i; + + printk ("Dest "); + for (i = 0; i < 6; i++) + printk(" %2.2X", (unsigned char)add[i]); + printk ("\n"); + + printk ("Source"); + for (i = 0; i < 6; i++) + printk(" %2.2X", (unsigned char)add[i+6]); + printk ("\n"); + printk ("type %2.2X%2.2X\n", (unsigned char)add[12], (unsigned char)add[13]); +} + +int apricot_probe(struct device *dev) +{ + int i; + struct i596_private *lp; + int checksum = 0; + int ioaddr = 0x300; + char eth_addr[6]; + + /* this is easy the ethernet interface can only be at 0x300 */ + /* first check nothing is already registered here */ + + if (check_region(ioaddr, APRICOT_TOTAL_SIZE)) + return ENODEV; + + for (i = 0; i < 8; i++) + { + eth_addr[i] = inb(ioaddr+8+i); + checksum += eth_addr[i]; + } + + /* checksum is a multiple of 0x100, got this wrong first time + some machines have 0x100, some 0x200. The DOS driver doesn't + even bother with the checksum */ + + if (checksum % 0x100) return ENODEV; + + /* Some other boards trip the checksum.. but then appear as ether + address 0. Trap these - AC */ + + if(memcmp(eth_addr,"\x00\x00\x49",3)!= 0) + return ENODEV; + + request_region(ioaddr, APRICOT_TOTAL_SIZE, "apricot"); + + dev->base_addr = ioaddr; + ether_setup(dev); + printk("%s: Apricot 82596 at %#3x,", dev->name, ioaddr); + + for (i = 0; i < 6; i++) + printk(" %2.2X", dev->dev_addr[i] = eth_addr[i]); + + dev->base_addr = ioaddr; + dev->irq = 10; + printk(" IRQ %d.\n", dev->irq); + + if (i596_debug > 0) printk(version); + + /* The APRICOT-specific entries in the device structure. */ + dev->open = &i596_open; + dev->stop = &i596_close; + dev->hard_start_xmit = &i596_start_xmit; + dev->get_stats = &i596_get_stats; + dev->set_multicast_list = &set_multicast_list; + + dev->mem_start = (int)kmalloc(sizeof(struct i596_private)+ 0x0f, GFP_KERNEL); + /* align for scp */ + dev->priv = (void *)((dev->mem_start + 0xf) & 0xfffffff0); + + lp = (struct i596_private *)dev->priv; + memset((void *)lp, 0, sizeof(struct i596_private)); + lp->scb.command = 0; + lp->scb.cmd = (struct i596_cmd *) I596_NULL; + lp->scb.rfd = (struct i596_rfd *)I596_NULL; + + return 0; +} + +static void +i596_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct i596_private *lp; + short ioaddr; + int boguscnt = 200; + unsigned short status, ack_cmd = 0; + + if (dev == NULL) { + printk ("i596_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + if (i596_debug > 3) printk ("%s: i596_interrupt(): irq %d\n",dev->name, irq); + + if (dev->interrupt) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + + dev->interrupt = 1; + + ioaddr = dev->base_addr; + + lp = (struct i596_private *)dev->priv; + + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("%s: i596 interrupt, timeout status %4.4x command %4.4x.\n", dev->name, lp->scb.status, lp->scb.command); + break; + } + status = lp->scb.status; + + if (i596_debug > 4) + printk("%s: i596 interrupt, status %4.4x.\n", dev->name, status); + + ack_cmd = status & 0xf000; + + if ((status & 0x8000) || (status & 0x2000)) + { + struct i596_cmd *ptr; + + if ((i596_debug > 4) && (status & 0x8000)) + printk("%s: i596 interrupt completed command.\n", dev->name); + if ((i596_debug > 4) && (status & 0x2000)) + printk("%s: i596 interrupt command unit inactive %x.\n", dev->name, status & 0x0700); + + while ((lp->cmd_head != (struct i596_cmd *) I596_NULL) && (lp->cmd_head->status & STAT_C)) + { + ptr = lp->cmd_head; + + lp->cmd_head = lp->cmd_head->next; + lp->cmd_backlog--; + + switch ((ptr->command) & 0x7) + { + case CmdTx: + { + struct tx_cmd *tx_cmd = (struct tx_cmd *) ptr; + struct sk_buff *skb = ((struct sk_buff *)(tx_cmd->tbd->data)) -1; + + dev_kfree_skb(skb, FREE_WRITE); + + if ((ptr->status) & STAT_OK) + { + if (i596_debug >2) print_eth(skb->data); + } + else + { + lp->stats.tx_errors++; + if ((ptr->status) & 0x0020) lp->stats.collisions++; + if (!((ptr->status) & 0x0040)) lp->stats.tx_heartbeat_errors++; + if ((ptr->status) & 0x0400) lp->stats.tx_carrier_errors++; + if ((ptr->status) & 0x0800) lp->stats.collisions++; + if ((ptr->status) & 0x1000) lp->stats.tx_aborted_errors++; + } + + + ptr->next = (struct i596_cmd * ) I596_NULL; + kfree_s((unsigned char *)tx_cmd, (sizeof (struct tx_cmd) + sizeof (struct i596_tbd))); + break; + } + case CmdMulticastList: + { + unsigned short count = *((unsigned short *) (ptr + 1)); + + ptr->next = (struct i596_cmd * ) I596_NULL; + kfree_s((unsigned char *)ptr, (sizeof (struct i596_cmd) + count + 2)); + break; + } + case CmdTDR: + { + unsigned long status = *((unsigned long *) (ptr + 1)); + + if (status & 0x8000) + { + if (i596_debug > 3) + printk("%s: link ok.\n", dev->name); + } + else + { + if (status & 0x4000) + printk("%s: Transceiver problem.\n", dev->name); + if (status & 0x2000) + printk("%s: Termination problem.\n", dev->name); + if (status & 0x1000) + printk("%s: Short circuit.\n", dev->name); + + printk("%s: Time %ld.\n", dev->name, status & 0x07ff); + } + } + default: + ptr->next = (struct i596_cmd * ) I596_NULL; + + lp->last_cmd = jiffies; + } + } + + ptr = lp->cmd_head; + while ((ptr != (struct i596_cmd *) I596_NULL) && (ptr != lp->cmd_tail)) + { + ptr->command &= 0x1fff; + ptr = ptr->next; + } + + if ((lp->cmd_head != (struct i596_cmd *) I596_NULL) && (dev->start)) ack_cmd |= CUC_START; + lp->scb.cmd = lp->cmd_head; + } + + if ((status & 0x1000) || (status & 0x4000)) + { + if ((i596_debug > 4) && (status & 0x4000)) + printk("%s: i596 interrupt received a frame.\n", dev->name); + if ((i596_debug > 4) && (status & 0x1000)) + printk("%s: i596 interrupt receive unit inactive %x.\n", dev->name, status & 0x0070); + + i596_rx(dev); + + if (dev->start) ack_cmd |= RX_START; + } + + /* acknowledge the interrupt */ + +/* + if ((lp->scb.cmd != (struct i596_cmd *) I596_NULL) && (dev->start)) ack_cmd | = CUC_START; +*/ + boguscnt = 100; + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("%s: i596 interrupt, timeout status %4.4x command %4.4x.\n", dev->name, lp->scb.status, lp->scb.command); + break; + } + lp->scb.command = ack_cmd; + + (void) inb (ioaddr+0x10); + outb (4, ioaddr+0xf); + outw (0, ioaddr+4); + + if (i596_debug > 4) + printk("%s: exiting interrupt.\n", dev->name); + + dev->interrupt = 0; + return; +} + +static int +i596_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct i596_private *lp = (struct i596_private *)dev->priv; + int boguscnt = 200; + + dev->start = 0; + dev->tbusy = 1; + + if (i596_debug > 1) + printk("%s: Shutting down ethercard, status was %4.4x.\n", + dev->name, lp->scb.status); + + lp->scb.command = CUC_ABORT|RX_ABORT; + outw(0, ioaddr+4); + + i596_cleanup_cmd(lp); + + while (lp->scb.status, lp->scb.command) + if (--boguscnt == 0) + { + printk("%s: close timed out with status %4.4x, cmd %4.4x.\n", + dev->name, lp->scb.status, lp->scb.command); + break; + } + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = 0; + remove_rx_bufs(dev); + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics * +i596_get_stats(struct device *dev) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + + return &lp->stats; +} + +/* + * Set or clear the multicast filter for this adaptor. + */ + +static void set_multicast_list(struct device *dev) +{ + struct i596_private *lp = (struct i596_private *)dev->priv; + struct i596_cmd *cmd; + + if (i596_debug > 1) + printk ("%s: set multicast list %d\n", dev->name, dev->mc_count); + + if (dev->mc_count > 0) + { + struct dev_mc_list *dmi; + char *cp; + cmd = (struct i596_cmd *) kmalloc(sizeof(struct i596_cmd)+2+dev->mc_count*6, GFP_ATOMIC); + if (cmd == NULL) + { + printk ("%s: set_multicast Memory squeeze.\n", dev->name); + return; + } + cmd->command = CmdMulticastList; + *((unsigned short *) (cmd + 1)) = dev->mc_count * 6; + cp=((char *)(cmd + 1))+2; + for(dmi=dev->mc_list;dmi!=NULL;dmi=dmi->next) + { + memcpy(cp, dmi,6); + cp+=6; + } + print_eth (((char *)(cmd + 1)) + 2); + i596_add_cmd(dev, cmd); + } + else + { + if (lp->set_conf.next != (struct i596_cmd * ) I596_NULL) + return; + if (dev->mc_count == 0 && !(dev->flags&(IFF_PROMISC|IFF_ALLMULTI))) + { + if(dev->flags&IFF_ALLMULTI) + dev->flags|=IFF_PROMISC; + lp->i596_config[8] &= ~0x01; + } + else + lp->i596_config[8] |= 0x01; + + i596_add_cmd(dev, &lp->set_conf); + } +} + +#ifdef HAVE_DEVLIST +static unsigned int apricot_portlist[] = {0x300, 0}; +struct netdev_entry apricot_drv = +{"apricot", apricot_probe, APRICOT_TOTAL_SIZE, apricot_portlist}; +#endif + +#ifdef MODULE +static char devicename[9] = { 0, }; +static struct device dev_apricot = { + devicename, /* device name inserted by /linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0x300, 10, + 0, 0, 0, NULL, apricot_probe }; + +static int io = 0x300; +static int irq = 10; + +int +init_module(void) +{ + dev_apricot.base_addr = io; + dev_apricot.irq = irq; + if (register_netdev(&dev_apricot) != 0) + return -EIO; + return 0; +} + +void +cleanup_module(void) +{ + unregister_netdev(&dev_apricot); + kfree_s((void *)dev_apricot.mem_start, sizeof(struct i596_private) + 0xf); + dev_apricot.priv = NULL; + + /* If we don't do this, we can't re-insmod it later. */ + release_region(dev_apricot.base_addr, APRICOT_TOTAL_SIZE); +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c apricot.c" + * End: + */ diff --git a/linux/src/drivers/net/at1700.c b/linux/src/drivers/net/at1700.c new file mode 100644 index 00000000..953c1889 --- /dev/null +++ b/linux/src/drivers/net/at1700.c @@ -0,0 +1,754 @@ +/* at1700.c: A network device driver for the Allied Telesis AT1700. + + Written 1993-98 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This is a device driver for the Allied Telesis AT1700, which is a + straight-forward Fujitsu MB86965 implementation. + + Sources: + The Fujitsu MB86965 datasheet. + + After the initial version of this driver was written Gerry Sawkins of + ATI provided their EEPROM configuration code header file. + Thanks to NIIBE Yutaka <gniibe@mri.co.jp> for bug fixes. + + Bugs: + The MB86965 has a design flaw that makes all probes unreliable. Not + only is it difficult to detect, it also moves around in I/O space in + response to inb()s from other device probes! +*/ + +static const char *version = + "at1700.c:v1.15 4/7/98 Donald Becker (becker@cesdis.gsfc.nasa.gov)\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/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* Tunable parameters. */ + +/* When to switch from the 64-entry multicast filter to Rx-all-multicast. */ +#define MC_FILTERBREAK 64 + +/* These unusual address orders are used to verify the CONFIG register. */ +static int at1700_probe_list[] = +{0x260, 0x280, 0x2a0, 0x240, 0x340, 0x320, 0x380, 0x300, 0}; +static int fmv18x_probe_list[] = +{0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x300, 0x340, 0}; + + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 1 +#endif +static unsigned int net_debug = NET_DEBUG; + +typedef unsigned char uchar; + +/* Information that need to be kept for each board. */ +struct net_local { + struct enet_statistics stats; + unsigned char mc_filter[8]; + uint jumpered:1; /* Set iff the board has jumper config. */ + uint tx_started:1; /* Packets are on the Tx queue. */ + uint invalid_irq:1; + uchar tx_queue; /* Number of packet on the Tx queue. */ + ushort tx_queue_len; /* Current length of the Tx queue. */ +}; + + +/* Offsets from the base address. */ +#define STATUS 0 +#define TX_STATUS 0 +#define RX_STATUS 1 +#define TX_INTR 2 /* Bit-mapped interrupt enable registers. */ +#define RX_INTR 3 +#define TX_MODE 4 +#define RX_MODE 5 +#define CONFIG_0 6 /* Misc. configuration settings. */ +#define CONFIG_1 7 +/* Run-time register bank 2 definitions. */ +#define DATAPORT 8 /* Word-wide DMA or programmed-I/O dataport. */ +#define TX_START 10 +#define MODE13 13 +/* Configuration registers only on the '865A/B chips. */ +#define EEPROM_Ctrl 16 +#define EEPROM_Data 17 +#define IOCONFIG 18 /* Either read the jumper, or move the I/O. */ +#define IOCONFIG1 19 +#define SAPROM 20 /* The station address PROM, if no EEPROM. */ +#define RESET 31 /* Write to reset some parts of the chip. */ +#define AT1700_IO_EXTENT 32 +/* Index to functions, as function prototypes. */ + +extern int at1700_probe(struct device *dev); + +static int at1700_probe1(struct device *dev, int ioaddr); +static int read_eeprom(int ioaddr, int location); +static int net_open(struct device *dev); +static int net_send_packet(struct sk_buff *skb, struct device *dev); +static void net_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void net_rx(struct device *dev); +static int net_close(struct device *dev); +static struct enet_statistics *net_get_stats(struct device *dev); +static void set_rx_mode(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, allocate space for the device and return success + (detachable devices only). + */ +#ifdef HAVE_DEVLIST +/* Support for a alternate probe manager, which will eliminate the + boilerplate below. */ +struct netdev_entry at1700_drv = +{"at1700", at1700_probe1, AT1700_IO_EXTENT, at1700_probe_list}; +#else +int +at1700_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return at1700_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; at1700_probe_list[i]; i++) { + int ioaddr = at1700_probe_list[i]; + if (check_region(ioaddr, AT1700_IO_EXTENT)) + continue; + if (at1700_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +/* The Fujitsu datasheet suggests that the NIC be probed for by checking its + "signature", the default bit pattern after a reset. This *doesn't* work -- + there is no way to reset the bus interface without a complete power-cycle! + + It turns out that ATI came to the same conclusion I did: the only thing + that can be done is checking a few bits and then diving right into an + EEPROM read. */ + +int at1700_probe1(struct device *dev, int ioaddr) +{ + char fmv_irqmap[4] = {3, 7, 10, 15}; + char at1700_irqmap[8] = {3, 4, 5, 9, 10, 11, 14, 15}; + unsigned int i, irq, is_fmv18x = 0, is_at1700 = 0; + + /* Resetting the chip doesn't reset the ISA interface, so don't bother. + That means we have to be careful with the register values we probe for. + */ +#ifdef notdef + printk("at1700 probe at %#x, eeprom is %4.4x %4.4x %4.4x ctrl %4.4x.\n", + ioaddr, read_eeprom(ioaddr, 4), read_eeprom(ioaddr, 5), + read_eeprom(ioaddr, 6), inw(ioaddr + EEPROM_Ctrl)); +#endif + /* We must check for the EEPROM-config boards first, else accessing + IOCONFIG0 will move the board! */ + if (at1700_probe_list[inb(ioaddr + IOCONFIG1) & 0x07] == ioaddr + && read_eeprom(ioaddr, 4) == 0x0000 + && (read_eeprom(ioaddr, 5) & 0xff00) == 0xF400) + is_at1700 = 1; + else if (fmv18x_probe_list[inb(ioaddr + IOCONFIG) & 0x07] == ioaddr + && inb(ioaddr + SAPROM ) == 0x00 + && inb(ioaddr + SAPROM + 1) == 0x00 + && inb(ioaddr + SAPROM + 2) == 0x0e) + is_fmv18x = 1; + else + return -ENODEV; + + /* Reset the internal state machines. */ + outb(0, ioaddr + RESET); + + /* Allocate a new 'dev' if needed. */ + if (dev == NULL) + dev = init_etherdev(0, sizeof(struct net_local)); + + if (is_at1700) + irq = at1700_irqmap[(read_eeprom(ioaddr, 12)&0x04) + | (read_eeprom(ioaddr, 0)>>14)]; + else + irq = fmv_irqmap[(inb(ioaddr + IOCONFIG)>>6) & 0x03]; + + /* Grab the region so that we can find another board if the IRQ request + fails. */ + request_region(ioaddr, AT1700_IO_EXTENT, dev->name); + + printk("%s: AT1700 found at %#3x, IRQ %d, address ", dev->name, + ioaddr, irq); + + dev->base_addr = ioaddr; + dev->irq = irq; + + for(i = 0; i < 3; i++) { + unsigned short eeprom_val = read_eeprom(ioaddr, 4+i); + printk("%04x", eeprom_val); + ((unsigned short *)dev->dev_addr)[i] = ntohs(eeprom_val); + } + + /* The EEPROM word 12 bit 0x0400 means use regular 100 ohm 10baseT signals, + rather than 150 ohm shielded twisted pair compensation. + 0x0000 == auto-sense the interface + 0x0800 == use TP interface + 0x1800 == use coax interface + */ + { + const char *porttype[] = {"auto-sense", "10baseT", "auto-sense", "10base2"}; + ushort setup_value = read_eeprom(ioaddr, 12); + + dev->if_port = setup_value >> 8; + printk(" %s interface.\n", porttype[(dev->if_port>>3) & 3]); + } + + /* Set the station address in bank zero. */ + outb(0xe0, ioaddr + CONFIG_1); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + 8 + i); + + /* Switch to bank 1 and set the multicast table to accept none. */ + outb(0xe4, ioaddr + CONFIG_1); + for (i = 0; i < 8; i++) + outb(0x00, ioaddr + 8 + i); + + /* Set the configuration register 0 to 32K 100ns. byte-wide memory, 16 bit + bus access, two 4K Tx queues, and disabled Tx and Rx. */ + outb(0xda, ioaddr + CONFIG_0); + + /* Switch to bank 2 and lock our I/O address. */ + outb(0xe8, ioaddr + CONFIG_1); + outb(dev->if_port, MODE13); + + /* Power-down the chip. Aren't we green! */ + outb(0x00, ioaddr + CONFIG_1); + + if (net_debug) + printk(version); + + /* 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)); + + dev->open = net_open; + dev->stop = net_close; + dev->hard_start_xmit = net_send_packet; + dev->get_stats = net_get_stats; + dev->set_multicast_list = &set_rx_mode; + + /* Fill in the fields of 'dev' with ethernet-generic values. */ + ether_setup(dev); + + { + struct net_local *lp = (struct net_local *)dev->priv; + lp->jumpered = is_fmv18x; + /* Snarf the interrupt vector now. */ + if (request_irq(irq, &net_interrupt, 0, dev->name, dev)) { + printk (" AT1700 at %#3x is unusable due to a conflict on" + "IRQ %d.\n", ioaddr, irq); + lp->invalid_irq = 1; + return 0; + } + } + + return 0; +} + + +/* EEPROM_Ctrl bits. */ +#define EE_SHIFT_CLK 0x40 /* EEPROM shift clock, in reg. 16. */ +#define EE_CS 0x20 /* EEPROM chip select, in reg. 16. */ +#define EE_DATA_WRITE 0x80 /* EEPROM chip data in, in reg. 17. */ +#define EE_DATA_READ 0x80 /* EEPROM chip data out, in reg. 17. */ + +/* Delay between EEPROM clock transitions. */ +#define eeprom_delay() do {} while (0); + +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD (5 << 6) +#define EE_READ_CMD (6 << 6) +#define EE_ERASE_CMD (7 << 6) + +static int read_eeprom(int ioaddr, int location) +{ + int i; + unsigned short retval = 0; + int ee_addr = ioaddr + EEPROM_Ctrl; + int ee_daddr = ioaddr + EEPROM_Data; + int read_cmd = location | EE_READ_CMD; + + /* Shift the read command bits out. */ + for (i = 9; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0; + outb(EE_CS, ee_addr); + outb(dataval, ee_daddr); + eeprom_delay(); + outb(EE_CS | EE_SHIFT_CLK, ee_addr); /* EEPROM clock tick. */ + eeprom_delay(); + } + outb(EE_DATA_WRITE, ee_daddr); + for (i = 16; i > 0; i--) { + outb(EE_CS, ee_addr); + eeprom_delay(); + outb(EE_CS | EE_SHIFT_CLK, ee_addr); + eeprom_delay(); + retval = (retval << 1) | ((inb(ee_daddr) & EE_DATA_READ) ? 1 : 0); + } + + /* Terminate the EEPROM access. */ + outb(EE_CS, ee_addr); + eeprom_delay(); + outb(EE_SHIFT_CLK, ee_addr); + outb(0, ee_addr); + return retval; +} + + + +static int net_open(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + /* Powerup the chip, initialize config register 1, and select bank 0. */ + outb(0xe0, ioaddr + CONFIG_1); + + /* Set the station address in bank zero. */ + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + 8 + i); + + /* Switch to bank 1 and set the multicast table to accept none. */ + outb(0xe4, ioaddr + CONFIG_1); + for (i = 0; i < 8; i++) + outb(0x00, ioaddr + 8 + i); + + /* Set the configuration register 0 to 32K 100ns. byte-wide memory, 16 bit + bus access, and two 4K Tx queues. */ + outb(0xda, ioaddr + CONFIG_0); + + /* Switch to register bank 2, enable the Rx and Tx. */ + outw(0xe85a, ioaddr + CONFIG_0); + + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + + /* Turn on Rx interrupts, leave Tx interrupts off until packet Tx. */ + outb(0x00, ioaddr + TX_INTR); + outb(0x81, ioaddr + RX_INTR); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + MOD_INC_USE_COUNT; + + return 0; +} + +static int +net_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + + 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 < 10) + return 1; + printk("%s: transmit timed out with status %04x, %s?\n", dev->name, + inw(ioaddr + STATUS), inb(ioaddr + TX_STATUS) & 0x80 + ? "IRQ conflict" : "network cable problem"); + printk("%s: timeout registers: %04x %04x %04x %04x %04x %04x %04x %04x.\n", + dev->name, inw(ioaddr + 0), inw(ioaddr + 2), inw(ioaddr + 4), + inw(ioaddr + 6), inw(ioaddr + 8), inw(ioaddr + 10), + inw(ioaddr + 12), inw(ioaddr + 14)); + lp->stats.tx_errors++; + /* ToDo: We should try to restart the adaptor... */ + outw(0xffff, ioaddr + 24); + outw(0xffff, ioaddr + TX_STATUS); + outw(0xe85a, ioaddr + CONFIG_0); + outw(0x8100, ioaddr + TX_INTR); + dev->tbusy=0; + dev->trans_start = jiffies; + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + } + + /* 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) { + dev_tint(dev); + return 0; + } + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + 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; + + /* Turn off the possible Tx interrupts. */ + outb(0x00, ioaddr + TX_INTR); + + outw(length, ioaddr + DATAPORT); + outsw(ioaddr + DATAPORT, buf, (length + 1) >> 1); + + lp->tx_queue++; + lp->tx_queue_len += length + 2; + + if (lp->tx_started == 0) { + /* If the Tx is idle, always trigger a transmit. */ + outb(0x80 | lp->tx_queue, ioaddr + TX_START); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + lp->tx_started = 1; + dev->tbusy = 0; + } else if (lp->tx_queue_len < 4096 - 1502) + /* Yes, there is room for one more packet. */ + dev->tbusy = 0; + + /* Turn on Tx interrupts back on. */ + outb(0x82, ioaddr + TX_INTR); + } + dev_kfree_skb (skb, FREE_WRITE); + + return 0; +} + +/* The typical workload of the driver: + Handle the network interface interrupts. */ +static void +net_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = dev_id; + struct net_local *lp; + int ioaddr, status; + + if (dev == NULL) { + printk ("at1700_interrupt(): irq %d for unknown device.\n", irq); + return; + } + dev->interrupt = 1; + + ioaddr = dev->base_addr; + lp = (struct net_local *)dev->priv; + status = inw(ioaddr + TX_STATUS); + outw(status, ioaddr + TX_STATUS); + + if (net_debug > 4) + printk("%s: Interrupt with status %04x.\n", dev->name, status); + if (status & 0xff00 + || (inb(ioaddr + RX_MODE) & 0x40) == 0) { /* Got a packet(s). */ + net_rx(dev); + } + if (status & 0x00ff) { + if (status & 0x80) { + lp->stats.tx_packets++; + if (lp->tx_queue) { + outb(0x80 | lp->tx_queue, ioaddr + TX_START); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } else { + lp->tx_started = 0; + /* Turn on Tx interrupts off. */ + outb(0x00, ioaddr + TX_INTR); + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } + } + } + + dev->interrupt = 0; + return; +} + +/* We have a good packet(s), get it/them out of the buffers. */ +static void +net_rx(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + int boguscount = 5; + + while ((inb(ioaddr + RX_MODE) & 0x40) == 0) { + ushort status = inw(ioaddr + DATAPORT); + ushort pkt_len = inw(ioaddr + DATAPORT); + + if (net_debug > 4) + printk("%s: Rxing packet mode %02x status %04x.\n", + dev->name, inb(ioaddr + RX_MODE), status); +#ifndef final_version + if (status == 0) { + outb(0x05, ioaddr + 14); + break; + } +#endif + + if ((status & 0xF0) != 0x20) { /* There was an error. */ + lp->stats.rx_errors++; + if (status & 0x08) lp->stats.rx_length_errors++; + if (status & 0x04) lp->stats.rx_frame_errors++; + if (status & 0x02) lp->stats.rx_crc_errors++; + if (status & 0x01) lp->stats.rx_over_errors++; + } else { + /* Malloc up new buffer. */ + struct sk_buff *skb; + + if (pkt_len > 1550) { + printk("%s: The AT1700 claimed a very large packet, size %d.\n", + dev->name, pkt_len); + /* Prime the FIFO and then flush the packet. */ + inw(ioaddr + DATAPORT); inw(ioaddr + DATAPORT); + outb(0x05, ioaddr + 14); + lp->stats.rx_errors++; + break; + } + skb = dev_alloc_skb(pkt_len+3); + if (skb == NULL) { + printk("%s: Memory squeeze, dropping packet (len %d).\n", + dev->name, pkt_len); + /* Prime the FIFO and then flush the packet. */ + inw(ioaddr + DATAPORT); inw(ioaddr + DATAPORT); + outb(0x05, ioaddr + 14); + lp->stats.rx_dropped++; + break; + } + skb->dev = dev; + skb_reserve(skb,2); + + insw(ioaddr + DATAPORT, skb_put(skb,pkt_len), (pkt_len + 1) >> 1); + skb->protocol=eth_type_trans(skb, dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + if (--boguscount <= 0) + break; + } + + /* If any worth-while packets have been received, dev_rint() + has done a mark_bh(NET_BH) for us and will work on them + when we get to the bottom-half routine. */ + { + int i; + for (i = 0; i < 20; i++) { + if ((inb(ioaddr + RX_MODE) & 0x40) == 0x40) + break; + inw(ioaddr + DATAPORT); /* dummy status read */ + outb(0x05, ioaddr + 14); + } + + if (net_debug > 5) + printk("%s: Exint Rx packet with mode %02x after %d ticks.\n", + dev->name, inb(ioaddr + RX_MODE), i); + } + return; +} + +/* The inverse routine to net_open(). */ +static int net_close(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + + dev->tbusy = 1; + dev->start = 0; + + /* Set configuration register 0 to disable Tx and Rx. */ + outb(0xda, ioaddr + CONFIG_0); + + /* No statistic counters on the chip to update. */ + +#if 0 + /* Disable the IRQ on boards where it is feasible. */ + if (lp->jumpered) { + outb(0x00, ioaddr + IOCONFIG1); + free_irq(dev->irq, dev); + } +#endif + + /* Power-down the chip. Green, green, green! */ + outb(0x00, ioaddr + CONFIG_1); + + MOD_DEC_USE_COUNT; + + return 0; +} + +/* Get the current statistics. + This may be called with the card open or closed. + There are no on-chip counters, so this function is trivial. +*/ +static struct enet_statistics * +net_get_stats(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + return &lp->stats; +} + +/* + Set the multicast/promiscuous mode for this adaptor. +*/ + +/* The little-endian AUTODIN II ethernet CRC calculation. + N.B. Do not use for bulk data, use a table-based routine instead. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial_le = 0xedb88320U; +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + +static void +set_rx_mode(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct net_local *lp = (struct net_local *)dev->priv; + unsigned char mc_filter[8]; /* Multicast hash filter */ + long flags; + int i; + + if (dev->flags & IFF_PROMISC) { + /* Unconditionally log net taps. */ + printk("%s: Promiscuous mode enabled.\n", dev->name); + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(3, ioaddr + RX_MODE); /* Enable promiscuous mode */ + } else if (dev->mc_count > MC_FILTERBREAK + || (dev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(2, ioaddr + RX_MODE); /* Use normal mode. */ + } else if (dev->mc_count == 0) { + memset(mc_filter, 0x00, sizeof(mc_filter)); + outb(1, ioaddr + RX_MODE); /* Ignore almost all multicasts. */ + } else { + struct dev_mc_list *mclist; + int i; + + memset(mc_filter, 0, sizeof(mc_filter)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + set_bit(ether_crc_le(ETH_ALEN, mclist->dmi_addr) >> 26, + mc_filter); + } + + save_flags(flags); + cli(); + if (memcmp(mc_filter, lp->mc_filter, sizeof(mc_filter))) { + int saved_bank = inw(ioaddr + CONFIG_0); + /* Switch to bank 1 and set the multicast table. */ + outw((saved_bank & ~0x0C00) | 0x0480, ioaddr + CONFIG_0); + for (i = 0; i < 8; i++) + outb(mc_filter[i], ioaddr + 8 + i); + memcpy(lp->mc_filter, mc_filter, sizeof(mc_filter)); + outw(saved_bank, ioaddr + CONFIG_0); + } + restore_flags(flags); + return; +} + +#ifdef MODULE +static char devicename[9] = { 0, }; +static struct device dev_at1700 = { + devicename, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, at1700_probe }; + +static int io = 0x260; +static int irq = 0; + +int init_module(void) +{ + if (io == 0) + printk("at1700: You should not use auto-probing with insmod!\n"); + dev_at1700.base_addr = io; + dev_at1700.irq = irq; + if (register_netdev(&dev_at1700) != 0) { + printk("at1700: register_netdev() returned non-zero.\n"); + return -EIO; + } + return 0; +} + +void +cleanup_module(void) +{ + unregister_netdev(&dev_at1700); + kfree(dev_at1700.priv); + dev_at1700.priv = NULL; + + /* If we don't do this, we can't re-insmod it later. */ + free_irq(dev_at1700.irq, NULL); + release_region(dev_at1700.base_addr, AT1700_IO_EXTENT); +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c at1700.c" + * alt-compile-command: "gcc -DMODVERSIONS -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c at1700.c" + * tab-width: 4 + * c-basic-offset: 4 + * c-indent-level: 4 + * End: + */ diff --git a/linux/src/drivers/net/atp.c b/linux/src/drivers/net/atp.c new file mode 100644 index 00000000..a9445eaa --- /dev/null +++ b/linux/src/drivers/net/atp.c @@ -0,0 +1,977 @@ +/* atp.c: Attached (pocket) ethernet adapter driver for linux. */ +/* + This is a driver for commonly OEMed pocket (parallel port) + ethernet adapters based on the Realtek RTL8002 and RTL8012 chips. + + Written 1993-95,1997 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + The timer-based reset code was written by Bill Carlson, wwc@super.org. +*/ + +static const char *version = + "atp.c:v1.08 4/1/97 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +/* Operational parameters that may be safely changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((400*HZ)/1000) + +/* + This file is a device driver for the RealTek (aka AT-Lan-Tec) pocket + ethernet adapter. This is a common low-cost OEM pocket ethernet + adapter, sold under many names. + + Sources: + This driver was written from the packet driver assembly code provided by + Vincent Bono of AT-Lan-Tec. Ever try to figure out how a complicated + device works just from the assembly code? It ain't pretty. The following + description is written based on guesses and writing lots of special-purpose + code to test my theorized operation. + + In 1997 Realtek made available the documentation for the second generation + RTL8012 chip, which has lead to several driver improvements. + http://www.realtek.com.tw/cn/cn.html + + Theory of Operation + + The RTL8002 adapter seems to be built around a custom spin of the SEEQ + controller core. It probably has a 16K or 64K internal packet buffer, of + which the first 4K is devoted to transmit and the rest to receive. + The controller maintains the queue of received packet and the packet buffer + access pointer internally, with only 'reset to beginning' and 'skip to next + packet' commands visible. The transmit packet queue holds two (or more?) + packets: both 'retransmit this packet' (due to collision) and 'transmit next + packet' commands must be started by hand. + + The station address is stored in a standard bit-serial EEPROM which must be + read (ughh) by the device driver. (Provisions have been made for + substituting a 74S288 PROM, but I haven't gotten reports of any models + using it.) Unlike built-in devices, a pocket adapter can temporarily lose + power without indication to the device driver. The major effect is that + the station address, receive filter (promiscuous, etc.) and transceiver + must be reset. + + The controller itself has 16 registers, some of which use only the lower + bits. The registers are read and written 4 bits at a time. The four bit + register address is presented on the data lines along with a few additional + timing and control bits. The data is then read from status port or written + to the data port. + + Correction: the controller has two banks of 16 registers. The second + bank contains only the multicast filter table (now used) and the EEPROM + access registers. + + Since the bulk data transfer of the actual packets through the slow + parallel port dominates the driver's running time, four distinct data + (non-register) transfer modes are provided by the adapter, two in each + direction. In the first mode timing for the nibble transfers is + provided through the data port. In the second mode the same timing is + provided through the control port. In either case the data is read from + the status port and written to the data port, just as it is accessing + registers. + + In addition to the basic data transfer methods, several more are modes are + created by adding some delay by doing multiple reads of the data to allow + it to stabilize. This delay seems to be needed on most machines. + + The data transfer mode is stored in the 'dev->if_port' field. Its default + value is '4'. It may be overridden at boot-time using the third parameter + to the "ether=..." initialization. + + The header file <atp.h> provides inline functions that encapsulate the + register and data access methods. These functions are hand-tuned to + generate reasonable object code. This header file also documents my + interpretations of the device registers. +*/ +#include <linux/config.h> +#ifdef MODULE +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#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/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "atp.h" + +/* Kernel compatibility defines, common to David Hind's PCMCIA package. + This is only in the support-all-kernels source code. */ +#include <linux/version.h> /* Evil, but neccessary */ + +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10300 +#define RUN_AT(x) (x) /* What to put in timer->expires. */ +#define DEV_ALLOC_SKB(len) alloc_skb(len, GFP_ATOMIC) +#define virt_to_bus(addr) ((unsigned long)addr) +#define bus_to_virt(addr) ((void*)addr) + +#else /* 1.3.0 and later */ +#define RUN_AT(x) (jiffies + (x)) +#define DEV_ALLOC_SKB(len) dev_alloc_skb(len + 2) +#endif +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10338 +#ifdef MODULE +#if !defined(CONFIG_MODVERSIONS) && !defined(__NO_VERSION__) +char kernel_version[] = UTS_RELEASE; +#endif +#else +#undef MOD_INC_USE_COUNT +#define MOD_INC_USE_COUNT +#undef MOD_DEC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif +#endif /* 1.3.38 */ + +#if (LINUX_VERSION_CODE >= 0x10344) +#define NEW_MULTICAST +#include <linux/delay.h> +#endif + +#ifdef SA_SHIRQ +#define FREE_IRQ(irqnum, dev) free_irq(irqnum, dev) +#define REQUEST_IRQ(i,h,f,n, instance) request_irq(i,h,f,n, instance) +#define IRQ(irq, dev_id, pt_regs) (irq, dev_id, pt_regs) +#else +#define FREE_IRQ(irqnum, dev) free_irq(irqnum) +#define REQUEST_IRQ(i,h,f,n, instance) request_irq(i,h,f,n) +#define IRQ(irq, dev_id, pt_regs) (irq, pt_regs) +#endif +/* End of kernel compatibility defines. */ + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 1 +#endif +static unsigned int net_debug = NET_DEBUG; + +/* The number of low I/O ports used by the ethercard. */ +#define ETHERCARD_TOTAL_SIZE 3 + +/* Sequence to switch an 8012 from printer mux to ethernet mode. */ +static char mux_8012[] = { 0xff, 0xf7, 0xff, 0xfb, 0xf3, 0xfb, 0xff, 0xf7,}; + +/* This code, written by wwc@super.org, resets the adapter every + TIMED_CHECKER ticks. This recovers from an unknown error which + hangs the device. */ +#define TIMED_CHECKER (HZ/4) +#ifdef TIMED_CHECKER +#include <linux/timer.h> +static void atp_timed_checker(unsigned long ignored); +#endif + +/* Index to functions, as function prototypes. */ + +extern int atp_init(struct device *dev); + +static int atp_probe1(struct device *dev, short ioaddr); +static void get_node_ID(struct device *dev); +static unsigned short eeprom_op(short ioaddr, unsigned int cmd); +static int net_open(struct device *dev); +static void hardware_init(struct device *dev); +static void write_packet(short ioaddr, int length, unsigned char *packet, int mode); +static void trigger_send(short ioaddr, int length); +static int net_send_packet(struct sk_buff *skb, struct device *dev); +static void net_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs); +static void net_rx(struct device *dev); +static void read_block(short ioaddr, int length, unsigned char *buffer, int data_mode); +static int net_close(struct device *dev); +static struct enet_statistics *net_get_stats(struct device *dev); +#ifdef NEW_MULTICAST +static void set_rx_mode_8002(struct device *dev); +static void set_rx_mode_8012(struct device *dev); +#else +static void set_rx_mode_8002(struct device *dev, int num_addrs, void *addrs); +static void set_rx_mode_8012(struct device *dev, int num_addrs, void *addrs); +#endif + + +/* A list of all installed ATP devices, for removing the driver module. */ +static struct device *root_atp_dev = NULL; + +/* Check for a network adapter 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, allocate space for the device and return success + (detachable devices only). + */ +int +atp_init(struct device *dev) +{ + int *port, ports[] = {0x378, 0x278, 0x3bc, 0}; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return atp_probe1(dev, base_addr); + else if (base_addr == 1) /* Don't probe at all. */ + return ENXIO; + + for (port = ports; *port; port++) { + int ioaddr = *port; + outb(0x57, ioaddr + PAR_DATA); + if (inb(ioaddr + PAR_DATA) != 0x57) + continue; + if (atp_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} + +static int atp_probe1(struct device *dev, short ioaddr) +{ + struct net_local *lp; + int saved_ctrl_reg, status, i; + + outb(0xff, ioaddr + PAR_DATA); + /* Save the original value of the Control register, in case we guessed + wrong. */ + saved_ctrl_reg = inb(ioaddr + PAR_CONTROL); + /* IRQEN=0, SLCTB=high INITB=high, AUTOFDB=high, STBB=high. */ + outb(0x04, ioaddr + PAR_CONTROL); + /* Turn off the printer multiplexer on the 8012. */ + for (i = 0; i < 8; i++) + outb(mux_8012[i], ioaddr + PAR_DATA); + write_reg_high(ioaddr, CMR1, CMR1h_RESET); + eeprom_delay(2048); + status = read_nibble(ioaddr, CMR1); + + if ((status & 0x78) != 0x08) { + /* The pocket adapter probe failed, restore the control register. */ + outb(saved_ctrl_reg, ioaddr + PAR_CONTROL); + return 1; + } + status = read_nibble(ioaddr, CMR2_h); + if ((status & 0x78) != 0x10) { + outb(saved_ctrl_reg, ioaddr + PAR_CONTROL); + return 1; + } + + dev = init_etherdev(dev, sizeof(struct net_local)); + + /* Find the IRQ used by triggering an interrupt. */ + write_reg_byte(ioaddr, CMR2, 0x01); /* No accept mode, IRQ out. */ + write_reg_high(ioaddr, CMR1, CMR1h_RxENABLE | CMR1h_TxENABLE); /* Enable Tx and Rx. */ + + /* Omit autoIRQ routine for now. Use "table lookup" instead. Uhgggh. */ + if (ioaddr == 0x378) + dev->irq = 7; + else + dev->irq = 5; + write_reg_high(ioaddr, CMR1, CMR1h_TxRxOFF); /* Disable Tx and Rx units. */ + write_reg(ioaddr, CMR2, CMR2_NULL); + + dev->base_addr = ioaddr; + + /* Read the station address PROM. */ + get_node_ID(dev); + + printk("%s: Pocket adapter found at %#3lx, IRQ %d, SAPROM " + "%02X:%02X:%02X:%02X:%02X:%02X.\n", dev->name, dev->base_addr, + dev->irq, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + + /* Reset the ethernet hardware and activate the printer pass-through. */ + write_reg_high(ioaddr, CMR1, CMR1h_RESET | CMR1h_MUX); + + if (net_debug) + printk(version); + + /* Initialize the device structure. */ + ether_setup(dev); + if (dev->priv == NULL) + dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + memset(dev->priv, 0, sizeof(struct net_local)); + + lp = (struct net_local *)dev->priv; + lp->chip_type = RTL8002; + lp->addr_mode = CMR2h_Normal; + + lp->next_module = root_atp_dev; + root_atp_dev = dev; + + /* For the ATP adapter the "if_port" is really the data transfer mode. */ + dev->if_port = (dev->mem_start & 0xf) ? (dev->mem_start & 0x7) : 4; + if (dev->mem_end & 0xf) + net_debug = dev->mem_end & 7; + + dev->open = net_open; + dev->stop = net_close; + dev->hard_start_xmit = net_send_packet; + dev->get_stats = net_get_stats; + dev->set_multicast_list = + lp->chip_type == RTL8002 ? &set_rx_mode_8002 : &set_rx_mode_8012; + + return 0; +} + +/* Read the station address PROM, usually a word-wide EEPROM. */ +static void get_node_ID(struct device *dev) +{ + short ioaddr = dev->base_addr; + int sa_offset = 0; + int i; + + write_reg(ioaddr, CMR2, CMR2_EEPROM); /* Point to the EEPROM control registers. */ + + /* Some adapters have the station address at offset 15 instead of offset + zero. Check for it, and fix it if needed. */ + if (eeprom_op(ioaddr, EE_READ(0)) == 0xffff) + sa_offset = 15; + + for (i = 0; i < 3; i++) + ((unsigned short *)dev->dev_addr)[i] = + ntohs(eeprom_op(ioaddr, EE_READ(sa_offset + i))); + + write_reg(ioaddr, CMR2, CMR2_NULL); +} + +/* + An EEPROM read command starts by shifting out 0x60+address, and then + shifting in the serial data. See the NatSemi databook for details. + * ________________ + * CS : __| + * ___ ___ + * CLK: ______| |___| | + * __ _______ _______ + * DI : __X_______X_______X + * DO : _________X_______X + */ + +static unsigned short eeprom_op(short ioaddr, unsigned int cmd) +{ + unsigned eedata_out = 0; + int num_bits = EE_CMD_SIZE; + + while (--num_bits >= 0) { + char outval = test_bit(num_bits, &cmd) ? EE_DATA_WRITE : 0; + write_reg_high(ioaddr, PROM_CMD, outval | EE_CLK_LOW); + eeprom_delay(5); + write_reg_high(ioaddr, PROM_CMD, outval | EE_CLK_HIGH); + eedata_out <<= 1; + if (read_nibble(ioaddr, PROM_DATA) & EE_DATA_READ) + eedata_out++; + eeprom_delay(5); + } + write_reg_high(ioaddr, PROM_CMD, EE_CLK_LOW & ~EE_CS); + return eedata_out; +} + + +/* Open/initialize the board. This is called (in the current kernel) + sometime after booting when the 'ifconfig' program is run. + + This routine sets everything up anew at each open, even + registers that "should" only need to be set once at boot, so that + there is non-reboot way to recover if something goes wrong. + + This is an attachable device: if there is no dev->priv entry then it wasn't + probed for at boot-time, and we need to probe for it again. + */ +static int net_open(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + + /* The interrupt line is turned off (tri-stated) when the device isn't in + use. That's especially important for "attached" interfaces where the + port or interrupt may be shared. */ +#ifndef SA_SHIRQ + if (irq2dev_map[dev->irq] != 0 + || (irq2dev_map[dev->irq] = dev) == 0 + || REQUEST_IRQ(dev->irq, &net_interrupt, 0, "ATP", dev)) { + return -EAGAIN; + } +#else + if (request_irq(dev->irq, &net_interrupt, 0, "ATP Ethernet", dev)) + return -EAGAIN; +#endif + + MOD_INC_USE_COUNT; + hardware_init(dev); + dev->start = 1; + + init_timer(&lp->timer); + lp->timer.expires = RUN_AT(TIMED_CHECKER); + lp->timer.data = (unsigned long)dev; + lp->timer.function = &atp_timed_checker; /* timer handler */ + add_timer(&lp->timer); + + return 0; +} + +/* This routine resets the hardware. We initialize everything, assuming that + the hardware may have been temporarily detached. */ +static void hardware_init(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + /* Turn off the printer multiplexer on the 8012. */ + for (i = 0; i < 8; i++) + outb(mux_8012[i], ioaddr + PAR_DATA); + write_reg_high(ioaddr, CMR1, CMR1h_RESET); + + for (i = 0; i < 6; i++) + write_reg_byte(ioaddr, PAR0 + i, dev->dev_addr[i]); + + write_reg_high(ioaddr, CMR2, lp->addr_mode); + + if (net_debug > 2) { + printk("%s: Reset: current Rx mode %d.\n", dev->name, + (read_nibble(ioaddr, CMR2_h) >> 3) & 0x0f); + } + + write_reg(ioaddr, CMR2, CMR2_IRQOUT); + write_reg_high(ioaddr, CMR1, CMR1h_RxENABLE | CMR1h_TxENABLE); + + /* Enable the interrupt line from the serial port. */ + outb(Ctrl_SelData + Ctrl_IRQEN, ioaddr + PAR_CONTROL); + + /* Unmask the interesting interrupts. */ + write_reg(ioaddr, IMR, ISR_RxOK | ISR_TxErr | ISR_TxOK); + write_reg_high(ioaddr, IMR, ISRh_RxErr); + + lp->tx_unit_busy = 0; + lp->pac_cnt_in_tx_buf = 0; + lp->saved_tx_size = 0; + + dev->tbusy = 0; + dev->interrupt = 0; +} + +static void trigger_send(short ioaddr, int length) +{ + write_reg_byte(ioaddr, TxCNT0, length & 0xff); + write_reg(ioaddr, TxCNT1, length >> 8); + write_reg(ioaddr, CMR1, CMR1_Xmit); +} + +static void write_packet(short ioaddr, int length, unsigned char *packet, int data_mode) +{ + length = (length + 1) & ~1; /* Round up to word length. */ + outb(EOC+MAR, ioaddr + PAR_DATA); + if ((data_mode & 1) == 0) { + /* Write the packet out, starting with the write addr. */ + outb(WrAddr+MAR, ioaddr + PAR_DATA); + do { + write_byte_mode0(ioaddr, *packet++); + } while (--length > 0) ; + } else { + /* Write the packet out in slow mode. */ + unsigned char outbyte = *packet++; + + outb(Ctrl_LNibWrite + Ctrl_IRQEN, ioaddr + PAR_CONTROL); + outb(WrAddr+MAR, ioaddr + PAR_DATA); + + outb((outbyte & 0x0f)|0x40, ioaddr + PAR_DATA); + outb(outbyte & 0x0f, ioaddr + PAR_DATA); + outbyte >>= 4; + outb(outbyte & 0x0f, ioaddr + PAR_DATA); + outb(Ctrl_HNibWrite + Ctrl_IRQEN, ioaddr + PAR_CONTROL); + while (--length > 0) + write_byte_mode1(ioaddr, *packet++); + } + /* Terminate the Tx frame. End of write: ECB. */ + outb(0xff, ioaddr + PAR_DATA); + outb(Ctrl_HNibWrite | Ctrl_SelData | Ctrl_IRQEN, ioaddr + PAR_CONTROL); +} + +static int +net_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + +#ifndef final_version + if (skb == NULL || skb->len <= 0) { + printk("%s: Obsolete driver layer request made: skbuff==NULL.\n", + dev->name); + dev_tint(dev); + return 0; + } +#endif + + /* Use transmit-while-tbusy as a crude error timer. */ + if (set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start < TX_TIMEOUT) + return 1; + printk("%s: transmit timed out, %s?\n", dev->name, + inb(ioaddr + PAR_CONTROL) & 0x10 ? "network cable problem" + : "IRQ conflict"); + lp->stats.tx_errors++; + /* Try to restart the adapter. */ + hardware_init(dev); + dev->tbusy=0; + dev->trans_start = jiffies; + return 1; + } else { + short length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + unsigned char *buf = skb->data; + int flags; + + /* Disable interrupts by writing 0x00 to the Interrupt Mask Register. + This sequence must not be interrupted by an incoming packet. */ + save_flags(flags); + cli(); + write_reg(ioaddr, IMR, 0); + write_reg_high(ioaddr, IMR, 0); + restore_flags(flags); + + write_packet(ioaddr, length, buf, dev->if_port); + + lp->pac_cnt_in_tx_buf++; + if (lp->tx_unit_busy == 0) { + trigger_send(ioaddr, length); + lp->saved_tx_size = 0; /* Redundant */ + lp->re_tx = 0; + lp->tx_unit_busy = 1; + } else + lp->saved_tx_size = length; + + dev->trans_start = jiffies; + /* Re-enable the LPT interrupts. */ + write_reg(ioaddr, IMR, ISR_RxOK | ISR_TxErr | ISR_TxOK); + write_reg_high(ioaddr, IMR, ISRh_RxErr); + } + + dev_kfree_skb (skb, FREE_WRITE); + + return 0; +} + +/* The typical workload of the driver: + Handle the network interface interrupts. */ +static void +net_interrupt IRQ(int irq, void *dev_instance, struct pt_regs * regs) +{ +#ifdef SA_SHIRQ + struct device *dev = (struct device *)dev_instance; +#else + struct device *dev = (struct device *)(irq2dev_map[irq]); +#endif + struct net_local *lp; + int ioaddr, status, boguscount = 20; + static int num_tx_since_rx = 0; + + if (dev == NULL) { + printk ("ATP_interrupt(): irq %d for unknown device.\n", irq); + return; + } + dev->interrupt = 1; + + ioaddr = dev->base_addr; + lp = (struct net_local *)dev->priv; + + /* Disable additional spurious interrupts. */ + outb(Ctrl_SelData, ioaddr + PAR_CONTROL); + + /* The adapter's output is currently the IRQ line, switch it to data. */ + write_reg(ioaddr, CMR2, CMR2_NULL); + write_reg(ioaddr, IMR, 0); + + if (net_debug > 5) printk("%s: In interrupt ", dev->name); + while (--boguscount > 0) { + status = read_nibble(ioaddr, ISR); + if (net_debug > 5) printk("loop status %02x..", status); + + if (status & (ISR_RxOK<<3)) { + write_reg(ioaddr, ISR, ISR_RxOK); /* Clear the Rx interrupt. */ + do { + int read_status = read_nibble(ioaddr, CMR1); + if (net_debug > 6) + printk("handling Rx packet %02x..", read_status); + /* We acknowledged the normal Rx interrupt, so if the interrupt + is still outstanding we must have a Rx error. */ + if (read_status & (CMR1_IRQ << 3)) { /* Overrun. */ + lp->stats.rx_over_errors++; + /* Set to no-accept mode long enough to remove a packet. */ + write_reg_high(ioaddr, CMR2, CMR2h_OFF); + net_rx(dev); + /* Clear the interrupt and return to normal Rx mode. */ + write_reg_high(ioaddr, ISR, ISRh_RxErr); + write_reg_high(ioaddr, CMR2, lp->addr_mode); + } else if ((read_status & (CMR1_BufEnb << 3)) == 0) { + net_rx(dev); + dev->last_rx = jiffies; + num_tx_since_rx = 0; + } else + break; + } while (--boguscount > 0); + } else if (status & ((ISR_TxErr + ISR_TxOK)<<3)) { + if (net_debug > 6) printk("handling Tx done.."); + /* Clear the Tx interrupt. We should check for too many failures + and reinitialize the adapter. */ + write_reg(ioaddr, ISR, ISR_TxErr + ISR_TxOK); + if (status & (ISR_TxErr<<3)) { + lp->stats.collisions++; + if (++lp->re_tx > 15) { + lp->stats.tx_aborted_errors++; + hardware_init(dev); + break; + } + /* Attempt to retransmit. */ + if (net_debug > 6) printk("attempting to ReTx"); + write_reg(ioaddr, CMR1, CMR1_ReXmit + CMR1_Xmit); + } else { + /* Finish up the transmit. */ + lp->stats.tx_packets++; + lp->pac_cnt_in_tx_buf--; + if ( lp->saved_tx_size) { + trigger_send(ioaddr, lp->saved_tx_size); + lp->saved_tx_size = 0; + lp->re_tx = 0; + } else + lp->tx_unit_busy = 0; + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } + num_tx_since_rx++; + } else if (num_tx_since_rx > 8 + && jiffies > dev->last_rx + 100) { + if (net_debug > 2) + printk("%s: Missed packet? No Rx after %d Tx and %ld jiffies" + " status %02x CMR1 %02x.\n", dev->name, + num_tx_since_rx, jiffies - dev->last_rx, status, + (read_nibble(ioaddr, CMR1) >> 3) & 15); + lp->stats.rx_missed_errors++; + hardware_init(dev); + num_tx_since_rx = 0; + break; + } else + break; + } + + /* This following code fixes a rare (and very difficult to track down) + problem where the adapter forgets its ethernet address. */ + { + int i; + for (i = 0; i < 6; i++) + write_reg_byte(ioaddr, PAR0 + i, dev->dev_addr[i]); + } + + /* Tell the adapter that it can go back to using the output line as IRQ. */ + write_reg(ioaddr, CMR2, CMR2_IRQOUT); + /* Enable the physical interrupt line, which is sure to be low until.. */ + outb(Ctrl_SelData + Ctrl_IRQEN, ioaddr + PAR_CONTROL); + /* .. we enable the interrupt sources. */ + write_reg(ioaddr, IMR, ISR_RxOK | ISR_TxErr | ISR_TxOK); + write_reg_high(ioaddr, IMR, ISRh_RxErr); /* Hmmm, really needed? */ + + if (net_debug > 5) printk("exiting interrupt.\n"); + + dev->interrupt = 0; + + return; +} + +#ifdef TIMED_CHECKER +/* This following code fixes a rare (and very difficult to track down) + problem where the adapter forgets its ethernet address. */ +static void atp_timed_checker(unsigned long data) +{ + struct device *dev = (struct device *)data; + int ioaddr = dev->base_addr; + struct net_local *lp = (struct net_local *)dev->priv; + int tickssofar = jiffies - lp->last_rx_time; + int i; + + if (tickssofar > 2*HZ && dev->interrupt == 0) { +#if 1 + for (i = 0; i < 6; i++) + write_reg_byte(ioaddr, PAR0 + i, dev->dev_addr[i]); + lp->last_rx_time = jiffies; +#else + for (i = 0; i < 6; i++) + if (read_cmd_byte(ioaddr, PAR0 + i) != atp_timed_dev->dev_addr[i]) + { + struct net_local *lp = (struct net_local *)atp_timed_dev->priv; + write_reg_byte(ioaddr, PAR0 + i, atp_timed_dev->dev_addr[i]); + if (i == 2) + lp->stats.tx_errors++; + else if (i == 3) + lp->stats.tx_dropped++; + else if (i == 4) + lp->stats.collisions++; + else + lp->stats.rx_errors++; + } +#endif + } + lp->timer.expires = RUN_AT(TIMED_CHECKER); + add_timer(&lp->timer); +} +#endif + +/* We have a good packet(s), get it/them out of the buffers. */ +static void net_rx(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + struct rx_header rx_head; + + /* Process the received packet. */ + outb(EOC+MAR, ioaddr + PAR_DATA); + read_block(ioaddr, 8, (unsigned char*)&rx_head, dev->if_port); + if (net_debug > 5) + printk(" rx_count %04x %04x %04x %04x..", rx_head.pad, + rx_head.rx_count, rx_head.rx_status, rx_head.cur_addr); + if ((rx_head.rx_status & 0x77) != 0x01) { + lp->stats.rx_errors++; + if (rx_head.rx_status & 0x0004) lp->stats.rx_frame_errors++; + else if (rx_head.rx_status & 0x0002) lp->stats.rx_crc_errors++; + if (net_debug > 3) printk("%s: Unknown ATP Rx error %04x.\n", + dev->name, rx_head.rx_status); + if (rx_head.rx_status & 0x0020) { + lp->stats.rx_fifo_errors++; + write_reg_high(ioaddr, CMR1, CMR1h_TxENABLE); + write_reg_high(ioaddr, CMR1, CMR1h_RxENABLE | CMR1h_TxENABLE); + } else if (rx_head.rx_status & 0x0050) + hardware_init(dev); + return; + } else { + /* Malloc up new buffer. The "-4" is omits the FCS (CRC). */ + int pkt_len = (rx_head.rx_count & 0x7ff) - 4; + struct sk_buff *skb; + + skb = DEV_ALLOC_SKB(pkt_len + 2); + if (skb == NULL) { + printk("%s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + goto done; + } + skb->dev = dev; + +#if LINUX_VERSION_CODE >= 0x10300 + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + read_block(ioaddr, pkt_len, skb_put(skb,pkt_len), dev->if_port); + skb->protocol = eth_type_trans(skb, dev); +#else + read_block(ioaddr, pkt_len, skb->data, dev->if_port); + skb->len = pkt_len; +#endif + netif_rx(skb); + lp->stats.rx_packets++; + } + done: + write_reg(ioaddr, CMR1, CMR1_NextPkt); + lp->last_rx_time = jiffies; + return; +} + +static void read_block(short ioaddr, int length, unsigned char *p, int data_mode) +{ + + if (data_mode <= 3) { /* Mode 0 or 1 */ + outb(Ctrl_LNibRead, ioaddr + PAR_CONTROL); + outb(length == 8 ? RdAddr | HNib | MAR : RdAddr | MAR, + ioaddr + PAR_DATA); + if (data_mode <= 1) { /* Mode 0 or 1 */ + do *p++ = read_byte_mode0(ioaddr); while (--length > 0); + } else /* Mode 2 or 3 */ + do *p++ = read_byte_mode2(ioaddr); while (--length > 0); + } else if (data_mode <= 5) + do *p++ = read_byte_mode4(ioaddr); while (--length > 0); + else + do *p++ = read_byte_mode6(ioaddr); while (--length > 0); + + outb(EOC+HNib+MAR, ioaddr + PAR_DATA); + outb(Ctrl_SelData, ioaddr + PAR_CONTROL); +} + +/* The inverse routine to net_open(). */ +static int +net_close(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + + dev->tbusy = 1; + dev->start = 0; + + del_timer(&lp->timer); + + /* Flush the Tx and disable Rx here. */ + lp->addr_mode = CMR2h_OFF; + write_reg_high(ioaddr, CMR2, CMR2h_OFF); + + /* Free the IRQ line. */ + outb(0x00, ioaddr + PAR_CONTROL); + FREE_IRQ(dev->irq, dev); +#ifndef SA_SHIRQ + irq2dev_map[dev->irq] = 0; +#endif + + /* Reset the ethernet hardware and activate the printer pass-through. */ + write_reg_high(ioaddr, CMR1, CMR1h_RESET | CMR1h_MUX); + + MOD_DEC_USE_COUNT; + + return 0; +} + +/* Get the current statistics. This may be called with the card open or + closed. */ +static struct enet_statistics * +net_get_stats(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + return &lp->stats; +} + +/* + * Set or clear the multicast filter for this adapter. + */ + +/* The little-endian AUTODIN32 ethernet CRC calculation. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial_le = 0xedb88320U; +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + +static void +#ifdef NEW_MULTICAST +set_rx_mode_8002(struct device *dev) +#else +static void set_rx_mode_8002(struct device *dev, int num_addrs, void *addrs); +#endif +{ + struct net_local *lp = (struct net_local *)dev->priv; + short ioaddr = dev->base_addr; + + if ( dev->mc_count > 0 || (dev->flags & (IFF_ALLMULTI|IFF_PROMISC))) { + /* We must make the kernel realise we had to move + * into promisc mode or we start all out war on + * the cable. - AC + */ + dev->flags|=IFF_PROMISC; + lp->addr_mode = CMR2h_PROMISC; + } else + lp->addr_mode = CMR2h_Normal; + write_reg_high(ioaddr, CMR2, lp->addr_mode); +} + +static void +#ifdef NEW_MULTICAST +set_rx_mode_8012(struct device *dev) +#else +static void set_rx_mode_8012(struct device *dev, int num_addrs, void *addrs); +#endif +{ + struct net_local *lp = (struct net_local *)dev->priv; + short ioaddr = dev->base_addr; + unsigned char new_mode, mc_filter[8]; /* Multicast hash filter */ + int i; + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + new_mode = CMR2h_PROMISC; + } else if ((dev->mc_count > 1000) || (dev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + new_mode = CMR2h_Normal; + } else { + struct dev_mc_list *mclist; + + memset(mc_filter, 0, sizeof(mc_filter)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + set_bit(ether_crc_le(ETH_ALEN, mclist->dmi_addr) & 0x3f, + mc_filter); + new_mode = CMR2h_Normal; + } + lp->addr_mode = new_mode; + write_reg(ioaddr, CMR2, CMR2_IRQOUT | 0x04); /* Switch to page 1. */ + for (i = 0; i < 8; i++) + write_reg_byte(ioaddr, i, mc_filter[i]); + if (net_debug > 2 || 1) { + lp->addr_mode = 1; + printk("%s: Mode %d, setting multicast filter to", + dev->name, lp->addr_mode); + for (i = 0; i < 8; i++) + printk(" %2.2x", mc_filter[i]); + printk(".\n"); + } + + write_reg_high(ioaddr, CMR2, lp->addr_mode); + write_reg(ioaddr, CMR2, CMR2_IRQOUT); /* Switch back to page 0 */ +} + +#ifdef MODULE +static int debug = 1; +int +init_module(void) +{ + net_debug = debug; + root_atp_dev = NULL; + atp_init(0); + return 0; +} + +void +cleanup_module(void) +{ + struct device *next_dev; + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + /* No need to release_region(), since we never snarf it. */ + while (root_atp_dev) { + next_dev = ((struct net_local *)root_atp_dev->priv)->next_module; + unregister_netdev(root_atp_dev); + kfree(root_atp_dev); + root_atp_dev = next_dev; + } +} +#endif /* MODULE */ + + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c atp.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/atp.h b/linux/src/drivers/net/atp.h new file mode 100644 index 00000000..b4d19337 --- /dev/null +++ b/linux/src/drivers/net/atp.h @@ -0,0 +1,274 @@ +/* Linux header file for the ATP pocket ethernet adapter. */ +/* v1.04 4/1/97 becker@cesdis.gsfc.nasa.gov. */ + +#include <linux/if_ether.h> +#include <linux/types.h> +#include <asm/io.h> + +struct net_local { +#ifdef __KERNEL__ + struct enet_statistics stats; +#endif + struct device *next_module; + struct timer_list timer; /* Media selection timer. */ + long last_rx_time; /* Last Rx, in jiffies, to handle Rx hang. */ + ushort saved_tx_size; + unsigned tx_unit_busy:1; + unsigned char re_tx, /* Number of packet retransmissions. */ + addr_mode, /* Current Rx filter e.g. promiscuous, etc. */ + pac_cnt_in_tx_buf, + chip_type; +}; + +struct rx_header { + ushort pad; /* Pad. */ + ushort rx_count; + ushort rx_status; /* Unknown bit assignments :-<. */ + ushort cur_addr; /* Apparently the current buffer address(?) */ +}; + +#define PAR_DATA 0 +#define PAR_STATUS 1 +#define PAR_CONTROL 2 + +enum chip_type { RTL8002, RTL8012 }; + +#define Ctrl_LNibRead 0x08 /* LP_PSELECP */ +#define Ctrl_HNibRead 0 +#define Ctrl_LNibWrite 0x08 /* LP_PSELECP */ +#define Ctrl_HNibWrite 0 +#define Ctrl_SelData 0x04 /* LP_PINITP */ +#define Ctrl_IRQEN 0x10 /* LP_PINTEN */ + +#define EOW 0xE0 +#define EOC 0xE0 +#define WrAddr 0x40 /* Set address of EPLC read, write register. */ +#define RdAddr 0xC0 +#define HNib 0x10 + +enum page0_regs +{ + /* The first six registers hold the ethernet physical station address. */ + PAR0 = 0, PAR1 = 1, PAR2 = 2, PAR3 = 3, PAR4 = 4, PAR5 = 5, + TxCNT0 = 6, TxCNT1 = 7, /* The transmit byte count. */ + TxSTAT = 8, RxSTAT = 9, /* Tx and Rx status. */ + ISR = 10, IMR = 11, /* Interrupt status and mask. */ + CMR1 = 12, /* Command register 1. */ + CMR2 = 13, /* Command register 2. */ + MODSEL = 14, /* Mode select register. */ + MAR = 14, /* Memory address register (?). */ + CMR2_h = 0x1d, }; + +enum eepage_regs +{ PROM_CMD = 6, PROM_DATA = 7 }; /* Note that PROM_CMD is in the "high" bits. */ + + +#define ISR_TxOK 0x01 +#define ISR_RxOK 0x04 +#define ISR_TxErr 0x02 +#define ISRh_RxErr 0x11 /* ISR, high nibble */ + +#define CMR1h_MUX 0x08 /* Select printer multiplexor on 8012. */ +#define CMR1h_RESET 0x04 /* Reset. */ +#define CMR1h_RxENABLE 0x02 /* Rx unit enable. */ +#define CMR1h_TxENABLE 0x01 /* Tx unit enable. */ +#define CMR1h_TxRxOFF 0x00 +#define CMR1_ReXmit 0x08 /* Trigger a retransmit. */ +#define CMR1_Xmit 0x04 /* Trigger a transmit. */ +#define CMR1_IRQ 0x02 /* Interrupt active. */ +#define CMR1_BufEnb 0x01 /* Enable the buffer(?). */ +#define CMR1_NextPkt 0x01 /* Enable the buffer(?). */ + +#define CMR2_NULL 8 +#define CMR2_IRQOUT 9 +#define CMR2_RAMTEST 10 +#define CMR2_EEPROM 12 /* Set to page 1, for reading the EEPROM. */ + +#define CMR2h_OFF 0 /* No accept mode. */ +#define CMR2h_Physical 1 /* Accept a physical address match only. */ +#define CMR2h_Normal 2 /* Accept physical and broadcast address. */ +#define CMR2h_PROMISC 3 /* Promiscuous mode. */ + +/* An inline function used below: it differs from inb() by explicitly return an unsigned + char, saving a truncation. */ +extern inline unsigned char inbyte(unsigned short port) +{ + unsigned char _v; + __asm__ __volatile__ ("inb %w1,%b0" :"=a" (_v):"d" (port)); + return _v; +} + +/* Read register OFFSET. + This command should always be terminated with read_end(). */ +extern inline unsigned char read_nibble(short port, unsigned char offset) +{ + unsigned char retval; + outb(EOC+offset, port + PAR_DATA); + outb(RdAddr+offset, port + PAR_DATA); + inbyte(port + PAR_STATUS); /* Settling time delay */ + retval = inbyte(port + PAR_STATUS); + outb(EOC+offset, port + PAR_DATA); + + return retval; +} + +/* Functions for bulk data read. The interrupt line is always disabled. */ +/* Get a byte using read mode 0, reading data from the control lines. */ +extern inline unsigned char read_byte_mode0(short ioaddr) +{ + unsigned char low_nib; + + outb(Ctrl_LNibRead, ioaddr + PAR_CONTROL); + inbyte(ioaddr + PAR_STATUS); + low_nib = (inbyte(ioaddr + PAR_STATUS) >> 3) & 0x0f; + outb(Ctrl_HNibRead, ioaddr + PAR_CONTROL); + inbyte(ioaddr + PAR_STATUS); /* Settling time delay -- needed! */ + inbyte(ioaddr + PAR_STATUS); /* Settling time delay -- needed! */ + return low_nib | ((inbyte(ioaddr + PAR_STATUS) << 1) & 0xf0); +} + +/* The same as read_byte_mode0(), but does multiple inb()s for stability. */ +extern inline unsigned char read_byte_mode2(short ioaddr) +{ + unsigned char low_nib; + + outb(Ctrl_LNibRead, ioaddr + PAR_CONTROL); + inbyte(ioaddr + PAR_STATUS); + low_nib = (inbyte(ioaddr + PAR_STATUS) >> 3) & 0x0f; + outb(Ctrl_HNibRead, ioaddr + PAR_CONTROL); + inbyte(ioaddr + PAR_STATUS); /* Settling time delay -- needed! */ + return low_nib | ((inbyte(ioaddr + PAR_STATUS) << 1) & 0xf0); +} + +/* Read a byte through the data register. */ +extern inline unsigned char read_byte_mode4(short ioaddr) +{ + unsigned char low_nib; + + outb(RdAddr | MAR, ioaddr + PAR_DATA); + low_nib = (inbyte(ioaddr + PAR_STATUS) >> 3) & 0x0f; + outb(RdAddr | HNib | MAR, ioaddr + PAR_DATA); + return low_nib | ((inbyte(ioaddr + PAR_STATUS) << 1) & 0xf0); +} + +/* Read a byte through the data register, double reading to allow settling. */ +extern inline unsigned char read_byte_mode6(short ioaddr) +{ + unsigned char low_nib; + + outb(RdAddr | MAR, ioaddr + PAR_DATA); + inbyte(ioaddr + PAR_STATUS); + low_nib = (inbyte(ioaddr + PAR_STATUS) >> 3) & 0x0f; + outb(RdAddr | HNib | MAR, ioaddr + PAR_DATA); + inbyte(ioaddr + PAR_STATUS); + return low_nib | ((inbyte(ioaddr + PAR_STATUS) << 1) & 0xf0); +} + +extern inline void +write_reg(short port, unsigned char reg, unsigned char value) +{ + unsigned char outval; + outb(EOC | reg, port + PAR_DATA); + outval = WrAddr | reg; + outb(outval, port + PAR_DATA); + outb(outval, port + PAR_DATA); /* Double write for PS/2. */ + + outval &= 0xf0; + outval |= value; + outb(outval, port + PAR_DATA); + outval &= 0x1f; + outb(outval, port + PAR_DATA); + outb(outval, port + PAR_DATA); + + outb(EOC | outval, port + PAR_DATA); +} + +extern inline void +write_reg_high(short port, unsigned char reg, unsigned char value) +{ + unsigned char outval = EOC | HNib | reg; + + outb(outval, port + PAR_DATA); + outval &= WrAddr | HNib | 0x0f; + outb(outval, port + PAR_DATA); + outb(outval, port + PAR_DATA); /* Double write for PS/2. */ + + outval = WrAddr | HNib | value; + outb(outval, port + PAR_DATA); + outval &= HNib | 0x0f; /* HNib | value */ + outb(outval, port + PAR_DATA); + outb(outval, port + PAR_DATA); + + outb(EOC | HNib | outval, port + PAR_DATA); +} + +/* Write a byte out using nibble mode. The low nibble is written first. */ +extern inline void +write_reg_byte(short port, unsigned char reg, unsigned char value) +{ + unsigned char outval; + outb(EOC | reg, port + PAR_DATA); /* Reset the address register. */ + outval = WrAddr | reg; + outb(outval, port + PAR_DATA); + outb(outval, port + PAR_DATA); /* Double write for PS/2. */ + + outb((outval & 0xf0) | (value & 0x0f), port + PAR_DATA); + outb(value & 0x0f, port + PAR_DATA); + value >>= 4; + outb(value, port + PAR_DATA); + outb(0x10 | value, port + PAR_DATA); + outb(0x10 | value, port + PAR_DATA); + + outb(EOC | value, port + PAR_DATA); /* Reset the address register. */ +} + +/* + * Bulk data writes to the packet buffer. The interrupt line remains enabled. + * The first, faster method uses only the dataport (data modes 0, 2 & 4). + * The second (backup) method uses data and control regs (modes 1, 3 & 5). + * It should only be needed when there is skew between the individual data + * lines. + */ +extern inline void write_byte_mode0(short ioaddr, unsigned char value) +{ + outb(value & 0x0f, ioaddr + PAR_DATA); + outb((value>>4) | 0x10, ioaddr + PAR_DATA); +} + +extern inline void write_byte_mode1(short ioaddr, unsigned char value) +{ + outb(value & 0x0f, ioaddr + PAR_DATA); + outb(Ctrl_IRQEN | Ctrl_LNibWrite, ioaddr + PAR_CONTROL); + outb((value>>4) | 0x10, ioaddr + PAR_DATA); + outb(Ctrl_IRQEN | Ctrl_HNibWrite, ioaddr + PAR_CONTROL); +} + +/* Write 16bit VALUE to the packet buffer: the same as above just doubled. */ +extern inline void write_word_mode0(short ioaddr, unsigned short value) +{ + outb(value & 0x0f, ioaddr + PAR_DATA); + value >>= 4; + outb((value & 0x0f) | 0x10, ioaddr + PAR_DATA); + value >>= 4; + outb(value & 0x0f, ioaddr + PAR_DATA); + value >>= 4; + outb((value & 0x0f) | 0x10, ioaddr + PAR_DATA); +} + +/* EEPROM_Ctrl bits. */ +#define EE_SHIFT_CLK 0x04 /* EEPROM shift clock. */ +#define EE_CS 0x02 /* EEPROM chip select. */ +#define EE_CLK_HIGH 0x12 +#define EE_CLK_LOW 0x16 +#define EE_DATA_WRITE 0x01 /* EEPROM chip data in. */ +#define EE_DATA_READ 0x08 /* EEPROM chip data out. */ + +/* Delay between EEPROM clock transitions. */ +#define eeprom_delay(ticks) \ +do { int _i = 40; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0) + +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD(offset) (((5 << 6) + (offset)) << 17) +#define EE_READ(offset) (((6 << 6) + (offset)) << 17) +#define EE_ERASE(offset) (((7 << 6) + (offset)) << 17) +#define EE_CMD_SIZE 27 /* The command+address+data size. */ diff --git a/linux/src/drivers/net/auto_irq.c b/linux/src/drivers/net/auto_irq.c new file mode 100644 index 00000000..82bc7b1c --- /dev/null +++ b/linux/src/drivers/net/auto_irq.c @@ -0,0 +1,123 @@ +/* auto_irq.c: Auto-configure IRQ lines for linux. */ +/* + Written 1994 by Donald Becker. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This code is a general-purpose IRQ line detector for devices with + jumpered IRQ lines. If you can make the device raise an IRQ (and + that IRQ line isn't already being used), these routines will tell + you what IRQ line it's using -- perfect for those oh-so-cool boot-time + device probes! + + To use this, first call autoirq_setup(timeout). TIMEOUT is how many + 'jiffies' (1/100 sec.) to detect other devices that have active IRQ lines, + and can usually be zero at boot. 'autoirq_setup()' returns the bit + vector of nominally-available IRQ lines (lines may be physically in-use, + but not yet registered to a device). + Next, set up your device to trigger an interrupt. + Finally call autoirq_report(TIMEOUT) to find out which IRQ line was + most recently active. The TIMEOUT should usually be zero, but may + be set to the number of jiffies to wait for a slow device to raise an IRQ. + + The idea of using the setup timeout to filter out bogus IRQs came from + the serial driver. +*/ + + +#ifdef version +static const char *version= +"auto_irq.c:v1.11 Donald Becker (becker@cesdis.gsfc.nasa.gov)"; +#endif + +#include <linux/sched.h> +#include <linux/delay.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <linux/netdevice.h> + +struct device *irq2dev_map[NR_IRQS] = {0, 0, /* ... zeroed */}; + +unsigned long irqs_busy = 0x2147; /* The set of fixed IRQs (keyboard, timer, etc) */ +unsigned long irqs_used = 0x0001; /* The set of fixed IRQs sometimes enabled. */ +unsigned long irqs_reserved = 0x0000; /* An advisory "reserved" table. */ +unsigned long irqs_shared = 0x0000; /* IRQ lines "shared" among conforming cards.*/ + +static volatile unsigned long irq_bitmap; /* The irqs we actually found. */ +static unsigned long irq_handled; /* The irq lines we have a handler on. */ +static volatile int irq_number; /* The latest irq number we actually found. */ + +static void autoirq_probe(int irq, void *dev_id, struct pt_regs * regs) +{ + irq_number = irq; + set_bit(irq, (void *)&irq_bitmap); /* irq_bitmap |= 1 << irq; */ + /* This code used to disable the irq. However, the interrupt stub + * would then re-enable the interrupt with (potentially) disastrous + * consequences + */ + free_irq(irq, dev_id); + return; +} + +int autoirq_setup(int waittime) +{ + int i; + unsigned long timeout = jiffies + waittime; + unsigned long boguscount = (waittime*loops_per_sec) / 100; + + irq_handled = 0; + irq_bitmap = 0; + + for (i = 0; i < 16; i++) { + if (test_bit(i, &irqs_busy) == 0 + && request_irq(i, autoirq_probe, SA_INTERRUPT, "irq probe", NULL) == 0) + set_bit(i, (void *)&irq_handled); /* irq_handled |= 1 << i;*/ + } + /* Update our USED lists. */ + irqs_used |= ~irq_handled; + + /* Hang out at least <waittime> jiffies waiting for bogus IRQ hits. */ + while (timeout > jiffies && --boguscount > 0) + ; + + irq_handled &= ~irq_bitmap; + + irq_number = 0; /* We are interested in new interrupts from now on */ + + return irq_handled; +} + +int autoirq_report(int waittime) +{ + int i; + unsigned long timeout = jiffies+waittime; + unsigned long boguscount = (waittime*loops_per_sec) / 100; + + /* Hang out at least <waittime> jiffies waiting for the IRQ. */ + + while (timeout > jiffies && --boguscount > 0) + if (irq_number) + break; + + irq_handled &= ~irq_bitmap; /* This eliminates the already reset handlers */ + + /* Retract the irq handlers that we installed. */ + for (i = 0; i < 16; i++) { + if (test_bit(i, (void *)&irq_handled)) + free_irq(i, NULL); + } + return irq_number; +} + +/* + * Local variables: + * compile-command: "gcc -DKERNEL -Wall -O6 -fomit-frame-pointer -I/usr/src/linux/net/tcp -c auto_irq.c" + * version-control: t + * kept-new-versions: 5 + * c-indent-level: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/de4x5.c b/linux/src/drivers/net/de4x5.c new file mode 100644 index 00000000..b6364cd6 --- /dev/null +++ b/linux/src/drivers/net/de4x5.c @@ -0,0 +1,5942 @@ +/* de4x5.c: A DIGITAL DC21x4x DECchip and DE425/DE434/DE435/DE450/DE500 + ethernet driver for Linux. + + Copyright 1994, 1995 Digital Equipment Corporation. + + Testing resources for this driver have been made available + in part by NASA Ames Research Center (mjacob@nas.nasa.gov). + + The author may be reached at davies@maniac.ultranet.com. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 675 Mass Ave, Cambridge, MA 02139, USA. + + Originally, this driver was written for the Digital Equipment + Corporation series of EtherWORKS ethernet cards: + + DE425 TP/COAX EISA + DE434 TP PCI + DE435 TP/COAX/AUI PCI + DE450 TP/COAX/AUI PCI + DE500 10/100 PCI Fasternet + + but it will now attempt to support all cards which conform to the + Digital Semiconductor SROM Specification. The driver currently + recognises the following chips: + + DC21040 (no SROM) + DC21041[A] + DC21140[A] + DC21142 + DC21143 + + So far the driver is known to work with the following cards: + + KINGSTON + Linksys + ZNYX342 + SMC8432 + SMC9332 (w/new SROM) + ZNYX31[45] + ZNYX346 10/100 4 port (can act as a 10/100 bridge!) + + The driver has been tested on a relatively busy network using the DE425, + DE434, DE435 and DE500 cards and benchmarked with 'ttcp': it transferred + 16M of data to a DECstation 5000/200 as follows: + + TCP UDP + TX RX TX RX + DE425 1030k 997k 1170k 1128k + DE434 1063k 995k 1170k 1125k + DE435 1063k 995k 1170k 1125k + DE500 1063k 998k 1170k 1125k in 10Mb/s mode + + All values are typical (in kBytes/sec) from a sample of 4 for each + measurement. Their error is +/-20k on a quiet (private) network and also + depend on what load the CPU has. + + ========================================================================= + This driver has been written substantially from scratch, although its + inheritance of style and stack interface from 'ewrk3.c' and in turn from + Donald Becker's 'lance.c' should be obvious. With the module autoload of + every usable DECchip board, I pinched Donald's 'next_module' field to + link my modules together. + + Upto 15 EISA cards can be supported under this driver, limited primarily + by the available IRQ lines. I have checked different configurations of + multiple depca, EtherWORKS 3 cards and de4x5 cards and have not found a + problem yet (provided you have at least depca.c v0.38) ... + + PCI support has been added to allow the driver to work with the DE434, + DE435, DE450 and DE500 cards. The I/O accesses are a bit of a kludge due + to the differences in the EISA and PCI CSR address offsets from the base + address. + + The ability to load this driver as a loadable module has been included + and used extensively during the driver development (to save those long + reboot sequences). Loadable module support under PCI and EISA has been + achieved by letting the driver autoprobe as if it were compiled into the + kernel. Do make sure you're not sharing interrupts with anything that + cannot accommodate interrupt sharing! + + To utilise this ability, you have to do 8 things: + + 0) have a copy of the loadable modules code installed on your system. + 1) copy de4x5.c from the /linux/drivers/net directory to your favourite + temporary directory. + 2) for fixed autoprobes (not recommended), edit the source code near + line 5594 to reflect the I/O address you're using, or assign these when + loading by: + + insmod de4x5 io=0xghh where g = bus number + hh = device number + + NB: autoprobing for modules is now supported by default. You may just + use: + + insmod de4x5 + + to load all available boards. For a specific board, still use + the 'io=?' above. + 3) compile de4x5.c, but include -DMODULE in the command line to ensure + that the correct bits are compiled (see end of source code). + 4) if you are wanting to add a new card, goto 5. Otherwise, recompile a + kernel with the de4x5 configuration turned off and reboot. + 5) insmod de4x5 [io=0xghh] + 6) run the net startup bits for your new eth?? interface(s) manually + (usually /etc/rc.inet[12] at boot time). + 7) enjoy! + + To unload a module, turn off the associated interface(s) + 'ifconfig eth?? down' then 'rmmod de4x5'. + + Automedia detection is included so that in principal you can disconnect + from, e.g. TP, reconnect to BNC and things will still work (after a + pause whilst the driver figures out where its media went). My tests + using ping showed that it appears to work.... + + By default, the driver will now autodetect any DECchip based card. + Should you have a need to restrict the driver to DIGITAL only cards, you + can compile with a DEC_ONLY define, or if loading as a module, use the + 'dec_only=1' parameter. + + I've changed the timing routines to use the kernel timer and scheduling + functions so that the hangs and other assorted problems that occurred + while autosensing the media should be gone. A bonus for the DC21040 + auto media sense algorithm is that it can now use one that is more in + line with the rest (the DC21040 chip doesn't have a hardware timer). + The downside is the 1 'jiffies' (10ms) resolution. + + IEEE 802.3u MII interface code has been added in anticipation that some + products may use it in the future. + + The SMC9332 card has a non-compliant SROM which needs fixing - I have + patched this driver to detect it because the SROM format used complies + to a previous DEC-STD format. + + I have removed the buffer copies needed for receive on Intels. I cannot + remove them for Alphas since the Tulip hardware only does longword + aligned DMA transfers and the Alphas get alignment traps with non + longword aligned data copies (which makes them really slow). No comment. + + I have added SROM decoding routines to make this driver work with any + card that supports the Digital Semiconductor SROM spec. This will help + all cards running the dc2114x series chips in particular. Cards using + the dc2104x chips should run correctly with the basic driver. I'm in + debt to <mjacob@feral.com> for the testing and feedback that helped get + this feature working. So far we have tested KINGSTON, SMC8432, SMC9332 + (with the latest SROM complying with the SROM spec V3: their first was + broken), ZNYX342 and LinkSys. ZYNX314 (dual 21041 MAC) and ZNYX 315 + (quad 21041 MAC) cards also appear to work despite their incorrectly + wired IRQs. + + I have added a temporary fix for interrupt problems when some SCSI cards + share the same interrupt as the DECchip based cards. The problem occurs + because the SCSI card wants to grab the interrupt as a fast interrupt + (runs the service routine with interrupts turned off) vs. this card + which really needs to run the service routine with interrupts turned on. + This driver will now add the interrupt service routine as a fast + interrupt if it is bounced from the slow interrupt. THIS IS NOT A + RECOMMENDED WAY TO RUN THE DRIVER and has been done for a limited time + until people sort out their compatibility issues and the kernel + interrupt service code is fixed. YOU SHOULD SEPARATE OUT THE FAST + INTERRUPT CARDS FROM THE SLOW INTERRUPT CARDS to ensure that they do not + run on the same interrupt. PCMCIA/CardBus is another can of worms... + + Finally, I think I have really fixed the module loading problem with + more than one DECchip based card. As a side effect, I don't mess with + the device structure any more which means that if more than 1 card in + 2.0.x is installed (4 in 2.1.x), the user will have to edit + linux/drivers/net/Space.c to make room for them. Hence, module loading + is the preferred way to use this driver, since it doesn't have this + limitation. + + Where SROM media detection is used and full duplex is specified in the + SROM, the feature is ignored unless lp->params.fdx is set at compile + time OR during a module load (insmod de4x5 args='eth??:fdx' [see + below]). This is because there is no way to automatically detect full + duplex links except through autonegotiation. When I include the + autonegotiation feature in the SROM autoconf code, this detection will + occur automatically for that case. + + Command line arguments are now allowed, similar to passing arguments + through LILO. This will allow a per adapter board set up of full duplex + and media. The only lexical constraints are: the board name (dev->name) + appears in the list before its parameters. The list of parameters ends + either at the end of the parameter list or with another board name. The + following parameters are allowed: + + fdx for full duplex + autosense to set the media/speed; with the following + sub-parameters: + TP, TP_NW, BNC, AUI, BNC_AUI, 100Mb, 10Mb, AUTO + + Case sensitivity is important for the sub-parameters. They *must* be + upper case. Examples: + + insmod de4x5 args='eth1:fdx autosense=BNC eth0:autosense=100Mb'. + + For a compiled in driver, in linux/drivers/net/CONFIG, place e.g. + DE4X5_OPTS = -DDE4X5_PARM='"eth0:fdx autosense=AUI eth2:autosense=TP"' + + Yes, I know full duplex isn't permissible on BNC or AUI; they're just + examples. By default, full duplex is turned off and AUTO is the default + autosense setting. In reality, I expect only the full duplex option to + be used. Note the use of single quotes in the two examples above and the + lack of commas to separate items. + + TO DO: + ------ + + o check what revision numbers the 21142 and 21143 have + o + + Revision History + ---------------- + + Version Date Description + + 0.1 17-Nov-94 Initial writing. ALPHA code release. + 0.2 13-Jan-95 Added PCI support for DE435's. + 0.21 19-Jan-95 Added auto media detection. + 0.22 10-Feb-95 Fix interrupt handler call <chris@cosy.sbg.ac.at>. + Fix recognition bug reported by <bkm@star.rl.ac.uk>. + Add request/release_region code. + Add loadable modules support for PCI. + Clean up loadable modules support. + 0.23 28-Feb-95 Added DC21041 and DC21140 support. + Fix missed frame counter value and initialisation. + Fixed EISA probe. + 0.24 11-Apr-95 Change delay routine to use <linux/udelay>. + Change TX_BUFFS_AVAIL macro. + Change media autodetection to allow manual setting. + Completed DE500 (DC21140) support. + 0.241 18-Apr-95 Interim release without DE500 Autosense Algorithm. + 0.242 10-May-95 Minor changes. + 0.30 12-Jun-95 Timer fix for DC21140. + Portability changes. + Add ALPHA changes from <jestabro@ant.tay1.dec.com>. + Add DE500 semi automatic autosense. + Add Link Fail interrupt TP failure detection. + Add timer based link change detection. + Plugged a memory leak in de4x5_queue_pkt(). + 0.31 13-Jun-95 Fixed PCI stuff for 1.3.1. + 0.32 26-Jun-95 Added verify_area() calls in de4x5_ioctl() from a + suggestion by <heiko@colossus.escape.de>. + 0.33 8-Aug-95 Add shared interrupt support (not released yet). + 0.331 21-Aug-95 Fix de4x5_open() with fast CPUs. + Fix de4x5_interrupt(). + Fix dc21140_autoconf() mess. + No shared interrupt support. + 0.332 11-Sep-95 Added MII management interface routines. + 0.40 5-Mar-96 Fix setup frame timeout <maartenb@hpkuipc.cern.ch>. + Add kernel timer code (h/w is too flaky). + Add MII based PHY autosense. + Add new multicasting code. + Add new autosense algorithms for media/mode + selection using kernel scheduling/timing. + Re-formatted. + Made changes suggested by <jeff@router.patch.net>: + Change driver to detect all DECchip based cards + with DEC_ONLY restriction a special case. + Changed driver to autoprobe as a module. No irq + checking is done now - assume BIOS is good! + Added SMC9332 detection <manabe@Roy.dsl.tutics.ac.jp> + 0.41 21-Mar-96 Don't check for get_hw_addr checksum unless DEC card + only <niles@axp745gsfc.nasa.gov> + Fix for multiple PCI cards reported by <jos@xos.nl> + Duh, put the SA_SHIRQ flag into request_interrupt(). + Fix SMC ethernet address in enet_det[]. + Print chip name instead of "UNKNOWN" during boot. + 0.42 26-Apr-96 Fix MII write TA bit error. + Fix bug in dc21040 and dc21041 autosense code. + Remove buffer copies on receive for Intels. + Change sk_buff handling during media disconnects to + eliminate DUP packets. + Add dynamic TX thresholding. + Change all chips to use perfect multicast filtering. + Fix alloc_device() bug <jari@markkus2.fimr.fi> + 0.43 21-Jun-96 Fix unconnected media TX retry bug. + Add Accton to the list of broken cards. + Fix TX under-run bug for non DC21140 chips. + Fix boot command probe bug in alloc_device() as + reported by <koen.gadeyne@barco.com> and + <orava@nether.tky.hut.fi>. + Add cache locks to prevent a race condition as + reported by <csd@microplex.com> and + <baba@beckman.uiuc.edu>. + Upgraded alloc_device() code. + 0.431 28-Jun-96 Fix potential bug in queue_pkt() from discussion + with <csd@microplex.com> + 0.44 13-Aug-96 Fix RX overflow bug in 2114[023] chips. + Fix EISA probe bugs reported by <os2@kpi.kharkov.ua> + and <michael@compurex.com>. + 0.441 9-Sep-96 Change dc21041_autoconf() to probe quiet BNC media + with a loopback packet. + 0.442 9-Sep-96 Include AUI in dc21041 media printout. Bug reported + by <bhat@mundook.cs.mu.OZ.AU> + 0.45 8-Dec-96 Include endian functions for PPC use, from work + by <cort@cs.nmt.edu> and <g.thomas@opengroup.org>. + 0.451 28-Dec-96 Added fix to allow autoprobe for modules after + suggestion from <mjacob@feral.com>. + 0.5 30-Jan-97 Added SROM decoding functions. + Updated debug flags. + Fix sleep/wakeup calls for PCI cards, bug reported + by <cross@gweep.lkg.dec.com>. + Added multi-MAC, one SROM feature from discussion + with <mjacob@feral.com>. + Added full module autoprobe capability. + Added attempt to use an SMC9332 with broken SROM. + Added fix for ZYNX multi-mac cards that didn't + get their IRQs wired correctly. + 0.51 13-Feb-97 Added endian fixes for the SROM accesses from + <paubert@iram.es> + Fix init_connection() to remove extra device reset. + Fix MAC/PHY reset ordering in dc21140m_autoconf(). + Fix initialisation problem with lp->timeout in + typeX_infoblock() from <paubert@iram.es>. + Fix MII PHY reset problem from work done by + <paubert@iram.es>. + 0.52 26-Apr-97 Some changes may not credit the right people - + a disk crash meant I lost some mail. + Change RX interrupt routine to drop rather than + defer packets to avoid hang reported by + <g.thomas@opengroup.org>. + Fix srom_exec() to return for COMPACT and type 1 + infoblocks. + Added DC21142 and DC21143 functions. + Added byte counters from <phil@tazenda.demon.co.uk> + Added SA_INTERRUPT temporary fix from + <mjacob@feral.com>. + 0.53 12-Nov-97 Fix the *_probe() to include 'eth??' name during + module load: bug reported by + <Piete.Brooks@cl.cam.ac.uk> + Fix multi-MAC, one SROM, to work with 2114x chips: + bug reported by <cmetz@inner.net>. + Make above search independent of BIOS device scan + direction. + Completed DC2114[23] autosense functions. + 0.531 21-Dec-97 Fix DE500-XA 100Mb/s bug reported by + <robin@intercore.com + Fix type1_infoblock() bug introduced in 0.53, from + problem reports by + <parmee@postecss.ncrfran.france.ncr.com> and + <jo@ice.dillingen.baynet.de>. + Added argument list to set up each board from either + a module's command line or a compiled in #define. + Added generic MII PHY functionality to deal with + newer PHY chips. + Fix the mess in 2.1.67. + 0.532 5-Jan-98 Fix bug in mii_get_phy() reported by + <redhat@cococo.net>. + Fix bug in pci_probe() for 64 bit systems reported + by <belliott@accessone.com>. + 0.533 9-Jan-98 Fix more 64 bit bugs reported by <jal@cs.brown.edu>. + 0.534 24-Jan-98 Fix last (?) endian bug from + <Geert.Uytterhoeven@cs.kuleuven.ac.be> + 0.535 21-Feb-98 Fix Ethernet Address PROM reset bug for DC21040. + 0.5351 4-Oct-98 Atomicize assertion of dev->interrupt for SMP (not + for Alpha arch.) from <lma@varesearch.com> + Add TP, AUI and BNC cases to 21140m_autoconf() for + case where a 21140 under SROM control uses, e.g. AUI + from problem report by <delchini@lpnp09.in2p3.fr> + Add MII parallel detection to 2114x_autoconf() for + case where no autonegotiation partner exists from + problem report by <mlapsley@ndirect.co.uk>. + Add ability to force connection type directly even + when using SROM control from problem report by + <earl@exis.net>. + Fix is_anc_capable() bug reported by + <Austin.Donnelly@cl.cam.ac.uk>. + Fix type[13]_infoblock() bug: during MII search, PHY + lp->rst not run because lp->ibn not initialised - + from report & fix by <paubert@iram.es>. + Fix probe bug with EISA & PCI cards present from + report by <eirik@netcom.com>. + Fix compiler problems associated with i386-string + ops from multiple bug reports and temporary fix + from <paubert@iram.es>. + Add an_exception() for old ZYNX346 and fix compile + warning on PPC & SPARC, from <ecd@skynet.be>. + Fix lastPCI to correctly work with compiled in + kernels and modules from bug report by + <Zlatko.Calusic@CARNet.hr> et al. + Fix dc2114x_autoconf() to stop multiple messages + when media is unconnected. + Change dev->interrupt to lp->interrupt to ensure + alignment for Alpha's and avoid their unaligned + access traps. This flag is merely for log messages: + should do something more definitive though... + + ========================================================================= +*/ + +static const char *version = "de4x5.c:V0.5351 1998/10/4 davies@maniac.ultranet.com\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> +#include <asm/unaligned.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unistd.h> +#include <linux/ctype.h> + +#include "de4x5.h" + +#define c_char const char + +#include <linux/version.h> +#if LINUX_VERSION_CODE < LinuxVersionCode(2,1,0) +# define __initfunc(__arginit) __arginit +# define test_and_set_bit set_bit +# define net_device_stats enet_statistics +# define copy_to_user(a,b,c) memcpy_tofs(a,b,c) +# define copy_from_user(a,b,c) memcpy_fromfs(a,b,c) +# define le16_to_cpu(a) cpu_to_le16(a) +# define le32_to_cpu(a) cpu_to_le32(a) +# ifdef __powerpc__ +# define cpu_to_le16(a) ((((a) & 0x00ffU) << 8) | (((a) & 0xff00U) >> 8)) +# define cpu_to_le32(a) ((((a) & 0x000000ffU) << 24) |\ + (((a) & 0x0000ff00U) << 8) |\ + (((a) & 0x00ff0000U) >> 8) |\ + (((a) & 0xff000000U) >> 24)) +# else +# define cpu_to_le16(a) (a) +# define cpu_to_le32(a) (a) +# endif /* __powerpc__ */ +# include <asm/segment.h> +#else +# include <asm/uaccess.h> +# include <linux/init.h> +#endif /* LINUX_VERSION_CODE */ +#define TWIDDLE(a) (u_short)le16_to_cpu(get_unaligned((u_short *)(a))) + +/* +** MII Information +*/ +struct phy_table { + int reset; /* Hard reset required? */ + int id; /* IEEE OUI */ + int ta; /* One cycle TA time - 802.3u is confusing here */ + struct { /* Non autonegotiation (parallel) speed det. */ + int reg; + int mask; + int value; + } spd; +}; + +struct mii_phy { + int reset; /* Hard reset required? */ + int id; /* IEEE OUI */ + int ta; /* One cycle TA time */ + struct { /* Non autonegotiation (parallel) speed det. */ + int reg; + int mask; + int value; + } spd; + int addr; /* MII address for the PHY */ + u_char *gep; /* Start of GEP sequence block in SROM */ + u_char *rst; /* Start of reset sequence in SROM */ + u_int mc; /* Media Capabilities */ + u_int ana; /* NWay Advertisement */ + u_int fdx; /* Full DupleX capabilites for each media */ + u_int ttm; /* Transmit Threshold Mode for each media */ + u_int mci; /* 21142 MII Connector Interrupt info */ +}; + +#define DE4X5_MAX_PHY 8 /* Allow upto 8 attached PHY devices per board */ + +struct sia_phy { + u_char mc; /* Media Code */ + u_char ext; /* csr13-15 valid when set */ + int csr13; /* SIA Connectivity Register */ + int csr14; /* SIA TX/RX Register */ + int csr15; /* SIA General Register */ + int gepc; /* SIA GEP Control Information */ + int gep; /* SIA GEP Data */ +}; + +/* +** Define the know universe of PHY devices that can be +** recognised by this driver. +*/ +static struct phy_table phy_info[] = { + {0, NATIONAL_TX, 1, {0x19, 0x40, 0x00}}, /* National TX */ + {1, BROADCOM_T4, 1, {0x10, 0x02, 0x02}}, /* Broadcom T4 */ + {0, SEEQ_T4 , 1, {0x12, 0x10, 0x10}}, /* SEEQ T4 */ + {0, CYPRESS_T4 , 1, {0x05, 0x20, 0x20}}, /* Cypress T4 */ + {0, 0x7810 , 1, {0x05, 0x0380, 0x0380}} /* Level One? */ +}; + +/* +** These GENERIC values assumes that the PHY devices follow 802.3u and +** allow parallel detection to set the link partner ability register. +** Detection of 100Base-TX [H/F Duplex] and 100Base-T4 is supported. +*/ +#define GENERIC_REG 0x05 /* Autoneg. Link Partner Advertisement Reg. */ +#define GENERIC_MASK MII_ANLPA_100M /* All 100Mb/s Technologies */ +#define GENERIC_VALUE MII_ANLPA_100M /* 100B-TX, 100B-TX FDX, 100B-T4 */ + +/* +** Define special SROM detection cases +*/ +static c_char enet_det[][ETH_ALEN] = { + {0x00, 0x00, 0xc0, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0xe8, 0x00, 0x00, 0x00} +}; + +#define SMC 1 +#define ACCTON 2 + +/* +** SROM Repair definitions. If a broken SROM is detected a card may +** use this information to help figure out what to do. This is a +** "stab in the dark" and so far for SMC9332's only. +*/ +static c_char srom_repair_info[][100] = { + {0x00,0x1e,0x00,0x00,0x00,0x08, /* SMC9332 */ + 0x1f,0x01,0x8f,0x01,0x00,0x01,0x00,0x02, + 0x01,0x00,0x00,0x78,0xe0,0x01,0x00,0x50, + 0x00,0x18,} +}; + + +#ifdef DE4X5_DEBUG +static int de4x5_debug = DE4X5_DEBUG; +#else +/*static int de4x5_debug = (DEBUG_MII | DEBUG_SROM | DEBUG_PCICFG | DEBUG_MEDIA | DEBUG_VERSION);*/ +static int de4x5_debug = (DEBUG_MEDIA | DEBUG_VERSION); +#endif + +/* +** Allow per adapter set up. For modules this is simply a command line +** parameter, e.g.: +** insmod de4x5 args='eth1:fdx autosense=BNC eth0:autosense=100Mb'. +** +** For a compiled in driver, place e.g. +** DE4X5_OPTS = -DDE4X5_PARM='"eth0:fdx autosense=AUI eth2:autosense=TP"' +** in linux/drivers/net/CONFIG +*/ +#ifdef DE4X5_PARM +static char *args = DE4X5_PARM; +#else +static char *args = NULL; +#endif + +struct parameters { + int fdx; + int autosense; +}; + +#define DE4X5_AUTOSENSE_MS 250 /* msec autosense tick (DE500) */ + +#define DE4X5_NDA 0xffe0 /* No Device (I/O) Address */ + +/* +** Ethernet PROM defines +*/ +#define PROBE_LENGTH 32 +#define ETH_PROM_SIG 0xAA5500FFUL + +/* +** Ethernet Info +*/ +#define PKT_BUF_SZ 1536 /* Buffer size for each Tx/Rx buffer */ +#define IEEE802_3_SZ 1518 /* Packet + CRC */ +#define MAX_PKT_SZ 1514 /* Maximum ethernet packet length */ +#define MAX_DAT_SZ 1500 /* Maximum ethernet data length */ +#define MIN_DAT_SZ 1 /* Minimum ethernet data length */ +#define PKT_HDR_LEN 14 /* Addresses and data length info */ +#define FAKE_FRAME_LEN (MAX_PKT_SZ + 1) +#define QUEUE_PKT_TIMEOUT (3*HZ) /* 3 second timeout */ + + +#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */ +#define CRC_POLYNOMIAL_LE 0xedb88320UL /* Ethernet CRC, little endian */ + +/* +** EISA bus defines +*/ +#define DE4X5_EISA_IO_PORTS 0x0c00 /* I/O port base address, slot 0 */ +#define DE4X5_EISA_TOTAL_SIZE 0x100 /* I/O address extent */ + +#define MAX_EISA_SLOTS 16 +#define EISA_SLOT_INC 0x1000 +#define EISA_ALLOWED_IRQ_LIST {5, 9, 10, 11} + +#define DE4X5_SIGNATURE {"DE425","DE434","DE435","DE450","DE500"} +#define DE4X5_NAME_LENGTH 8 + +/* +** Ethernet PROM defines for DC21040 +*/ +#define PROBE_LENGTH 32 +#define ETH_PROM_SIG 0xAA5500FFUL + +/* +** PCI Bus defines +*/ +#define PCI_MAX_BUS_NUM 8 +#define DE4X5_PCI_TOTAL_SIZE 0x80 /* I/O address extent */ +#define DE4X5_CLASS_CODE 0x00020000 /* Network controller, Ethernet */ +#define NO_MORE_PCI -2 /* PCI bus search all done */ + +/* +** Memory Alignment. Each descriptor is 4 longwords long. To force a +** particular alignment on the TX descriptor, adjust DESC_SKIP_LEN and +** DESC_ALIGN. ALIGN aligns the start address of the private memory area +** and hence the RX descriptor ring's first entry. +*/ +#define ALIGN4 ((u_long)4 - 1) /* 1 longword align */ +#define ALIGN8 ((u_long)8 - 1) /* 2 longword align */ +#define ALIGN16 ((u_long)16 - 1) /* 4 longword align */ +#define ALIGN32 ((u_long)32 - 1) /* 8 longword align */ +#define ALIGN64 ((u_long)64 - 1) /* 16 longword align */ +#define ALIGN128 ((u_long)128 - 1) /* 32 longword align */ + +#define ALIGN ALIGN32 /* Keep the DC21040 happy... */ +#define CACHE_ALIGN CAL_16LONG +#define DESC_SKIP_LEN DSL_0 /* Must agree with DESC_ALIGN */ +/*#define DESC_ALIGN u32 dummy[4]; / * Must agree with DESC_SKIP_LEN */ +#define DESC_ALIGN + +#ifndef DEC_ONLY /* See README.de4x5 for using this */ +static int dec_only = 0; +#else +static int dec_only = 1; +#endif + +/* +** DE4X5 IRQ ENABLE/DISABLE +*/ +#define ENABLE_IRQs { \ + imr |= lp->irq_en;\ + outl(imr, DE4X5_IMR); /* Enable the IRQs */\ +} + +#define DISABLE_IRQs {\ + imr = inl(DE4X5_IMR);\ + imr &= ~lp->irq_en;\ + outl(imr, DE4X5_IMR); /* Disable the IRQs */\ +} + +#define UNMASK_IRQs {\ + imr |= lp->irq_mask;\ + outl(imr, DE4X5_IMR); /* Unmask the IRQs */\ +} + +#define MASK_IRQs {\ + imr = inl(DE4X5_IMR);\ + imr &= ~lp->irq_mask;\ + outl(imr, DE4X5_IMR); /* Mask the IRQs */\ +} + +/* +** DE4X5 START/STOP +*/ +#define START_DE4X5 {\ + omr = inl(DE4X5_OMR);\ + omr |= OMR_ST | OMR_SR;\ + outl(omr, DE4X5_OMR); /* Enable the TX and/or RX */\ +} + +#define STOP_DE4X5 {\ + omr = inl(DE4X5_OMR);\ + omr &= ~(OMR_ST|OMR_SR);\ + outl(omr, DE4X5_OMR); /* Disable the TX and/or RX */ \ +} + +/* +** DE4X5 SIA RESET +*/ +#define RESET_SIA outl(0, DE4X5_SICR); /* Reset SIA connectivity regs */ + +/* +** DE500 AUTOSENSE TIMER INTERVAL (MILLISECS) +*/ +#define DE4X5_AUTOSENSE_MS 250 + +/* +** SROM Structure +*/ +struct de4x5_srom { + char sub_vendor_id[2]; + char sub_system_id[2]; + char reserved[12]; + char id_block_crc; + char reserved2; + char version; + char num_controllers; + char ieee_addr[6]; + char info[100]; + short chksum; +}; +#define SUB_VENDOR_ID 0x500a + +/* +** DE4X5 Descriptors. Make sure that all the RX buffers are contiguous +** and have sizes of both a power of 2 and a multiple of 4. +** A size of 256 bytes for each buffer could be chosen because over 90% of +** all packets in our network are <256 bytes long and 64 longword alignment +** is possible. 1536 showed better 'ttcp' performance. Take your pick. 32 TX +** descriptors are needed for machines with an ALPHA CPU. +*/ +#define NUM_RX_DESC 8 /* Number of RX descriptors */ +#define NUM_TX_DESC 32 /* Number of TX descriptors */ +#define RX_BUFF_SZ 1536 /* Power of 2 for kmalloc and */ + /* Multiple of 4 for DC21040 */ + /* Allows 512 byte alignment */ +struct de4x5_desc { + volatile s32 status; + u32 des1; + u32 buf; + u32 next; + DESC_ALIGN +}; + +/* +** The DE4X5 private structure +*/ +#define DE4X5_PKT_STAT_SZ 16 +#define DE4X5_PKT_BIN_SZ 128 /* Should be >=100 unless you + increase DE4X5_PKT_STAT_SZ */ + +struct de4x5_private { + char adapter_name[80]; /* Adapter name */ + u_long interrupt; /* Aligned ISR flag */ + struct de4x5_desc rx_ring[NUM_RX_DESC]; /* RX descriptor ring */ + struct de4x5_desc tx_ring[NUM_TX_DESC]; /* TX descriptor ring */ + struct sk_buff *tx_skb[NUM_TX_DESC]; /* TX skb for freeing when sent */ + struct sk_buff *rx_skb[NUM_RX_DESC]; /* RX skb's */ + int rx_new, rx_old; /* RX descriptor ring pointers */ + int tx_new, tx_old; /* TX descriptor ring pointers */ + char setup_frame[SETUP_FRAME_LEN]; /* Holds MCA and PA info. */ + char frame[64]; /* Min sized packet for loopback*/ + struct net_device_stats stats; /* Public stats */ + struct { + u_int bins[DE4X5_PKT_STAT_SZ]; /* Private stats counters */ + u_int unicast; + u_int multicast; + u_int broadcast; + u_int excessive_collisions; + u_int tx_underruns; + u_int excessive_underruns; + u_int rx_runt_frames; + u_int rx_collision; + u_int rx_dribble; + u_int rx_overflow; + } pktStats; + char rxRingSize; + char txRingSize; + int bus; /* EISA or PCI */ + int bus_num; /* PCI Bus number */ + int device; /* Device number on PCI bus */ + int state; /* Adapter OPENED or CLOSED */ + int chipset; /* DC21040, DC21041 or DC21140 */ + s32 irq_mask; /* Interrupt Mask (Enable) bits */ + s32 irq_en; /* Summary interrupt bits */ + int media; /* Media (eg TP), mode (eg 100B)*/ + int c_media; /* Remember the last media conn */ + int fdx; /* media full duplex flag */ + int linkOK; /* Link is OK */ + int autosense; /* Allow/disallow autosensing */ + int tx_enable; /* Enable descriptor polling */ + int setup_f; /* Setup frame filtering type */ + int local_state; /* State within a 'media' state */ + struct mii_phy phy[DE4X5_MAX_PHY]; /* List of attached PHY devices */ + struct sia_phy sia; /* SIA PHY Information */ + int active; /* Index to active PHY device */ + int mii_cnt; /* Number of attached PHY's */ + int timeout; /* Scheduling counter */ + struct timer_list timer; /* Timer info for kernel */ + int tmp; /* Temporary global per card */ + struct { + void *priv; /* Original kmalloc'd mem addr */ + void *buf; /* Original kmalloc'd mem addr */ + u_long lock; /* Lock the cache accesses */ + s32 csr0; /* Saved Bus Mode Register */ + s32 csr6; /* Saved Operating Mode Reg. */ + s32 csr7; /* Saved IRQ Mask Register */ + s32 gep; /* Saved General Purpose Reg. */ + s32 gepc; /* Control info for GEP */ + s32 csr13; /* Saved SIA Connectivity Reg. */ + s32 csr14; /* Saved SIA TX/RX Register */ + s32 csr15; /* Saved SIA General Register */ + int save_cnt; /* Flag if state already saved */ + struct sk_buff *skb; /* Save the (re-ordered) skb's */ + } cache; + struct de4x5_srom srom; /* A copy of the SROM */ + struct device *next_module; /* Link to the next module */ + int rx_ovf; /* Check for 'RX overflow' tag */ + int useSROM; /* For non-DEC card use SROM */ + int useMII; /* Infoblock using the MII */ + int asBitValid; /* Autosense bits in GEP? */ + int asPolarity; /* 0 => asserted high */ + int asBit; /* Autosense bit number in GEP */ + int defMedium; /* SROM default medium */ + int tcount; /* Last infoblock number */ + int infoblock_init; /* Initialised this infoblock? */ + int infoleaf_offset; /* SROM infoleaf for controller */ + s32 infoblock_csr6; /* csr6 value in SROM infoblock */ + int infoblock_media; /* infoblock media */ + int (*infoleaf_fn)(struct device *); /* Pointer to infoleaf function */ + u_char *rst; /* Pointer to Type 5 reset info */ + u_char ibn; /* Infoblock number */ + struct parameters params; /* Command line/ #defined params */ +}; + +/* +** Kludge to get around the fact that the CSR addresses have different +** offsets in the PCI and EISA boards. Also note that the ethernet address +** PROM is accessed differently. +*/ +static struct bus_type { + int bus; + int bus_num; + int device; + int chipset; + struct de4x5_srom srom; + int autosense; + int useSROM; +} bus; + +/* +** To get around certain poxy cards that don't provide an SROM +** for the second and more DECchip, I have to key off the first +** chip's address. I'll assume there's not a bad SROM iff: +** +** o the chipset is the same +** o the bus number is the same and > 0 +** o the sum of all the returned hw address bytes is 0 or 0x5fa +** +** Also have to save the irq for those cards whose hardware designers +** can't follow the PCI to PCI Bridge Architecture spec. +*/ +static struct { + int chipset; + int bus; + int irq; + u_char addr[ETH_ALEN]; +} last = {0,}; + +/* +** The transmit ring full condition is described by the tx_old and tx_new +** pointers by: +** tx_old = tx_new Empty ring +** tx_old = tx_new+1 Full ring +** tx_old+txRingSize = tx_new+1 Full ring (wrapped condition) +*/ +#define TX_BUFFS_AVAIL ((lp->tx_old<=lp->tx_new)?\ + lp->tx_old+lp->txRingSize-lp->tx_new-1:\ + lp->tx_old -lp->tx_new-1) + +#define TX_PKT_PENDING (lp->tx_old != lp->tx_new) + +/* +** Public Functions +*/ +static int de4x5_open(struct device *dev); +static int de4x5_queue_pkt(struct sk_buff *skb, struct device *dev); +static void de4x5_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int de4x5_close(struct device *dev); +static struct net_device_stats *de4x5_get_stats(struct device *dev); +static void de4x5_local_stats(struct device *dev, char *buf, int pkt_len); +static void set_multicast_list(struct device *dev); +static int de4x5_ioctl(struct device *dev, struct ifreq *rq, int cmd); + +/* +** Private functions +*/ +static int de4x5_hw_init(struct device *dev, u_long iobase); +static int de4x5_init(struct device *dev); +static int de4x5_sw_reset(struct device *dev); +static int de4x5_rx(struct device *dev); +static int de4x5_tx(struct device *dev); +static int de4x5_ast(struct device *dev); +static int de4x5_txur(struct device *dev); +static int de4x5_rx_ovfc(struct device *dev); + +static int autoconf_media(struct device *dev); +static void create_packet(struct device *dev, char *frame, int len); +static void de4x5_us_delay(u32 usec); +static void de4x5_ms_delay(u32 msec); +static void load_packet(struct device *dev, char *buf, u32 flags, struct sk_buff *skb); +static int dc21040_autoconf(struct device *dev); +static int dc21041_autoconf(struct device *dev); +static int dc21140m_autoconf(struct device *dev); +static int dc2114x_autoconf(struct device *dev); +static int srom_autoconf(struct device *dev); +static int de4x5_suspect_state(struct device *dev, int timeout, int prev_state, int (*fn)(struct device *, int), int (*asfn)(struct device *)); +static int dc21040_state(struct device *dev, int csr13, int csr14, int csr15, int timeout, int next_state, int suspect_state, int (*fn)(struct device *, int)); +static int test_media(struct device *dev, s32 irqs, s32 irq_mask, s32 csr13, s32 csr14, s32 csr15, s32 msec); +static int test_for_100Mb(struct device *dev, int msec); +static int wait_for_link(struct device *dev); +static int test_mii_reg(struct device *dev, int reg, int mask, int pol, long msec); +static int is_spd_100(struct device *dev); +static int is_100_up(struct device *dev); +static int is_10_up(struct device *dev); +static int is_anc_capable(struct device *dev); +static int ping_media(struct device *dev, int msec); +static struct sk_buff *de4x5_alloc_rx_buff(struct device *dev, int index, int len); +static void de4x5_free_rx_buffs(struct device *dev); +static void de4x5_free_tx_buffs(struct device *dev); +static void de4x5_save_skbs(struct device *dev); +static void de4x5_rst_desc_ring(struct device *dev); +static void de4x5_cache_state(struct device *dev, int flag); +static void de4x5_put_cache(struct device *dev, struct sk_buff *skb); +static void de4x5_putb_cache(struct device *dev, struct sk_buff *skb); +static struct sk_buff *de4x5_get_cache(struct device *dev); +static void de4x5_setup_intr(struct device *dev); +static void de4x5_init_connection(struct device *dev); +static int de4x5_reset_phy(struct device *dev); +static void reset_init_sia(struct device *dev, s32 sicr, s32 strr, s32 sigr); +static int test_ans(struct device *dev, s32 irqs, s32 irq_mask, s32 msec); +static int test_tp(struct device *dev, s32 msec); +static int EISA_signature(char *name, s32 eisa_id); +static int PCI_signature(char *name, struct bus_type *lp); +static void DevicePresent(u_long iobase); +static void enet_addr_rst(u_long aprom_addr); +static int de4x5_bad_srom(struct bus_type *lp); +static short srom_rd(u_long address, u_char offset); +static void srom_latch(u_int command, u_long address); +static void srom_command(u_int command, u_long address); +static void srom_address(u_int command, u_long address, u_char offset); +static short srom_data(u_int command, u_long address); +/*static void srom_busy(u_int command, u_long address);*/ +static void sendto_srom(u_int command, u_long addr); +static int getfrom_srom(u_long addr); +static int srom_map_media(struct device *dev); +static int srom_infoleaf_info(struct device *dev); +static void srom_init(struct device *dev); +static void srom_exec(struct device *dev, u_char *p); +static int mii_rd(u_char phyreg, u_char phyaddr, u_long ioaddr); +static void mii_wr(int data, u_char phyreg, u_char phyaddr, u_long ioaddr); +static int mii_rdata(u_long ioaddr); +static void mii_wdata(int data, int len, u_long ioaddr); +static void mii_ta(u_long rw, u_long ioaddr); +static int mii_swap(int data, int len); +static void mii_address(u_char addr, u_long ioaddr); +static void sendto_mii(u32 command, int data, u_long ioaddr); +static int getfrom_mii(u32 command, u_long ioaddr); +static int mii_get_oui(u_char phyaddr, u_long ioaddr); +static int mii_get_phy(struct device *dev); +static void SetMulticastFilter(struct device *dev); +static int get_hw_addr(struct device *dev); +static void srom_repair(struct device *dev, int card); +static int test_bad_enet(struct device *dev, int status); +static int an_exception(struct bus_type *lp); +#if !defined(__sparc_v9__) && !defined(__powerpc__) && !defined(__alpha__) +static void eisa_probe(struct device *dev, u_long iobase); +#endif +static void pci_probe(struct device *dev, u_long iobase); +static void srom_search(int index); +static char *build_setup_frame(struct device *dev, int mode); +static void disable_ast(struct device *dev); +static void enable_ast(struct device *dev, u32 time_out); +static long de4x5_switch_mac_port(struct device *dev); +static int gep_rd(struct device *dev); +static void gep_wr(s32 data, struct device *dev); +static void timeout(struct device *dev, void (*fn)(u_long data), u_long data, u_long msec); +static void yawn(struct device *dev, int state); +static void link_modules(struct device *dev, struct device *tmp); +static void de4x5_parse_params(struct device *dev); +static void de4x5_dbg_open(struct device *dev); +static void de4x5_dbg_mii(struct device *dev, int k); +static void de4x5_dbg_media(struct device *dev); +static void de4x5_dbg_srom(struct de4x5_srom *p); +static void de4x5_dbg_rx(struct sk_buff *skb, int len); +static int de4x5_strncmp(char *a, char *b, int n); +static int dc21041_infoleaf(struct device *dev); +static int dc21140_infoleaf(struct device *dev); +static int dc21142_infoleaf(struct device *dev); +static int dc21143_infoleaf(struct device *dev); +static int type0_infoblock(struct device *dev, u_char count, u_char *p); +static int type1_infoblock(struct device *dev, u_char count, u_char *p); +static int type2_infoblock(struct device *dev, u_char count, u_char *p); +static int type3_infoblock(struct device *dev, u_char count, u_char *p); +static int type4_infoblock(struct device *dev, u_char count, u_char *p); +static int type5_infoblock(struct device *dev, u_char count, u_char *p); +static int compact_infoblock(struct device *dev, u_char count, u_char *p); + +#ifdef MODULE +int init_module(void); +void cleanup_module(void); +static struct device *unlink_modules(struct device *p); +static struct device *insert_device(struct device *dev, u_long iobase, + int (*init)(struct device *)); +static int count_adapters(void); +static int loading_module = 1; +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,0) +MODULE_PARM(de4x5_debug, "i"); +MODULE_PARM(dec_only, "i"); +MODULE_PARM(args, "s"); +#endif /* LINUX_VERSION_CODE */ +# else +static int loading_module = 0; +#endif /* MODULE */ + +static char name[DE4X5_NAME_LENGTH + 1]; +#if !defined(__sparc_v9__) && !defined(__powerpc__) && !defined(__alpha__) +static u_char de4x5_irq[] = EISA_ALLOWED_IRQ_LIST; +static int lastEISA = 0; +#else +static int lastEISA = MAX_EISA_SLOTS; /* Only PCI probes */ +#endif +static int num_de4x5s = 0; +static int cfrv = 0, useSROM = 0; +static int lastPCI = -1; +static struct device *lastModule = NULL; + +/* +** List the SROM infoleaf functions and chipsets +*/ +struct InfoLeaf { + int chipset; + int (*fn)(struct device *); +}; +static struct InfoLeaf infoleaf_array[] = { + {DC21041, dc21041_infoleaf}, + {DC21140, dc21140_infoleaf}, + {DC21142, dc21142_infoleaf}, + {DC21143, dc21143_infoleaf} +}; +#define INFOLEAF_SIZE (sizeof(infoleaf_array)/(sizeof(int)+sizeof(int *))) + +/* +** List the SROM info block functions +*/ +static int (*dc_infoblock[])(struct device *dev, u_char, u_char *) = { + type0_infoblock, + type1_infoblock, + type2_infoblock, + type3_infoblock, + type4_infoblock, + type5_infoblock, + compact_infoblock +}; + +#define COMPACT (sizeof(dc_infoblock)/sizeof(int *) - 1) + +/* +** Miscellaneous defines... +*/ +#define RESET_DE4X5 {\ + int i;\ + i=inl(DE4X5_BMR);\ + de4x5_ms_delay(1);\ + outl(i | BMR_SWR, DE4X5_BMR);\ + de4x5_ms_delay(1);\ + outl(i, DE4X5_BMR);\ + de4x5_ms_delay(1);\ + for (i=0;i<5;i++) {inl(DE4X5_BMR); de4x5_ms_delay(1);}\ + de4x5_ms_delay(1);\ +} + +#define PHY_HARD_RESET {\ + outl(GEP_HRST, DE4X5_GEP); /* Hard RESET the PHY dev. */\ + udelay(1000); /* Assert for 1ms */\ + outl(0x00, DE4X5_GEP);\ + udelay(2000); /* Wait for 2ms */\ +} + + +/* +** Autoprobing in modules is allowed here. See the top of the file for +** more info. +*/ +__initfunc(int +de4x5_probe(struct device *dev)) +{ + u_long iobase = dev->base_addr; + +#if !defined(__sparc_v9__) && !defined(__powerpc__) && !defined(__alpha__) + eisa_probe(dev, iobase); +#endif + if (lastEISA == MAX_EISA_SLOTS) { + pci_probe(dev, iobase); + } + + return (dev->priv ? 0 : -ENODEV); +} + +__initfunc(static int +de4x5_hw_init(struct device *dev, u_long iobase)) +{ + struct bus_type *lp = &bus; + int i, status=0; + char *tmp; + + /* Ensure we're not sleeping */ + if (lp->bus == EISA) { + outb(WAKEUP, PCI_CFPM); + } else { + pcibios_write_config_byte(lp->bus_num, lp->device << 3, + PCI_CFDA_PSM, WAKEUP); + } + de4x5_ms_delay(10); + + RESET_DE4X5; + + if ((inl(DE4X5_STS) & (STS_TS | STS_RS)) != 0) { + return -ENXIO; /* Hardware could not reset */ + } + + /* + ** Now find out what kind of DC21040/DC21041/DC21140 board we have. + */ + useSROM = FALSE; + if (lp->bus == PCI) { + PCI_signature(name, lp); + } else { + EISA_signature(name, EISA_ID0); + } + + if (*name == '\0') { /* Not found a board signature */ + return -ENXIO; + } + + dev->base_addr = iobase; + if (lp->bus == EISA) { + printk("%s: %s at 0x%04lx (EISA slot %ld)", + dev->name, name, iobase, ((iobase>>12)&0x0f)); + } else { /* PCI port address */ + printk("%s: %s at 0x%04lx (PCI bus %d, device %d)", dev->name, name, + iobase, lp->bus_num, lp->device); + } + + printk(", h/w address "); + status = get_hw_addr(dev); + for (i = 0; i < ETH_ALEN - 1; i++) { /* get the ethernet addr. */ + printk("%2.2x:", dev->dev_addr[i]); + } + printk("%2.2x,\n", dev->dev_addr[i]); + + if (status != 0) { + printk(" which has an Ethernet PROM CRC error.\n"); + return -ENXIO; + } else { + struct de4x5_private *lp; + + /* + ** Reserve a section of kernel memory for the adapter + ** private area and the TX/RX descriptor rings. + */ + dev->priv = (void *) kmalloc(sizeof(struct de4x5_private) + ALIGN, + GFP_KERNEL); + if (dev->priv == NULL) { + return -ENOMEM; + } + + /* + ** Align to a longword boundary + */ + tmp = dev->priv; + dev->priv = (void *)(((u_long)dev->priv + ALIGN) & ~ALIGN); + lp = (struct de4x5_private *)dev->priv; + memset(dev->priv, 0, sizeof(struct de4x5_private)); + lp->bus = bus.bus; + lp->bus_num = bus.bus_num; + lp->device = bus.device; + lp->chipset = bus.chipset; + lp->cache.priv = tmp; + lp->cache.gepc = GEP_INIT; + lp->asBit = GEP_SLNK; + lp->asPolarity = GEP_SLNK; + lp->asBitValid = TRUE; + lp->timeout = -1; + lp->useSROM = useSROM; + memcpy((char *)&lp->srom,(char *)&bus.srom,sizeof(struct de4x5_srom)); + de4x5_parse_params(dev); + + /* + ** Choose correct autosensing in case someone messed up + */ + lp->autosense = lp->params.autosense; + if (lp->chipset != DC21140) { + if ((lp->chipset==DC21040) && (lp->params.autosense&TP_NW)) { + lp->params.autosense = TP; + } + if ((lp->chipset==DC21041) && (lp->params.autosense&BNC_AUI)) { + lp->params.autosense = BNC; + } + } + lp->fdx = lp->params.fdx; + sprintf(lp->adapter_name,"%s (%s)", name, dev->name); + + /* + ** Set up the RX descriptor ring (Intels) + ** Allocate contiguous receive buffers, long word aligned (Alphas) + */ +#if !defined(__alpha__) && !defined(__powerpc__) && !defined(__sparc_v9__) && !defined(DE4X5_DO_MEMCPY) + for (i=0; i<NUM_RX_DESC; i++) { + lp->rx_ring[i].status = 0; + lp->rx_ring[i].des1 = RX_BUFF_SZ; + lp->rx_ring[i].buf = 0; + lp->rx_ring[i].next = 0; + lp->rx_skb[i] = (struct sk_buff *) 1; /* Dummy entry */ + } + +#else + if ((tmp = (void *)kmalloc(RX_BUFF_SZ * NUM_RX_DESC + ALIGN, + GFP_KERNEL)) == NULL) { + kfree(lp->cache.priv); + return -ENOMEM; + } + + lp->cache.buf = tmp; + tmp = (char *)(((u_long) tmp + ALIGN) & ~ALIGN); + for (i=0; i<NUM_RX_DESC; i++) { + lp->rx_ring[i].status = 0; + lp->rx_ring[i].des1 = cpu_to_le32(RX_BUFF_SZ); + lp->rx_ring[i].buf = cpu_to_le32(virt_to_bus(tmp+i*RX_BUFF_SZ)); + lp->rx_ring[i].next = 0; + lp->rx_skb[i] = (struct sk_buff *) 1; /* Dummy entry */ + } +#endif + + barrier(); + + request_region(iobase, (lp->bus == PCI ? DE4X5_PCI_TOTAL_SIZE : + DE4X5_EISA_TOTAL_SIZE), + lp->adapter_name); + + lp->rxRingSize = NUM_RX_DESC; + lp->txRingSize = NUM_TX_DESC; + + /* Write the end of list marker to the descriptor lists */ + lp->rx_ring[lp->rxRingSize - 1].des1 |= cpu_to_le32(RD_RER); + lp->tx_ring[lp->txRingSize - 1].des1 |= cpu_to_le32(TD_TER); + + /* Tell the adapter where the TX/RX rings are located. */ + outl(virt_to_bus(lp->rx_ring), DE4X5_RRBA); + outl(virt_to_bus(lp->tx_ring), DE4X5_TRBA); + + /* Initialise the IRQ mask and Enable/Disable */ + lp->irq_mask = IMR_RIM | IMR_TIM | IMR_TUM | IMR_UNM; + lp->irq_en = IMR_NIM | IMR_AIM; + + /* Create a loopback packet frame for later media probing */ + create_packet(dev, lp->frame, sizeof(lp->frame)); + + /* Check if the RX overflow bug needs testing for */ + i = cfrv & 0x000000fe; + if ((lp->chipset == DC21140) && (i == 0x20)) { + lp->rx_ovf = 1; + } + + /* Initialise the SROM pointers if possible */ + if (lp->useSROM) { + lp->state = INITIALISED; + if (srom_infoleaf_info(dev)) { + return -ENXIO; + } + srom_init(dev); + } + + lp->state = CLOSED; + + /* + ** Check for an MII interface + */ + if ((lp->chipset != DC21040) && (lp->chipset != DC21041)) { + mii_get_phy(dev); + } + +#ifndef __sparc_v9__ + printk(" and requires IRQ%d (provided by %s).\n", dev->irq, +#else + printk(" and requires IRQ%x (provided by %s).\n", dev->irq, +#endif + ((lp->bus == PCI) ? "PCI BIOS" : "EISA CNFG")); + } + + if (de4x5_debug & DEBUG_VERSION) { + printk(version); + } + + /* The DE4X5-specific entries in the device structure. */ + dev->open = &de4x5_open; + dev->hard_start_xmit = &de4x5_queue_pkt; + dev->stop = &de4x5_close; + dev->get_stats = &de4x5_get_stats; + dev->set_multicast_list = &set_multicast_list; + dev->do_ioctl = &de4x5_ioctl; + + dev->mem_start = 0; + + /* Fill in the generic fields of the device structure. */ + ether_setup(dev); + + /* Let the adapter sleep to save power */ + yawn(dev, SLEEP); + + return status; +} + + +static int +de4x5_open(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int i, status = 0; + s32 omr; + + /* Allocate the RX buffers */ + for (i=0; i<lp->rxRingSize; i++) { + if (de4x5_alloc_rx_buff(dev, i, 0) == NULL) { + de4x5_free_rx_buffs(dev); + return -EAGAIN; + } + } + + /* + ** Wake up the adapter + */ + yawn(dev, WAKEUP); + + /* + ** Re-initialize the DE4X5... + */ + status = de4x5_init(dev); + + lp->state = OPEN; + de4x5_dbg_open(dev); + + if (request_irq(dev->irq, (void *)de4x5_interrupt, SA_SHIRQ, + lp->adapter_name, dev)) { + printk("de4x5_open(): Requested IRQ%d is busy - attemping FAST/SHARE...", dev->irq); + if (request_irq(dev->irq, de4x5_interrupt, SA_INTERRUPT | SA_SHIRQ, + lp->adapter_name, dev)) { + printk("\n Cannot get IRQ- reconfigure your hardware.\n"); + disable_ast(dev); + de4x5_free_rx_buffs(dev); + de4x5_free_tx_buffs(dev); + yawn(dev, SLEEP); + lp->state = CLOSED; + return -EAGAIN; + } else { + printk("\n Succeeded, but you should reconfigure your hardware to avoid this.\n"); + printk("WARNING: there may be IRQ related problems in heavily loaded systems.\n"); + } + } + + dev->tbusy = 0; + dev->start = 1; + lp->interrupt = UNMASK_INTERRUPTS; + dev->trans_start = jiffies; + + START_DE4X5; + + de4x5_setup_intr(dev); + + if (de4x5_debug & DEBUG_OPEN) { + printk("\tsts: 0x%08x\n", inl(DE4X5_STS)); + printk("\tbmr: 0x%08x\n", inl(DE4X5_BMR)); + printk("\timr: 0x%08x\n", inl(DE4X5_IMR)); + printk("\tomr: 0x%08x\n", inl(DE4X5_OMR)); + printk("\tsisr: 0x%08x\n", inl(DE4X5_SISR)); + printk("\tsicr: 0x%08x\n", inl(DE4X5_SICR)); + printk("\tstrr: 0x%08x\n", inl(DE4X5_STRR)); + printk("\tsigr: 0x%08x\n", inl(DE4X5_SIGR)); + } + + MOD_INC_USE_COUNT; + + return status; +} + +/* +** Initialize the DE4X5 operating conditions. NB: a chip problem with the +** DC21140 requires using perfect filtering mode for that chip. Since I can't +** see why I'd want > 14 multicast addresses, I have changed all chips to use +** the perfect filtering mode. Keep the DMA burst length at 8: there seems +** to be data corruption problems if it is larger (UDP errors seen from a +** ttcp source). +*/ +static int +de4x5_init(struct device *dev) +{ + /* Lock out other processes whilst setting up the hardware */ + test_and_set_bit(0, (void *)&dev->tbusy); + + de4x5_sw_reset(dev); + + /* Autoconfigure the connected port */ + autoconf_media(dev); + + return 0; +} + +static int +de4x5_sw_reset(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int i, j, status = 0; + s32 bmr, omr; + + /* Select the MII or SRL port now and RESET the MAC */ + if (!lp->useSROM) { + if (lp->phy[lp->active].id != 0) { + lp->infoblock_csr6 = OMR_SDP | OMR_PS | OMR_HBD; + } else { + lp->infoblock_csr6 = OMR_SDP | OMR_TTM; + } + de4x5_switch_mac_port(dev); + } + + /* + ** Set the programmable burst length to 8 longwords for all the DC21140 + ** Fasternet chips and 4 longwords for all others: DMA errors result + ** without these values. Cache align 16 long. + */ + bmr = (lp->chipset==DC21140 ? PBL_8 : PBL_4) | DESC_SKIP_LEN | CACHE_ALIGN; + bmr |= ((lp->chipset & ~0x00ff)==DC2114x ? BMR_RML : 0); + outl(bmr, DE4X5_BMR); + + omr = inl(DE4X5_OMR) & ~OMR_PR; /* Turn off promiscuous mode */ + if (lp->chipset == DC21140) { + omr |= (OMR_SDP | OMR_SB); + } + lp->setup_f = PERFECT; + outl(virt_to_bus(lp->rx_ring), DE4X5_RRBA); + outl(virt_to_bus(lp->tx_ring), DE4X5_TRBA); + + lp->rx_new = lp->rx_old = 0; + lp->tx_new = lp->tx_old = 0; + + for (i = 0; i < lp->rxRingSize; i++) { + lp->rx_ring[i].status = cpu_to_le32(R_OWN); + } + + for (i = 0; i < lp->txRingSize; i++) { + lp->tx_ring[i].status = cpu_to_le32(0); + } + + barrier(); + + /* Build the setup frame depending on filtering mode */ + SetMulticastFilter(dev); + + load_packet(dev, lp->setup_frame, PERFECT_F|TD_SET|SETUP_FRAME_LEN, NULL); + outl(omr|OMR_ST, DE4X5_OMR); + + /* Poll for setup frame completion (adapter interrupts are disabled now) */ + sti(); /* Ensure timer interrupts */ + for (j=0, i=0;(i<500) && (j==0);i++) { /* Upto 500ms delay */ + udelay(1000); + if ((s32)le32_to_cpu(lp->tx_ring[lp->tx_new].status) >= 0) j=1; + } + outl(omr, DE4X5_OMR); /* Stop everything! */ + + if (j == 0) { + printk("%s: Setup frame timed out, status %08x\n", dev->name, + inl(DE4X5_STS)); + status = -EIO; + } + + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + lp->tx_old = lp->tx_new; + + return status; +} + +/* +** Writes a socket buffer address to the next available transmit descriptor. +*/ +static int +de4x5_queue_pkt(struct sk_buff *skb, struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int status = 0; + + test_and_set_bit(0, (void*)&dev->tbusy); /* Stop send re-tries */ + if (lp->tx_enable == NO) { /* Cannot send for now */ + return -1; + } + + /* + ** Clean out the TX ring asynchronously to interrupts - sometimes the + ** interrupts are lost by delayed descriptor status updates relative to + ** the irq assertion, especially with a busy PCI bus. + */ + cli(); + de4x5_tx(dev); + sti(); + + /* Test if cache is already locked - requeue skb if so */ + if (test_and_set_bit(0, (void *)&lp->cache.lock) && !lp->interrupt) + return -1; + + /* Transmit descriptor ring full or stale skb */ + if (dev->tbusy || lp->tx_skb[lp->tx_new]) { + if (lp->interrupt) { + de4x5_putb_cache(dev, skb); /* Requeue the buffer */ + } else { + de4x5_put_cache(dev, skb); + } + if (de4x5_debug & DEBUG_TX) { + printk("%s: transmit busy, lost media or stale skb found:\n STS:%08x\n tbusy:%ld\n IMR:%08x\n OMR:%08x\n Stale skb: %s\n",dev->name, inl(DE4X5_STS), dev->tbusy, inl(DE4X5_IMR), inl(DE4X5_OMR), (lp->tx_skb[lp->tx_new] ? "YES" : "NO")); + } + } else if (skb->len > 0) { + /* If we already have stuff queued locally, use that first */ + if (lp->cache.skb && !lp->interrupt) { + de4x5_put_cache(dev, skb); + skb = de4x5_get_cache(dev); + } + + while (skb && !dev->tbusy && !lp->tx_skb[lp->tx_new]) { + cli(); + test_and_set_bit(0, (void*)&dev->tbusy); + load_packet(dev, skb->data, TD_IC | TD_LS | TD_FS | skb->len, skb); +#if LINUX_VERSION_CODE >= ((2 << 16) | (1 << 8)) + lp->stats.tx_bytes += skb->len; +#endif + outl(POLL_DEMAND, DE4X5_TPD);/* Start the TX */ + + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + dev->trans_start = jiffies; + + if (TX_BUFFS_AVAIL) { + dev->tbusy = 0; /* Another pkt may be queued */ + } + skb = de4x5_get_cache(dev); + sti(); + } + if (skb) de4x5_putb_cache(dev, skb); + } + + lp->cache.lock = 0; + + return status; +} + +/* +** The DE4X5 interrupt handler. +** +** I/O Read/Writes through intermediate PCI bridges are never 'posted', +** so that the asserted interrupt always has some real data to work with - +** if these I/O accesses are ever changed to memory accesses, ensure the +** STS write is read immediately to complete the transaction if the adapter +** is not on bus 0. Lost interrupts can still occur when the PCI bus load +** is high and descriptor status bits cannot be set before the associated +** interrupt is asserted and this routine entered. +*/ +static void +de4x5_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_id; + struct de4x5_private *lp; + s32 imr, omr, sts, limit; + u_long iobase; + + if (dev == NULL) { + printk ("de4x5_interrupt(): irq %d for unknown device.\n", irq); + return; + } + lp = (struct de4x5_private *)dev->priv; + iobase = dev->base_addr; + + DISABLE_IRQs; /* Ensure non re-entrancy */ + + if (test_and_set_bit(MASK_INTERRUPTS, (void*) &lp->interrupt)) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + +#if LINUX_VERSION_CODE >= ((2 << 16) | (1 << 8)) + synchronize_irq(); +#endif + + for (limit=0; limit<8; limit++) { + sts = inl(DE4X5_STS); /* Read IRQ status */ + outl(sts, DE4X5_STS); /* Reset the board interrupts */ + + if (!(sts & lp->irq_mask)) break;/* All done */ + + if (sts & (STS_RI | STS_RU)) /* Rx interrupt (packet[s] arrived) */ + de4x5_rx(dev); + + if (sts & (STS_TI | STS_TU)) /* Tx interrupt (packet sent) */ + de4x5_tx(dev); + + if (sts & STS_LNF) { /* TP Link has failed */ + lp->irq_mask &= ~IMR_LFM; + } + + if (sts & STS_UNF) { /* Transmit underrun */ + de4x5_txur(dev); + } + + if (sts & STS_SE) { /* Bus Error */ + STOP_DE4X5; + printk("%s: Fatal bus error occurred, sts=%#8x, device stopped.\n", + dev->name, sts); + return; + } + } + + /* Load the TX ring with any locally stored packets */ + if (!test_and_set_bit(0, (void *)&lp->cache.lock)) { + while (lp->cache.skb && !dev->tbusy && lp->tx_enable) { + de4x5_queue_pkt(de4x5_get_cache(dev), dev); + } + lp->cache.lock = 0; + } + + lp->interrupt = UNMASK_INTERRUPTS; + ENABLE_IRQs; + + return; +} + +static int +de4x5_rx(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int entry; + s32 status; + + for (entry=lp->rx_new; (s32)le32_to_cpu(lp->rx_ring[entry].status)>=0; + entry=lp->rx_new) { + status = (s32)le32_to_cpu(lp->rx_ring[entry].status); + + if (lp->rx_ovf) { + if (inl(DE4X5_MFC) & MFC_FOCM) { + de4x5_rx_ovfc(dev); + break; + } + } + + if (status & RD_FS) { /* Remember the start of frame */ + lp->rx_old = entry; + } + + if (status & RD_LS) { /* Valid frame status */ + if (lp->tx_enable) lp->linkOK++; + if (status & RD_ES) { /* There was an error. */ + lp->stats.rx_errors++; /* Update the error stats. */ + if (status & (RD_RF | RD_TL)) lp->stats.rx_frame_errors++; + if (status & RD_CE) lp->stats.rx_crc_errors++; + if (status & RD_OF) lp->stats.rx_fifo_errors++; + if (status & RD_TL) lp->stats.rx_length_errors++; + if (status & RD_RF) lp->pktStats.rx_runt_frames++; + if (status & RD_CS) lp->pktStats.rx_collision++; + if (status & RD_DB) lp->pktStats.rx_dribble++; + if (status & RD_OF) lp->pktStats.rx_overflow++; + } else { /* A valid frame received */ + struct sk_buff *skb; + short pkt_len = (short)(le32_to_cpu(lp->rx_ring[entry].status) + >> 16) - 4; + + if ((skb = de4x5_alloc_rx_buff(dev, entry, pkt_len)) == NULL) { + printk("%s: Insufficient memory; nuking packet.\n", + dev->name); + lp->stats.rx_dropped++; + } else { + de4x5_dbg_rx(skb, pkt_len); + + /* Push up the protocol stack */ + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + + /* Update stats */ + lp->stats.rx_packets++; +#if LINUX_VERSION_CODE >= ((2 << 16) | (1 << 8)) + lp->stats.rx_bytes += pkt_len; +#endif + de4x5_local_stats(dev, skb->data, pkt_len); + } + } + + /* Change buffer ownership for this frame, back to the adapter */ + for (;lp->rx_old!=entry;lp->rx_old=(++lp->rx_old)%lp->rxRingSize) { + lp->rx_ring[lp->rx_old].status = cpu_to_le32(R_OWN); + barrier(); + } + lp->rx_ring[entry].status = cpu_to_le32(R_OWN); + barrier(); + } + + /* + ** Update entry information + */ + lp->rx_new = (++lp->rx_new) % lp->rxRingSize; + } + + return 0; +} + +/* +** Buffer sent - check for TX buffer errors. +*/ +static int +de4x5_tx(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int entry; + s32 status; + + for (entry = lp->tx_old; entry != lp->tx_new; entry = lp->tx_old) { + status = (s32)le32_to_cpu(lp->tx_ring[entry].status); + if (status < 0) { /* Buffer not sent yet */ + break; + } else if (status != 0x7fffffff) { /* Not setup frame */ + if (status & TD_ES) { /* An error happened */ + lp->stats.tx_errors++; + if (status & TD_NC) lp->stats.tx_carrier_errors++; + if (status & TD_LC) lp->stats.tx_window_errors++; + if (status & TD_UF) lp->stats.tx_fifo_errors++; + if (status & TD_EC) lp->pktStats.excessive_collisions++; + if (status & TD_DE) lp->stats.tx_aborted_errors++; + + if (TX_PKT_PENDING) { + outl(POLL_DEMAND, DE4X5_TPD);/* Restart a stalled TX */ + } + } else { /* Packet sent */ + lp->stats.tx_packets++; + if (lp->tx_enable) lp->linkOK++; + } + /* Update the collision counter */ + lp->stats.collisions += ((status & TD_EC) ? 16 : + ((status & TD_CC) >> 3)); + + /* Free the buffer. */ + if (lp->tx_skb[entry] != NULL) { + dev_kfree_skb(lp->tx_skb[entry], FREE_WRITE); + lp->tx_skb[entry] = NULL; + } + } + + /* Update all the pointers */ + lp->tx_old = (++lp->tx_old) % lp->txRingSize; + } + + if (TX_BUFFS_AVAIL && dev->tbusy) { /* Any resources available? */ + dev->tbusy = 0; /* Clear TX busy flag */ + if (lp->interrupt) mark_bh(NET_BH); + } + + return 0; +} + +static int +de4x5_ast(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int next_tick = DE4X5_AUTOSENSE_MS; + + disable_ast(dev); + + if (lp->useSROM) { + next_tick = srom_autoconf(dev); + } else if (lp->chipset == DC21140) { + next_tick = dc21140m_autoconf(dev); + } else if (lp->chipset == DC21041) { + next_tick = dc21041_autoconf(dev); + } else if (lp->chipset == DC21040) { + next_tick = dc21040_autoconf(dev); + } + lp->linkOK = 0; + enable_ast(dev, next_tick); + + return 0; +} + +static int +de4x5_txur(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int omr; + + omr = inl(DE4X5_OMR); + if (!(omr & OMR_SF) || (lp->chipset==DC21041) || (lp->chipset==DC21040)) { + omr &= ~(OMR_ST|OMR_SR); + outl(omr, DE4X5_OMR); + while (inl(DE4X5_STS) & STS_TS); + if ((omr & OMR_TR) < OMR_TR) { + omr += 0x4000; + } else { + omr |= OMR_SF; + } + outl(omr | OMR_ST | OMR_SR, DE4X5_OMR); + } + + return 0; +} + +static int +de4x5_rx_ovfc(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int omr; + + omr = inl(DE4X5_OMR); + outl(omr & ~OMR_SR, DE4X5_OMR); + while (inl(DE4X5_STS) & STS_RS); + + for (; (s32)le32_to_cpu(lp->rx_ring[lp->rx_new].status)>=0;) { + lp->rx_ring[lp->rx_new].status = cpu_to_le32(R_OWN); + lp->rx_new = (++lp->rx_new % lp->rxRingSize); + } + + outl(omr, DE4X5_OMR); + + return 0; +} + +static int +de4x5_close(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 imr, omr; + + disable_ast(dev); + dev->start = 0; + dev->tbusy = 1; + + if (de4x5_debug & DEBUG_CLOSE) { + printk("%s: Shutting down ethercard, status was %8.8x.\n", + dev->name, inl(DE4X5_STS)); + } + + /* + ** We stop the DE4X5 here... mask interrupts and stop TX & RX + */ + DISABLE_IRQs; + STOP_DE4X5; + + /* Free the associated irq */ + free_irq(dev->irq, dev); + lp->state = CLOSED; + + /* Free any socket buffers */ + de4x5_free_rx_buffs(dev); + de4x5_free_tx_buffs(dev); + + MOD_DEC_USE_COUNT; + + /* Put the adapter to sleep to save power */ + yawn(dev, SLEEP); + + return 0; +} + +static struct net_device_stats * +de4x5_get_stats(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + lp->stats.rx_missed_errors = (int)(inl(DE4X5_MFC) & (MFC_OVFL | MFC_CNTR)); + + return &lp->stats; +} + +static void +de4x5_local_stats(struct device *dev, char *buf, int pkt_len) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i; + + for (i=1; i<DE4X5_PKT_STAT_SZ-1; i++) { + if (pkt_len < (i*DE4X5_PKT_BIN_SZ)) { + lp->pktStats.bins[i]++; + i = DE4X5_PKT_STAT_SZ; + } + } + if (buf[0] & 0x01) { /* Multicast/Broadcast */ + if ((*(s32 *)&buf[0] == -1) && (*(s16 *)&buf[4] == -1)) { + lp->pktStats.broadcast++; + } else { + lp->pktStats.multicast++; + } + } else if ((*(s32 *)&buf[0] == *(s32 *)&dev->dev_addr[0]) && + (*(s16 *)&buf[4] == *(s16 *)&dev->dev_addr[4])) { + lp->pktStats.unicast++; + } + + lp->pktStats.bins[0]++; /* Duplicates stats.rx_packets */ + if (lp->pktStats.bins[0] == 0) { /* Reset counters */ + memset((char *)&lp->pktStats, 0, sizeof(lp->pktStats)); + } + + return; +} + +static void +load_packet(struct device *dev, char *buf, u32 flags, struct sk_buff *skb) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + + lp->tx_ring[lp->tx_new].buf = cpu_to_le32(virt_to_bus(buf)); + lp->tx_ring[lp->tx_new].des1 &= cpu_to_le32(TD_TER); + lp->tx_ring[lp->tx_new].des1 |= cpu_to_le32(flags); + lp->tx_skb[lp->tx_new] = skb; + barrier(); + lp->tx_ring[lp->tx_new].status = cpu_to_le32(T_OWN); + barrier(); + + return; +} + +/* +** Set or clear the multicast filter for this adaptor. +*/ +static void +set_multicast_list(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + /* First, double check that the adapter is open */ + if (lp->state == OPEN) { + if (dev->flags & IFF_PROMISC) { /* set promiscuous mode */ + u32 omr; + omr = inl(DE4X5_OMR); + omr |= OMR_PR; + outl(omr, DE4X5_OMR); + } else { + SetMulticastFilter(dev); + load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | + SETUP_FRAME_LEN, NULL); + + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + outl(POLL_DEMAND, DE4X5_TPD); /* Start the TX */ + dev->trans_start = jiffies; + } + } + + return; +} + +/* +** Calculate the hash code and update the logical address filter +** from a list of ethernet multicast addresses. +** Little endian crc one liner from Matt Thomas, DEC. +*/ +static void +SetMulticastFilter(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + struct dev_mc_list *dmi=dev->mc_list; + u_long iobase = dev->base_addr; + int i, j, bit, byte; + u16 hashcode; + u32 omr, crc, poly = CRC_POLYNOMIAL_LE; + char *pa; + unsigned char *addrs; + + omr = inl(DE4X5_OMR); + omr &= ~(OMR_PR | OMR_PM); + pa = build_setup_frame(dev, ALL); /* Build the basic frame */ + + if ((dev->flags & IFF_ALLMULTI) || (dev->mc_count > 14)) { + omr |= OMR_PM; /* Pass all multicasts */ + } else if (lp->setup_f == HASH_PERF) { /* Hash Filtering */ + for (i=0;i<dev->mc_count;i++) { /* for each address in the list */ + addrs=dmi->dmi_addr; + dmi=dmi->next; + if ((*addrs & 0x01) == 1) { /* multicast address? */ + crc = 0xffffffff; /* init CRC for each address */ + for (byte=0;byte<ETH_ALEN;byte++) {/* for each address byte */ + /* process each address bit */ + for (bit = *addrs++,j=0;j<8;j++, bit>>=1) { + crc = (crc >> 1) ^ (((crc ^ bit) & 0x01) ? poly : 0); + } + } + hashcode = crc & HASH_BITS; /* hashcode is 9 LSb of CRC */ + + byte = hashcode >> 3; /* bit[3-8] -> byte in filter */ + bit = 1 << (hashcode & 0x07);/* bit[0-2] -> bit in byte */ + + byte <<= 1; /* calc offset into setup frame */ + if (byte & 0x02) { + byte -= 1; + } + lp->setup_frame[byte] |= bit; + } + } + } else { /* Perfect filtering */ + for (j=0; j<dev->mc_count; j++) { + addrs=dmi->dmi_addr; + dmi=dmi->next; + for (i=0; i<ETH_ALEN; i++) { + *(pa + (i&1)) = *addrs++; + if (i & 0x01) pa += 4; + } + } + } + outl(omr, DE4X5_OMR); + + return; +} + +#if !defined(__sparc_v9__) && !defined(__powerpc__) && !defined(__alpha__) +/* +** EISA bus I/O device probe. Probe from slot 1 since slot 0 is usually +** the motherboard. Upto 15 EISA devices are supported. +*/ +__initfunc(static void +eisa_probe(struct device *dev, u_long ioaddr)) +{ + int i, maxSlots, status, device; + u_char irq; + u_short vendor; + u32 cfid; + u_long iobase; + struct bus_type *lp = &bus; + char name[DE4X5_STRLEN]; + + if (lastEISA == MAX_EISA_SLOTS) return;/* No more EISA devices to search */ + + lp->bus = EISA; + + if (ioaddr == 0) { /* Autoprobing */ + iobase = EISA_SLOT_INC; /* Get the first slot address */ + i = 1; + maxSlots = MAX_EISA_SLOTS; + } else { /* Probe a specific location */ + iobase = ioaddr; + i = (ioaddr >> 12); + maxSlots = i + 1; + } + + for (status = -ENODEV; (i<maxSlots) && (dev!=NULL); i++, iobase+=EISA_SLOT_INC) { + if (EISA_signature(name, EISA_ID)) { + cfid = (u32) inl(PCI_CFID); + cfrv = (u_short) inl(PCI_CFRV); + device = (cfid >> 8) & 0x00ffff00; + vendor = (u_short) cfid; + + /* Read the EISA Configuration Registers */ + irq = inb(EISA_REG0); + irq = de4x5_irq[(irq >> 1) & 0x03]; + + if (is_DC2114x) device |= (cfrv & CFRV_RN); + lp->chipset = device; + + /* Write the PCI Configuration Registers */ + outl(PCI_COMMAND_IO | PCI_COMMAND_MASTER, PCI_CFCS); + outl(0x00006000, PCI_CFLT); + outl(iobase, PCI_CBIO); + + DevicePresent(EISA_APROM); + if (check_region(iobase, DE4X5_EISA_TOTAL_SIZE) == 0) { + dev->irq = irq; + if ((status = de4x5_hw_init(dev, iobase)) == 0) { + num_de4x5s++; + if (loading_module) link_modules(lastModule, dev); + lastEISA = i; + return; + } + } else if (ioaddr != 0) { + printk("%s: region already allocated at 0x%04lx.\n", dev->name,iobase); + } + } + } + + if (ioaddr == 0) lastEISA = i; + + return; +} +#endif /* !(__sparc_v9__) && !(__powerpc__) && !defined(__alpha__)*/ + +/* +** PCI bus I/O device probe +** NB: PCI I/O accesses and Bus Mastering are enabled by the PCI BIOS, not +** the driver. Some PCI BIOS's, pre V2.1, need the slot + features to be +** enabled by the user first in the set up utility. Hence we just check for +** enabled features and silently ignore the card if they're not. +** +** STOP PRESS: Some BIOS's __require__ the driver to enable the bus mastering +** bit. Here, check for I/O accesses and then set BM. If you put the card in +** a non BM slot, you're on your own (and complain to the PC vendor that your +** PC doesn't conform to the PCI standard)! +*/ +#define PCI_DEVICE (dev_num << 3) +#define PCI_LAST_DEV 32 + +__initfunc(static void +pci_probe(struct device *dev, u_long ioaddr)) +{ + u_char pb, pbus, dev_num, dnum, dev_fn, timer, tirq; + u_short dev_id, vendor, index, status; + u_int tmp, irq = 0, device, class = DE4X5_CLASS_CODE; + u_long iobase = 0; /* Clear upper 32 bits in Alphas */ + struct bus_type *lp = &bus; + + if (lastPCI == NO_MORE_PCI) return; + + if (!pcibios_present()) { + lastPCI = NO_MORE_PCI; + return; /* No PCI bus in this machine! */ + } + + lp->bus = PCI; + lp->bus_num = 0; + + if ((ioaddr < 0x1000) && loading_module) { + pbus = (u_short)(ioaddr >> 8); + dnum = (u_short)(ioaddr & 0xff); + } else { + pbus = 0; + dnum = 0; + } + + for (index=lastPCI+1; + (pcibios_find_class(class, index, &pb, &dev_fn)!= PCIBIOS_DEVICE_NOT_FOUND); + index++) { + dev_num = PCI_SLOT(dev_fn); + if ((!pbus && !dnum) || ((pbus == pb) && (dnum == dev_num))) { +#ifdef __sparc_v9__ + struct pci_dev *pdev; + for (pdev = pci_devices; pdev; pdev = pdev->next) { + if ((pdev->bus->number==pb) && (pdev->devfn==dev_fn)) break; + } +#endif + device = 0; + pcibios_read_config_word(pb, PCI_DEVICE, PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pb, PCI_DEVICE, PCI_DEVICE_ID, &dev_id); + device = dev_id; + device <<= 8; + if (!(is_DC21040 || is_DC21041 || is_DC21140 || is_DC2114x)) { + continue; + } + + /* Search for an SROM on this bus */ + if (lp->bus_num != pb) { + lp->bus_num = pb; + srom_search(index); + } + + /* Get the chip configuration revision register */ + pcibios_read_config_dword(pb, PCI_DEVICE, PCI_REVISION_ID, &cfrv); + + /* Set the device number information */ + lp->device = dev_num; + lp->bus_num = pb; + + /* Set the chipset information */ + if (is_DC2114x) device |= (cfrv & CFRV_RN); + lp->chipset = device; + + /* Get the board I/O address (64 bits on sparc64) */ +#ifndef __sparc_v9__ + pcibios_read_config_dword(pb, PCI_DEVICE, PCI_BASE_ADDRESS_0, &tmp); + iobase = tmp; +#else + iobase = pdev->base_address[0]; +#endif + iobase &= CBIO_MASK; + + /* Fetch the IRQ to be used */ +#ifndef __sparc_v9__ + pcibios_read_config_byte(pb, PCI_DEVICE, PCI_INTERRUPT_LINE, &tirq); + irq = tirq; +#else + irq = pdev->irq; +#endif + if ((irq == 0) || (irq == 0xff) || ((int)irq == -1)) continue; + + /* Check if I/O accesses and Bus Mastering are enabled */ + pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status); +#ifdef __powerpc__ + if (!(status & PCI_COMMAND_IO)) { + status |= PCI_COMMAND_IO; + pcibios_write_config_word(pb, PCI_DEVICE, PCI_COMMAND, status); + pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status); + } +#endif /* __powerpc__ */ + if (!(status & PCI_COMMAND_IO)) continue; + + if (!(status & PCI_COMMAND_MASTER)) { + status |= PCI_COMMAND_MASTER; + pcibios_write_config_word(pb, PCI_DEVICE, PCI_COMMAND, status); + pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status); + } + if (!(status & PCI_COMMAND_MASTER)) continue; + + /* Check the latency timer for values >= 0x60 */ + pcibios_read_config_byte(pb, PCI_DEVICE, PCI_LATENCY_TIMER, &timer); + if (timer < 0x60) { + pcibios_write_config_byte(pb, PCI_DEVICE, PCI_LATENCY_TIMER, 0x60); + } + + DevicePresent(DE4X5_APROM); + if (check_region(iobase, DE4X5_PCI_TOTAL_SIZE) == 0) { + dev->irq = irq; + if ((status = de4x5_hw_init(dev, iobase)) == 0) { + num_de4x5s++; + lastPCI = index; + if (loading_module) link_modules(lastModule, dev); + return; + } + } else if (ioaddr != 0) { + printk("%s: region already allocated at 0x%04lx.\n", dev->name, + iobase); + } + } + } + + lastPCI = NO_MORE_PCI; + + return; +} + +/* +** This function searches the current bus (which is >0) for a DECchip with an +** SROM, so that in multiport cards that have one SROM shared between multiple +** DECchips, we can find the base SROM irrespective of the BIOS scan direction. +** For single port cards this is a time waster... +*/ +__initfunc(static void +srom_search(int index)) +{ + u_char pb, dev_fn, tirq; + u_short dev_id, dev_num, vendor, status; + u_int tmp, irq = 0, device, class = DE4X5_CLASS_CODE; + u_long iobase = 0; /* Clear upper 32 bits in Alphas */ + int i, j; + struct bus_type *lp = &bus; + + for (; + (pcibios_find_class(class, index, &pb, &dev_fn)!= PCIBIOS_DEVICE_NOT_FOUND); + index++) { + + if (lp->bus_num != pb) return; + dev_num = PCI_SLOT(dev_fn); +#ifdef __sparc_v9__ + struct pci_dev *pdev; + for (pdev = pci_devices; pdev; pdev = pdev->next) { + if ((pdev->bus->number == pb) && (pdev->devfn == dev_fn)) break; + } +#endif + device = 0; + pcibios_read_config_word(pb, PCI_DEVICE, PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pb, PCI_DEVICE, PCI_DEVICE_ID, &dev_id); + device = dev_id; + device <<= 8; + if (!(is_DC21040 || is_DC21041 || is_DC21140 || is_DC2114x)) { + continue; + } + + /* Get the chip configuration revision register */ + pcibios_read_config_dword(pb, PCI_DEVICE, PCI_REVISION_ID, &cfrv); + + /* Set the device number information */ + lp->device = dev_num; + lp->bus_num = pb; + + /* Set the chipset information */ + if (is_DC2114x) device |= (cfrv & CFRV_RN); + lp->chipset = device; + + /* Get the board I/O address (64 bits on sparc64) */ +#ifndef __sparc_v9__ + pcibios_read_config_dword(pb, PCI_DEVICE, PCI_BASE_ADDRESS_0, &tmp); + iobase = tmp; +#else + iobase = pdev->base_address[0]; +#endif + iobase &= CBIO_MASK; + + /* Fetch the IRQ to be used */ +#ifndef __sparc_v9__ + pcibios_read_config_byte(pb, PCI_DEVICE, PCI_INTERRUPT_LINE, &tirq); + irq = tirq; +#else + irq = pdev->irq; +#endif + if ((irq == 0) || (irq == 0xff) || ((int)irq == -1)) continue; + + /* Check if I/O accesses are enabled */ + pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status); + if (!(status & PCI_COMMAND_IO)) continue; + + /* Search for a valid SROM attached to this DECchip */ + DevicePresent(DE4X5_APROM); + for (j=0, i=0; i<ETH_ALEN; i++) { + j += (u_char) *((u_char *)&lp->srom + SROM_HWADD + i); + } + if ((j != 0) && (j != 0x5fa)) { + last.chipset = device; + last.bus = pb; + last.irq = irq; + for (i=0; i<ETH_ALEN; i++) { + last.addr[i] = (u_char)*((u_char *)&lp->srom + SROM_HWADD + i); + } + return; + } + } + + return; +} + +__initfunc(static void +link_modules(struct device *dev, struct device *tmp)) +{ + struct device *p=dev; + + if (p) { + while (((struct de4x5_private *)(p->priv))->next_module) { + p = ((struct de4x5_private *)(p->priv))->next_module; + } + + if (dev != tmp) { + ((struct de4x5_private *)(p->priv))->next_module = tmp; + } else { + ((struct de4x5_private *)(p->priv))->next_module = NULL; + } + } + + return; +} + +/* +** Auto configure the media here rather than setting the port at compile +** time. This routine is called by de4x5_init() and when a loss of media is +** detected (excessive collisions, loss of carrier, no carrier or link fail +** [TP] or no recent receive activity) to check whether the user has been +** sneaky and changed the port on us. +*/ +static int +autoconf_media(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int next_tick = DE4X5_AUTOSENSE_MS; + + lp->linkOK = 0; + lp->c_media = AUTO; /* Bogus last media */ + disable_ast(dev); + inl(DE4X5_MFC); /* Zero the lost frames counter */ + lp->media = INIT; + lp->tcount = 0; + + if (lp->useSROM) { + next_tick = srom_autoconf(dev); + } else if (lp->chipset == DC21040) { + next_tick = dc21040_autoconf(dev); + } else if (lp->chipset == DC21041) { + next_tick = dc21041_autoconf(dev); + } else if (lp->chipset == DC21140) { + next_tick = dc21140m_autoconf(dev); + } + + enable_ast(dev, next_tick); + + return (lp->media); +} + +/* +** Autoconfigure the media when using the DC21040. AUI cannot be distinguished +** from BNC as the port has a jumper to set thick or thin wire. When set for +** BNC, the BNC port will indicate activity if it's not terminated correctly. +** The only way to test for that is to place a loopback packet onto the +** network and watch for errors. Since we're messing with the interrupt mask +** register, disable the board interrupts and do not allow any more packets to +** be queued to the hardware. Re-enable everything only when the media is +** found. +** I may have to "age out" locally queued packets so that the higher layer +** timeouts don't effectively duplicate packets on the network. +*/ +static int +dc21040_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int next_tick = DE4X5_AUTOSENSE_MS; + s32 imr; + + switch (lp->media) { + case INIT: + DISABLE_IRQs; + lp->tx_enable = NO; + lp->timeout = -1; + de4x5_save_skbs(dev); + if ((lp->autosense == AUTO) || (lp->autosense == TP)) { + lp->media = TP; + } else if ((lp->autosense == BNC) || (lp->autosense == AUI) || (lp->autosense == BNC_AUI)) { + lp->media = BNC_AUI; + } else if (lp->autosense == EXT_SIA) { + lp->media = EXT_SIA; + } else { + lp->media = NC; + } + lp->local_state = 0; + next_tick = dc21040_autoconf(dev); + break; + + case TP: + next_tick = dc21040_state(dev, 0x8f01, 0xffff, 0x0000, 3000, BNC_AUI, + TP_SUSPECT, test_tp); + break; + + case TP_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, TP, test_tp, dc21040_autoconf); + break; + + case BNC: + case AUI: + case BNC_AUI: + next_tick = dc21040_state(dev, 0x8f09, 0x0705, 0x0006, 3000, EXT_SIA, + BNC_AUI_SUSPECT, ping_media); + break; + + case BNC_AUI_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, BNC_AUI, ping_media, dc21040_autoconf); + break; + + case EXT_SIA: + next_tick = dc21040_state(dev, 0x3041, 0x0000, 0x0006, 3000, + NC, EXT_SIA_SUSPECT, ping_media); + break; + + case EXT_SIA_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, EXT_SIA, ping_media, dc21040_autoconf); + break; + + case NC: + /* default to TP for all */ + reset_init_sia(dev, 0x8f01, 0xffff, 0x0000); + if (lp->media != lp->c_media) { + de4x5_dbg_media(dev); + lp->c_media = lp->media; + } + lp->media = INIT; + lp->tx_enable = NO; + break; + } + + return next_tick; +} + +static int +dc21040_state(struct device *dev, int csr13, int csr14, int csr15, int timeout, + int next_state, int suspect_state, + int (*fn)(struct device *, int)) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int next_tick = DE4X5_AUTOSENSE_MS; + int linkBad; + + switch (lp->local_state) { + case 0: + reset_init_sia(dev, csr13, csr14, csr15); + lp->local_state++; + next_tick = 500; + break; + + case 1: + if (!lp->tx_enable) { + linkBad = fn(dev, timeout); + if (linkBad < 0) { + next_tick = linkBad & ~TIMER_CB; + } else { + if (linkBad && (lp->autosense == AUTO)) { + lp->local_state = 0; + lp->media = next_state; + } else { + de4x5_init_connection(dev); + } + } + } else if (!lp->linkOK && (lp->autosense == AUTO)) { + lp->media = suspect_state; + next_tick = 3000; + } + break; + } + + return next_tick; +} + +static int +de4x5_suspect_state(struct device *dev, int timeout, int prev_state, + int (*fn)(struct device *, int), + int (*asfn)(struct device *)) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int next_tick = DE4X5_AUTOSENSE_MS; + int linkBad; + + switch (lp->local_state) { + case 1: + if (lp->linkOK) { + lp->media = prev_state; + } else { + lp->local_state++; + next_tick = asfn(dev); + } + break; + + case 2: + linkBad = fn(dev, timeout); + if (linkBad < 0) { + next_tick = linkBad & ~TIMER_CB; + } else if (!linkBad) { + lp->local_state--; + lp->media = prev_state; + } else { + lp->media = INIT; + lp->tcount++; + } + } + + return next_tick; +} + +/* +** Autoconfigure the media when using the DC21041. AUI needs to be tested +** before BNC, because the BNC port will indicate activity if it's not +** terminated correctly. The only way to test for that is to place a loopback +** packet onto the network and watch for errors. Since we're messing with +** the interrupt mask register, disable the board interrupts and do not allow +** any more packets to be queued to the hardware. Re-enable everything only +** when the media is found. +*/ +static int +dc21041_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 sts, irqs, irq_mask, imr, omr; + int next_tick = DE4X5_AUTOSENSE_MS; + + switch (lp->media) { + case INIT: + DISABLE_IRQs; + lp->tx_enable = NO; + lp->timeout = -1; + de4x5_save_skbs(dev); /* Save non transmitted skb's */ + if ((lp->autosense == AUTO) || (lp->autosense == TP_NW)) { + lp->media = TP; /* On chip auto negotiation is broken */ + } else if (lp->autosense == TP) { + lp->media = TP; + } else if (lp->autosense == BNC) { + lp->media = BNC; + } else if (lp->autosense == AUI) { + lp->media = AUI; + } else { + lp->media = NC; + } + lp->local_state = 0; + next_tick = dc21041_autoconf(dev); + break; + + case TP_NW: + if (lp->timeout < 0) { + omr = inl(DE4X5_OMR);/* Set up full duplex for the autonegotiate */ + outl(omr | OMR_FDX, DE4X5_OMR); + } + irqs = STS_LNF | STS_LNP; + irq_mask = IMR_LFM | IMR_LPM; + sts = test_media(dev, irqs, irq_mask, 0xef01, 0xffff, 0x0008, 2400); + if (sts < 0) { + next_tick = sts & ~TIMER_CB; + } else { + if (sts & STS_LNP) { + lp->media = ANS; + } else { + lp->media = AUI; + } + next_tick = dc21041_autoconf(dev); + } + break; + + case ANS: + if (!lp->tx_enable) { + irqs = STS_LNP; + irq_mask = IMR_LPM; + sts = test_ans(dev, irqs, irq_mask, 3000); + if (sts < 0) { + next_tick = sts & ~TIMER_CB; + } else { + if (!(sts & STS_LNP) && (lp->autosense == AUTO)) { + lp->media = TP; + next_tick = dc21041_autoconf(dev); + } else { + lp->local_state = 1; + de4x5_init_connection(dev); + } + } + } else if (!lp->linkOK && (lp->autosense == AUTO)) { + lp->media = ANS_SUSPECT; + next_tick = 3000; + } + break; + + case ANS_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, ANS, test_tp, dc21041_autoconf); + break; + + case TP: + if (!lp->tx_enable) { + if (lp->timeout < 0) { + omr = inl(DE4X5_OMR); /* Set up half duplex for TP */ + outl(omr & ~OMR_FDX, DE4X5_OMR); + } + irqs = STS_LNF | STS_LNP; + irq_mask = IMR_LFM | IMR_LPM; + sts = test_media(dev,irqs, irq_mask, 0xef01, 0xff3f, 0x0008, 2400); + if (sts < 0) { + next_tick = sts & ~TIMER_CB; + } else { + if (!(sts & STS_LNP) && (lp->autosense == AUTO)) { + if (inl(DE4X5_SISR) & SISR_NRA) { + lp->media = AUI; /* Non selected port activity */ + } else { + lp->media = BNC; + } + next_tick = dc21041_autoconf(dev); + } else { + lp->local_state = 1; + de4x5_init_connection(dev); + } + } + } else if (!lp->linkOK && (lp->autosense == AUTO)) { + lp->media = TP_SUSPECT; + next_tick = 3000; + } + break; + + case TP_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, TP, test_tp, dc21041_autoconf); + break; + + case AUI: + if (!lp->tx_enable) { + if (lp->timeout < 0) { + omr = inl(DE4X5_OMR); /* Set up half duplex for AUI */ + outl(omr & ~OMR_FDX, DE4X5_OMR); + } + irqs = 0; + irq_mask = 0; + sts = test_media(dev,irqs, irq_mask, 0xef09, 0xf73d, 0x000e, 1000); + if (sts < 0) { + next_tick = sts & ~TIMER_CB; + } else { + if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) { + lp->media = BNC; + next_tick = dc21041_autoconf(dev); + } else { + lp->local_state = 1; + de4x5_init_connection(dev); + } + } + } else if (!lp->linkOK && (lp->autosense == AUTO)) { + lp->media = AUI_SUSPECT; + next_tick = 3000; + } + break; + + case AUI_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, AUI, ping_media, dc21041_autoconf); + break; + + case BNC: + switch (lp->local_state) { + case 0: + if (lp->timeout < 0) { + omr = inl(DE4X5_OMR); /* Set up half duplex for BNC */ + outl(omr & ~OMR_FDX, DE4X5_OMR); + } + irqs = 0; + irq_mask = 0; + sts = test_media(dev,irqs, irq_mask, 0xef09, 0xf73d, 0x0006, 1000); + if (sts < 0) { + next_tick = sts & ~TIMER_CB; + } else { + lp->local_state++; /* Ensure media connected */ + next_tick = dc21041_autoconf(dev); + } + break; + + case 1: + if (!lp->tx_enable) { + if ((sts = ping_media(dev, 3000)) < 0) { + next_tick = sts & ~TIMER_CB; + } else { + if (sts) { + lp->local_state = 0; + lp->media = NC; + } else { + de4x5_init_connection(dev); + } + } + } else if (!lp->linkOK && (lp->autosense == AUTO)) { + lp->media = BNC_SUSPECT; + next_tick = 3000; + } + break; + } + break; + + case BNC_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, BNC, ping_media, dc21041_autoconf); + break; + + case NC: + omr = inl(DE4X5_OMR); /* Set up full duplex for the autonegotiate */ + outl(omr | OMR_FDX, DE4X5_OMR); + reset_init_sia(dev, 0xef01, 0xffff, 0x0008);/* Initialise the SIA */ + if (lp->media != lp->c_media) { + de4x5_dbg_media(dev); + lp->c_media = lp->media; + } + lp->media = INIT; + lp->tx_enable = NO; + break; + } + + return next_tick; +} + +/* +** Some autonegotiation chips are broken in that they do not return the +** acknowledge bit (anlpa & MII_ANLPA_ACK) in the link partner advertisement +** register, except at the first power up negotiation. +*/ +static int +dc21140m_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int ana, anlpa, cap, cr, slnk, sr; + int next_tick = DE4X5_AUTOSENSE_MS; + u_long imr, omr, iobase = dev->base_addr; + + switch(lp->media) { + case INIT: + if (lp->timeout < 0) { + DISABLE_IRQs; + lp->tx_enable = FALSE; + lp->linkOK = 0; + de4x5_save_skbs(dev); /* Save non transmitted skb's */ + } + if ((next_tick = de4x5_reset_phy(dev)) < 0) { + next_tick &= ~TIMER_CB; + } else { + if (lp->useSROM) { + if (srom_map_media(dev) < 0) { + lp->tcount++; + return next_tick; + } + srom_exec(dev, lp->phy[lp->active].gep); + if (lp->infoblock_media == ANS) { + ana = lp->phy[lp->active].ana | MII_ANA_CSMA; + mii_wr(ana, MII_ANA, lp->phy[lp->active].addr, DE4X5_MII); + } + } else { + lp->tmp = MII_SR_ASSC; /* Fake out the MII speed set */ + SET_10Mb; + if (lp->autosense == _100Mb) { + lp->media = _100Mb; + } else if (lp->autosense == _10Mb) { + lp->media = _10Mb; + } else if ((lp->autosense == AUTO) && + ((sr=is_anc_capable(dev)) & MII_SR_ANC)) { + ana = (((sr >> 6) & MII_ANA_TAF) | MII_ANA_CSMA); + ana &= (lp->fdx ? ~0 : ~MII_ANA_FDAM); + mii_wr(ana, MII_ANA, lp->phy[lp->active].addr, DE4X5_MII); + lp->media = ANS; + } else if (lp->autosense == AUTO) { + lp->media = SPD_DET; + } else if (is_spd_100(dev) && is_100_up(dev)) { + lp->media = _100Mb; + } else { + lp->media = NC; + } + } + lp->local_state = 0; + next_tick = dc21140m_autoconf(dev); + } + break; + + case ANS: + switch (lp->local_state) { + case 0: + if (lp->timeout < 0) { + mii_wr(MII_CR_ASSE | MII_CR_RAN, MII_CR, lp->phy[lp->active].addr, DE4X5_MII); + } + cr = test_mii_reg(dev, MII_CR, MII_CR_RAN, FALSE, 500); + if (cr < 0) { + next_tick = cr & ~TIMER_CB; + } else { + if (cr) { + lp->local_state = 0; + lp->media = SPD_DET; + } else { + lp->local_state++; + } + next_tick = dc21140m_autoconf(dev); + } + break; + + case 1: + if ((sr=test_mii_reg(dev, MII_SR, MII_SR_ASSC, TRUE, 2000)) < 0) { + next_tick = sr & ~TIMER_CB; + } else { + lp->media = SPD_DET; + lp->local_state = 0; + if (sr) { /* Success! */ + lp->tmp = MII_SR_ASSC; + anlpa = mii_rd(MII_ANLPA, lp->phy[lp->active].addr, DE4X5_MII); + ana = mii_rd(MII_ANA, lp->phy[lp->active].addr, DE4X5_MII); + if (!(anlpa & MII_ANLPA_RF) && + (cap = anlpa & MII_ANLPA_TAF & ana)) { + if (cap & MII_ANA_100M) { + lp->fdx = ((ana & anlpa & MII_ANA_FDAM & MII_ANA_100M) ? TRUE : FALSE); + lp->media = _100Mb; + } else if (cap & MII_ANA_10M) { + lp->fdx = ((ana & anlpa & MII_ANA_FDAM & MII_ANA_10M) ? TRUE : FALSE); + + lp->media = _10Mb; + } + } + } /* Auto Negotiation failed to finish */ + next_tick = dc21140m_autoconf(dev); + } /* Auto Negotiation failed to start */ + break; + } + break; + + case SPD_DET: /* Choose 10Mb/s or 100Mb/s */ + if (lp->timeout < 0) { + lp->tmp = (lp->phy[lp->active].id ? MII_SR_LKS : + (~gep_rd(dev) & GEP_LNP)); + SET_100Mb_PDET; + } + if ((slnk = test_for_100Mb(dev, 6500)) < 0) { + next_tick = slnk & ~TIMER_CB; + } else { + if (is_spd_100(dev) && is_100_up(dev)) { + lp->media = _100Mb; + } else if ((!is_spd_100(dev) && (is_10_up(dev) & lp->tmp))) { + lp->media = _10Mb; + } else { + lp->media = NC; + } + next_tick = dc21140m_autoconf(dev); + } + break; + + case _100Mb: /* Set 100Mb/s */ + next_tick = 3000; + if (!lp->tx_enable) { + SET_100Mb; + de4x5_init_connection(dev); + } else { + if (!lp->linkOK && (lp->autosense == AUTO)) { + if (!is_100_up(dev) || (!lp->useSROM && !is_spd_100(dev))) { + lp->media = INIT; + lp->tcount++; + next_tick = DE4X5_AUTOSENSE_MS; + } + } + } + break; + + case BNC: + case AUI: + case _10Mb: /* Set 10Mb/s */ + next_tick = 3000; + if (!lp->tx_enable) { + SET_10Mb; + de4x5_init_connection(dev); + } else { + if (!lp->linkOK && (lp->autosense == AUTO)) { + if (!is_10_up(dev) || (!lp->useSROM && is_spd_100(dev))) { + lp->media = INIT; + lp->tcount++; + next_tick = DE4X5_AUTOSENSE_MS; + } + } + } + break; + + case NC: + if (lp->media != lp->c_media) { + de4x5_dbg_media(dev); + lp->c_media = lp->media; + } + lp->media = INIT; + lp->tx_enable = FALSE; + break; + } + + return next_tick; +} + +/* +** This routine may be merged into dc21140m_autoconf() sometime as I'm +** changing how I figure out the media - but trying to keep it backwards +** compatible with the de500-xa and de500-aa. +** Whether it's BNC, AUI, SYM or MII is sorted out in the infoblock +** functions and set during de4x5_mac_port() and/or de4x5_reset_phy(). +** This routine just has to figure out whether 10Mb/s or 100Mb/s is +** active. +** When autonegotiation is working, the ANS part searches the SROM for +** the highest common speed (TP) link that both can run and if that can +** be full duplex. That infoblock is executed and then the link speed set. +** +** Only _10Mb and _100Mb are tested here. +*/ +static int +dc2114x_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 cr, anlpa, ana, cap, irqs, irq_mask, imr, omr, slnk, sr, sts; + int next_tick = DE4X5_AUTOSENSE_MS; + + switch (lp->media) { + case INIT: + if (lp->timeout < 0) { + DISABLE_IRQs; + lp->tx_enable = FALSE; + lp->linkOK = 0; + lp->timeout = -1; + de4x5_save_skbs(dev); /* Save non transmitted skb's */ + if (lp->params.autosense & ~AUTO) { + srom_map_media(dev); /* Fixed media requested */ + if (lp->media != lp->params.autosense) { + lp->tcount++; + lp->media = INIT; + return next_tick; + } + lp->media = INIT; + } + } + if ((next_tick = de4x5_reset_phy(dev)) < 0) { + next_tick &= ~TIMER_CB; + } else { + if (lp->autosense == _100Mb) { + lp->media = _100Mb; + } else if (lp->autosense == _10Mb) { + lp->media = _10Mb; + } else if (lp->autosense == TP) { + lp->media = TP; + } else if (lp->autosense == BNC) { + lp->media = BNC; + } else if (lp->autosense == AUI) { + lp->media = AUI; + } else { + lp->media = SPD_DET; + if ((lp->infoblock_media == ANS) && + ((sr=is_anc_capable(dev)) & MII_SR_ANC)) { + ana = (((sr >> 6) & MII_ANA_TAF) | MII_ANA_CSMA); + ana &= (lp->fdx ? ~0 : ~MII_ANA_FDAM); + mii_wr(ana, MII_ANA, lp->phy[lp->active].addr, DE4X5_MII); + lp->media = ANS; + } + } + lp->local_state = 0; + next_tick = dc2114x_autoconf(dev); + } + break; + + case ANS: + switch (lp->local_state) { + case 0: + if (lp->timeout < 0) { + mii_wr(MII_CR_ASSE | MII_CR_RAN, MII_CR, lp->phy[lp->active].addr, DE4X5_MII); + } + cr = test_mii_reg(dev, MII_CR, MII_CR_RAN, FALSE, 500); + if (cr < 0) { + next_tick = cr & ~TIMER_CB; + } else { + if (cr) { + lp->local_state = 0; + lp->media = SPD_DET; + } else { + lp->local_state++; + } + next_tick = dc2114x_autoconf(dev); + } + break; + + case 1: + if ((sr=test_mii_reg(dev, MII_SR, MII_SR_ASSC, TRUE, 2000)) < 0) { + next_tick = sr & ~TIMER_CB; + } else { + lp->media = SPD_DET; + lp->local_state = 0; + if (sr) { /* Success! */ + lp->tmp = MII_SR_ASSC; + anlpa = mii_rd(MII_ANLPA, lp->phy[lp->active].addr, DE4X5_MII); + ana = mii_rd(MII_ANA, lp->phy[lp->active].addr, DE4X5_MII); + if (!(anlpa & MII_ANLPA_RF) && + (cap = anlpa & MII_ANLPA_TAF & ana)) { + if (cap & MII_ANA_100M) { + lp->fdx = ((ana & anlpa & MII_ANA_FDAM & MII_ANA_100M) ? TRUE : FALSE); + lp->media = _100Mb; + } else if (cap & MII_ANA_10M) { + lp->fdx = ((ana & anlpa & MII_ANA_FDAM & MII_ANA_10M) ? TRUE : FALSE); + lp->media = _10Mb; + } + } + } /* Auto Negotiation failed to finish */ + next_tick = dc2114x_autoconf(dev); + } /* Auto Negotiation failed to start */ + break; + } + break; + + case AUI: + if (!lp->tx_enable) { + if (lp->timeout < 0) { + omr = inl(DE4X5_OMR); /* Set up half duplex for AUI */ + outl(omr & ~OMR_FDX, DE4X5_OMR); + } + irqs = 0; + irq_mask = 0; + sts = test_media(dev,irqs, irq_mask, 0, 0, 0, 1000); + if (sts < 0) { + next_tick = sts & ~TIMER_CB; + } else { + if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) { + lp->media = BNC; + next_tick = dc2114x_autoconf(dev); + } else { + lp->local_state = 1; + de4x5_init_connection(dev); + } + } + } else if (!lp->linkOK && (lp->autosense == AUTO)) { + lp->media = AUI_SUSPECT; + next_tick = 3000; + } + break; + + case AUI_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, AUI, ping_media, dc2114x_autoconf); + break; + + case BNC: + switch (lp->local_state) { + case 0: + if (lp->timeout < 0) { + omr = inl(DE4X5_OMR); /* Set up half duplex for BNC */ + outl(omr & ~OMR_FDX, DE4X5_OMR); + } + irqs = 0; + irq_mask = 0; + sts = test_media(dev,irqs, irq_mask, 0, 0, 0, 1000); + if (sts < 0) { + next_tick = sts & ~TIMER_CB; + } else { + lp->local_state++; /* Ensure media connected */ + next_tick = dc2114x_autoconf(dev); + } + break; + + case 1: + if (!lp->tx_enable) { + if ((sts = ping_media(dev, 3000)) < 0) { + next_tick = sts & ~TIMER_CB; + } else { + if (sts) { + lp->local_state = 0; + lp->tcount++; + lp->media = INIT; + } else { + de4x5_init_connection(dev); + } + } + } else if (!lp->linkOK && (lp->autosense == AUTO)) { + lp->media = BNC_SUSPECT; + next_tick = 3000; + } + break; + } + break; + + case BNC_SUSPECT: + next_tick = de4x5_suspect_state(dev, 1000, BNC, ping_media, dc2114x_autoconf); + break; + + case SPD_DET: /* Choose 10Mb/s or 100Mb/s */ + if (srom_map_media(dev) < 0) { + lp->tcount++; + lp->media = INIT; + return next_tick; + } + if (lp->media == _100Mb) { + if ((slnk = test_for_100Mb(dev, 6500)) < 0) { + lp->media = SPD_DET; + return (slnk & ~TIMER_CB); + } + } else { + if (wait_for_link(dev) < 0) { + lp->media = SPD_DET; + return PDET_LINK_WAIT; + } + } + if (lp->media == ANS) { /* Do MII parallel detection */ + if (is_spd_100(dev)) { + lp->media = _100Mb; + } else { + lp->media = _10Mb; + } + next_tick = dc2114x_autoconf(dev); + } else if (((lp->media == _100Mb) && is_100_up(dev)) || + (((lp->media == _10Mb) || (lp->media == TP) || + (lp->media == BNC) || (lp->media == AUI)) && + is_10_up(dev))) { + next_tick = dc2114x_autoconf(dev); + } else { + lp->tcount++; + lp->media = INIT; + } + break; + + case _10Mb: + next_tick = 3000; + if (!lp->tx_enable) { + SET_10Mb; + de4x5_init_connection(dev); + } else { + if (!lp->linkOK && (lp->autosense == AUTO)) { + if (!is_10_up(dev) || (!lp->useSROM && is_spd_100(dev))) { + lp->media = INIT; + lp->tcount++; + next_tick = DE4X5_AUTOSENSE_MS; + } + } + } + break; + + case _100Mb: + next_tick = 3000; + if (!lp->tx_enable) { + SET_100Mb; + de4x5_init_connection(dev); + } else { + if (!lp->linkOK && (lp->autosense == AUTO)) { + if (!is_100_up(dev) || (!lp->useSROM && !is_spd_100(dev))) { + lp->media = INIT; + lp->tcount++; + next_tick = DE4X5_AUTOSENSE_MS; + } + } + } + break; + + default: + lp->tcount++; +printk("Huh?: media:%02x\n", lp->media); + lp->media = INIT; + break; + } + + return next_tick; +} + +static int +srom_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + + return lp->infoleaf_fn(dev); +} + +/* +** This mapping keeps the original media codes and FDX flag unchanged. +** While it isn't strictly necessary, it helps me for the moment... +** The early return avoids a media state / SROM media space clash. +*/ +static int +srom_map_media(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + + lp->fdx = 0; + if (lp->infoblock_media == lp->media) + return 0; + + switch(lp->infoblock_media) { + case SROM_10BASETF: + if (!lp->params.fdx) return -1; + lp->fdx = TRUE; + case SROM_10BASET: + if (lp->params.fdx && !lp->fdx) return -1; + if ((lp->chipset == DC21140) || ((lp->chipset & ~0x00ff) == DC2114x)) { + lp->media = _10Mb; + } else { + lp->media = TP; + } + break; + + case SROM_10BASE2: + lp->media = BNC; + break; + + case SROM_10BASE5: + lp->media = AUI; + break; + + case SROM_100BASETF: + if (!lp->params.fdx) return -1; + lp->fdx = TRUE; + case SROM_100BASET: + if (lp->params.fdx && !lp->fdx) return -1; + lp->media = _100Mb; + break; + + case SROM_100BASET4: + lp->media = _100Mb; + break; + + case SROM_100BASEFF: + if (!lp->params.fdx) return -1; + lp->fdx = TRUE; + case SROM_100BASEF: + if (lp->params.fdx && !lp->fdx) return -1; + lp->media = _100Mb; + break; + + case ANS: + lp->media = ANS; + break; + + default: + printk("%s: Bad media code [%d] detected in SROM!\n", dev->name, + lp->infoblock_media); + return -1; + break; + } + + return 0; +} + +static void +de4x5_init_connection(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if (lp->media != lp->c_media) { + de4x5_dbg_media(dev); + lp->c_media = lp->media; /* Stop scrolling media messages */ + } + + cli(); + de4x5_rst_desc_ring(dev); + de4x5_setup_intr(dev); + lp->tx_enable = YES; + dev->tbusy = 0; + sti(); + outl(POLL_DEMAND, DE4X5_TPD); + mark_bh(NET_BH); + + return; +} + +/* +** General PHY reset function. Some MII devices don't reset correctly +** since their MII address pins can float at voltages that are dependent +** on the signal pin use. Do a double reset to ensure a reset. +*/ +static int +de4x5_reset_phy(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int next_tick = 0; + + if ((lp->useSROM) || (lp->phy[lp->active].id)) { + if (lp->timeout < 0) { + if (lp->useSROM) { + if (lp->phy[lp->active].rst) { + srom_exec(dev, lp->phy[lp->active].rst); + srom_exec(dev, lp->phy[lp->active].rst); + } else if (lp->rst) { /* Type 5 infoblock reset */ + srom_exec(dev, lp->rst); + srom_exec(dev, lp->rst); + } + } else { + PHY_HARD_RESET; + } + if (lp->useMII) { + mii_wr(MII_CR_RST, MII_CR, lp->phy[lp->active].addr, DE4X5_MII); + } + } + if (lp->useMII) { + next_tick = test_mii_reg(dev, MII_CR, MII_CR_RST, FALSE, 500); + } + } else if (lp->chipset == DC21140) { + PHY_HARD_RESET; + } + + return next_tick; +} + +static int +test_media(struct device *dev, s32 irqs, s32 irq_mask, s32 csr13, s32 csr14, s32 csr15, s32 msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 sts, csr12; + + if (lp->timeout < 0) { + lp->timeout = msec/100; + if (!lp->useSROM) { /* Already done if by SROM, else dc2104[01] */ + reset_init_sia(dev, csr13, csr14, csr15); + } + + /* set up the interrupt mask */ + outl(irq_mask, DE4X5_IMR); + + /* clear all pending interrupts */ + sts = inl(DE4X5_STS); + outl(sts, DE4X5_STS); + + /* clear csr12 NRA and SRA bits */ + if ((lp->chipset == DC21041) || lp->useSROM) { + csr12 = inl(DE4X5_SISR); + outl(csr12, DE4X5_SISR); + } + } + + sts = inl(DE4X5_STS) & ~TIMER_CB; + + if (!(sts & irqs) && --lp->timeout) { + sts = 100 | TIMER_CB; + } else { + lp->timeout = -1; + } + + return sts; +} + +static int +test_tp(struct device *dev, s32 msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int sisr; + + if (lp->timeout < 0) { + lp->timeout = msec/100; + } + + sisr = (inl(DE4X5_SISR) & ~TIMER_CB) & (SISR_LKF | SISR_NCR); + + if (sisr && --lp->timeout) { + sisr = 100 | TIMER_CB; + } else { + lp->timeout = -1; + } + + return sisr; +} + +/* +** Samples the 100Mb Link State Signal. The sample interval is important +** because too fast a rate can give erroneous results and confuse the +** speed sense algorithm. +*/ +#define SAMPLE_INTERVAL 500 /* ms */ +#define SAMPLE_DELAY 2000 /* ms */ +static int +test_for_100Mb(struct device *dev, int msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int gep = 0, ret = ((lp->chipset & ~0x00ff)==DC2114x? -1 :GEP_SLNK); + + if (lp->timeout < 0) { + if ((msec/SAMPLE_INTERVAL) <= 0) return 0; + if (msec > SAMPLE_DELAY) { + lp->timeout = (msec - SAMPLE_DELAY)/SAMPLE_INTERVAL; + gep = SAMPLE_DELAY | TIMER_CB; + return gep; + } else { + lp->timeout = msec/SAMPLE_INTERVAL; + } + } + + if (lp->phy[lp->active].id || lp->useSROM) { + gep = is_100_up(dev) | is_spd_100(dev); + } else { + gep = (~gep_rd(dev) & (GEP_SLNK | GEP_LNP)); + } + if (!(gep & ret) && --lp->timeout) { + gep = SAMPLE_INTERVAL | TIMER_CB; + } else { + lp->timeout = -1; + } + + return gep; +} + +static int +wait_for_link(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + + if (lp->timeout < 0) { + lp->timeout = 1; + } + + if (lp->timeout--) { + return TIMER_CB; + } else { + lp->timeout = -1; + } + + return 0; +} + +/* +** +** +*/ +static int +test_mii_reg(struct device *dev, int reg, int mask, int pol, long msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int test; + u_long iobase = dev->base_addr; + + if (lp->timeout < 0) { + lp->timeout = msec/100; + } + + if (pol) pol = ~0; + reg = mii_rd((u_char)reg, lp->phy[lp->active].addr, DE4X5_MII) & mask; + test = (reg ^ pol) & mask; + + if (test && --lp->timeout) { + reg = 100 | TIMER_CB; + } else { + lp->timeout = -1; + } + + return reg; +} + +static int +is_spd_100(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int spd; + + if (lp->useMII) { + spd = mii_rd(lp->phy[lp->active].spd.reg, lp->phy[lp->active].addr, DE4X5_MII); + spd = ~(spd ^ lp->phy[lp->active].spd.value); + spd &= lp->phy[lp->active].spd.mask; + } else if (!lp->useSROM) { /* de500-xa */ + spd = ((~gep_rd(dev)) & GEP_SLNK); + } else { + if ((lp->ibn == 2) || !lp->asBitValid) + return ((lp->chipset == DC21143)?(~inl(DE4X5_SISR)&SISR_LS100):0); + + spd = (lp->asBitValid & (lp->asPolarity ^ (gep_rd(dev) & lp->asBit))) | + (lp->linkOK & ~lp->asBitValid); + } + + return spd; +} + +static int +is_100_up(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if (lp->useMII) { + /* Double read for sticky bits & temporary drops */ + mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII); + return (mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII) & MII_SR_LKS); + } else if (!lp->useSROM) { /* de500-xa */ + return ((~gep_rd(dev)) & GEP_SLNK); + } else { + if ((lp->ibn == 2) || !lp->asBitValid) + return ((lp->chipset == DC21143)?(~inl(DE4X5_SISR)&SISR_LS100):0); + + return ((lp->asBitValid&(lp->asPolarity^(gep_rd(dev)&lp->asBit))) | + (lp->linkOK & ~lp->asBitValid)); + } +} + +static int +is_10_up(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if (lp->useMII) { + /* Double read for sticky bits & temporary drops */ + mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII); + return (mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII) & MII_SR_LKS); + } else if (!lp->useSROM) { /* de500-xa */ + return ((~gep_rd(dev)) & GEP_LNP); + } else { + if ((lp->ibn == 2) || !lp->asBitValid) + return (((lp->chipset & ~0x00ff) == DC2114x) ? + (~inl(DE4X5_SISR)&SISR_LS10): + 0); + + return ((lp->asBitValid&(lp->asPolarity^(gep_rd(dev)&lp->asBit))) | + (lp->linkOK & ~lp->asBitValid)); + } +} + +static int +is_anc_capable(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if (lp->phy[lp->active].id && (!lp->useSROM || lp->useMII)) { + return (mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII)); + } else if ((lp->chipset & ~0x00ff) == DC2114x) { + return (inl(DE4X5_SISR) & SISR_LPN) >> 12; + } else { + return 0; + } +} + +/* +** Send a packet onto the media and watch for send errors that indicate the +** media is bad or unconnected. +*/ +static int +ping_media(struct device *dev, int msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int sisr; + + if (lp->timeout < 0) { + lp->timeout = msec/100; + + lp->tmp = lp->tx_new; /* Remember the ring position */ + load_packet(dev, lp->frame, TD_LS | TD_FS | sizeof(lp->frame), NULL); + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + outl(POLL_DEMAND, DE4X5_TPD); + } + + sisr = inl(DE4X5_SISR); + + if ((!(sisr & SISR_NCR)) && + ((s32)le32_to_cpu(lp->tx_ring[lp->tmp].status) < 0) && + (--lp->timeout)) { + sisr = 100 | TIMER_CB; + } else { + if ((!(sisr & SISR_NCR)) && + !(le32_to_cpu(lp->tx_ring[lp->tmp].status) & (T_OWN | TD_ES)) && + lp->timeout) { + sisr = 0; + } else { + sisr = 1; + } + lp->timeout = -1; + } + + return sisr; +} + +/* +** This function does 2 things: on Intels it kmalloc's another buffer to +** replace the one about to be passed up. On Alpha's it kmallocs a buffer +** into which the packet is copied. +*/ +static struct sk_buff * +de4x5_alloc_rx_buff(struct device *dev, int index, int len) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + struct sk_buff *p; + +#if !defined(__alpha__) && !defined(__powerpc__) && !defined(__sparc_v9__) && !defined(DE4X5_DO_MEMCPY) + struct sk_buff *ret; + u_long i=0, tmp; + + p = dev_alloc_skb(IEEE802_3_SZ + ALIGN + 2); + if (!p) return NULL; + + p->dev = dev; + tmp = virt_to_bus(p->data); + i = ((tmp + ALIGN) & ~ALIGN) - tmp; + skb_reserve(p, i); + lp->rx_ring[index].buf = tmp + i; + + ret = lp->rx_skb[index]; + lp->rx_skb[index] = p; + + if ((u_long) ret > 1) { + skb_put(ret, len); + } + + return ret; + +#else + if (lp->state != OPEN) return (struct sk_buff *)1; /* Fake out the open */ + + p = dev_alloc_skb(len + 2); + if (!p) return NULL; + + p->dev = dev; + skb_reserve(p, 2); /* Align */ + if (index < lp->rx_old) { /* Wrapped buffer */ + short tlen = (lp->rxRingSize - lp->rx_old) * RX_BUFF_SZ; + memcpy(skb_put(p,tlen), + bus_to_virt(le32_to_cpu(lp->rx_ring[lp->rx_old].buf)),tlen); + memcpy(skb_put(p,len-tlen), + bus_to_virt(le32_to_cpu(lp->rx_ring[0].buf)), len-tlen); + } else { /* Linear buffer */ + memcpy(skb_put(p,len), + bus_to_virt(le32_to_cpu(lp->rx_ring[lp->rx_old].buf)),len); + } + + return p; +#endif +} + +static void +de4x5_free_rx_buffs(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i; + + for (i=0; i<lp->rxRingSize; i++) { + if ((u_long) lp->rx_skb[i] > 1) { + dev_kfree_skb(lp->rx_skb[i], FREE_WRITE); + } + lp->rx_ring[i].status = 0; + lp->rx_skb[i] = (struct sk_buff *)1; /* Dummy entry */ + } + + return; +} + +static void +de4x5_free_tx_buffs(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i; + + for (i=0; i<lp->txRingSize; i++) { + if (lp->tx_skb[i]) { + dev_kfree_skb(lp->tx_skb[i], FREE_WRITE); + lp->tx_skb[i] = NULL; + } + lp->tx_ring[i].status = 0; + } + + /* Unload the locally queued packets */ + while (lp->cache.skb) { + dev_kfree_skb(de4x5_get_cache(dev), FREE_WRITE); + } + + return; +} + +/* +** When a user pulls a connection, the DECchip can end up in a +** 'running - waiting for end of transmission' state. This means that we +** have to perform a chip soft reset to ensure that we can synchronize +** the hardware and software and make any media probes using a loopback +** packet meaningful. +*/ +static void +de4x5_save_skbs(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 omr; + + if (!lp->cache.save_cnt) { + STOP_DE4X5; + de4x5_tx(dev); /* Flush any sent skb's */ + de4x5_free_tx_buffs(dev); + de4x5_cache_state(dev, DE4X5_SAVE_STATE); + de4x5_sw_reset(dev); + de4x5_cache_state(dev, DE4X5_RESTORE_STATE); + lp->cache.save_cnt++; + START_DE4X5; + } + + return; +} + +static void +de4x5_rst_desc_ring(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int i; + s32 omr; + + if (lp->cache.save_cnt) { + STOP_DE4X5; + outl(virt_to_bus(lp->rx_ring), DE4X5_RRBA); + outl(virt_to_bus(lp->tx_ring), DE4X5_TRBA); + + lp->rx_new = lp->rx_old = 0; + lp->tx_new = lp->tx_old = 0; + + for (i = 0; i < lp->rxRingSize; i++) { + lp->rx_ring[i].status = cpu_to_le32(R_OWN); + } + + for (i = 0; i < lp->txRingSize; i++) { + lp->tx_ring[i].status = cpu_to_le32(0); + } + + barrier(); + lp->cache.save_cnt--; + START_DE4X5; + } + + return; +} + +static void +de4x5_cache_state(struct device *dev, int flag) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + switch(flag) { + case DE4X5_SAVE_STATE: + lp->cache.csr0 = inl(DE4X5_BMR); + lp->cache.csr6 = (inl(DE4X5_OMR) & ~(OMR_ST | OMR_SR)); + lp->cache.csr7 = inl(DE4X5_IMR); + break; + + case DE4X5_RESTORE_STATE: + outl(lp->cache.csr0, DE4X5_BMR); + outl(lp->cache.csr6, DE4X5_OMR); + outl(lp->cache.csr7, DE4X5_IMR); + if (lp->chipset == DC21140) { + gep_wr(lp->cache.gepc, dev); + gep_wr(lp->cache.gep, dev); + } else { + reset_init_sia(dev, lp->cache.csr13, lp->cache.csr14, + lp->cache.csr15); + } + break; + } + + return; +} + +static void +de4x5_put_cache(struct device *dev, struct sk_buff *skb) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + struct sk_buff *p; + + if (lp->cache.skb) { + for (p=lp->cache.skb; p->next; p=p->next); + p->next = skb; + } else { + lp->cache.skb = skb; + } + skb->next = NULL; + + return; +} + +static void +de4x5_putb_cache(struct device *dev, struct sk_buff *skb) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + struct sk_buff *p = lp->cache.skb; + + lp->cache.skb = skb; + skb->next = p; + + return; +} + +static struct sk_buff * +de4x5_get_cache(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + struct sk_buff *p = lp->cache.skb; + + if (p) { + lp->cache.skb = p->next; + p->next = NULL; + } + + return p; +} + +/* +** Check the Auto Negotiation State. Return OK when a link pass interrupt +** is received and the auto-negotiation status is NWAY OK. +*/ +static int +test_ans(struct device *dev, s32 irqs, s32 irq_mask, s32 msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 sts, ans; + + if (lp->timeout < 0) { + lp->timeout = msec/100; + outl(irq_mask, DE4X5_IMR); + + /* clear all pending interrupts */ + sts = inl(DE4X5_STS); + outl(sts, DE4X5_STS); + } + + ans = inl(DE4X5_SISR) & SISR_ANS; + sts = inl(DE4X5_STS) & ~TIMER_CB; + + if (!(sts & irqs) && (ans ^ ANS_NWOK) && --lp->timeout) { + sts = 100 | TIMER_CB; + } else { + lp->timeout = -1; + } + + return sts; +} + +static void +de4x5_setup_intr(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 imr, sts; + + if (inl(DE4X5_OMR) & OMR_SR) { /* Only unmask if TX/RX is enabled */ + imr = 0; + UNMASK_IRQs; + sts = inl(DE4X5_STS); /* Reset any pending (stale) interrupts */ + outl(sts, DE4X5_STS); + ENABLE_IRQs; + } + + return; +} + +/* +** +*/ +static void +reset_init_sia(struct device *dev, s32 csr13, s32 csr14, s32 csr15) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + RESET_SIA; + if (lp->useSROM) { + if (lp->ibn == 3) { + srom_exec(dev, lp->phy[lp->active].rst); + srom_exec(dev, lp->phy[lp->active].gep); + outl(1, DE4X5_SICR); + return; + } else { + csr15 = lp->cache.csr15; + csr14 = lp->cache.csr14; + csr13 = lp->cache.csr13; + outl(csr15 | lp->cache.gepc, DE4X5_SIGR); + outl(csr15 | lp->cache.gep, DE4X5_SIGR); + } + } else { + outl(csr15, DE4X5_SIGR); + } + outl(csr14, DE4X5_STRR); + outl(csr13, DE4X5_SICR); + + de4x5_ms_delay(10); + + return; +} + +/* +** Create a loopback ethernet packet +*/ +static void +create_packet(struct device *dev, char *frame, int len) +{ + int i; + char *buf = frame; + + for (i=0; i<ETH_ALEN; i++) { /* Use this source address */ + *buf++ = dev->dev_addr[i]; + } + for (i=0; i<ETH_ALEN; i++) { /* Use this destination address */ + *buf++ = dev->dev_addr[i]; + } + + *buf++ = 0; /* Packet length (2 bytes) */ + *buf++ = 1; + + return; +} + +/* +** Known delay in microseconds +*/ +static void +de4x5_us_delay(u32 usec) +{ + udelay(usec); + + return; +} + +/* +** Known delay in milliseconds, in millisecond steps. +*/ +static void +de4x5_ms_delay(u32 msec) +{ + u_int i; + + for (i=0; i<msec; i++) { + de4x5_us_delay(1000); + } + + return; +} + + +/* +** Look for a particular board name in the EISA configuration space +*/ +static int +EISA_signature(char *name, s32 eisa_id) +{ + static c_char *signatures[] = DE4X5_SIGNATURE; + char ManCode[DE4X5_STRLEN]; + union { + s32 ID; + char Id[4]; + } Eisa; + int i, status = 0, siglen = sizeof(signatures)/sizeof(c_char *); + + *name = '\0'; + Eisa.ID = inl(eisa_id); + + ManCode[0]=(((Eisa.Id[0]>>2)&0x1f)+0x40); + ManCode[1]=(((Eisa.Id[1]&0xe0)>>5)+((Eisa.Id[0]&0x03)<<3)+0x40); + ManCode[2]=(((Eisa.Id[2]>>4)&0x0f)+0x30); + ManCode[3]=((Eisa.Id[2]&0x0f)+0x30); + ManCode[4]=(((Eisa.Id[3]>>4)&0x0f)+0x30); + ManCode[5]='\0'; + + for (i=0;i<siglen;i++) { + if (strstr(ManCode, signatures[i]) != NULL) { + strcpy(name,ManCode); + status = 1; + break; + } + } + + return status; /* return the device name string */ +} + +/* +** Look for a particular board name in the PCI configuration space +*/ +static int +PCI_signature(char *name, struct bus_type *lp) +{ + static c_char *de4x5_signatures[] = DE4X5_SIGNATURE; + int i, status = 0, siglen = sizeof(de4x5_signatures)/sizeof(c_char *); + + if (lp->chipset == DC21040) { + strcpy(name, "DE434/5"); + return status; + } else { /* Search for a DEC name in the SROM */ + int i = *((char *)&lp->srom + 19) * 3; + strncpy(name, (char *)&lp->srom + 26 + i, 8); + } + name[8] = '\0'; + for (i=0; i<siglen; i++) { + if (strstr(name,de4x5_signatures[i])!=NULL) break; + } + if (i == siglen) { + if (dec_only) { + *name = '\0'; + } else { /* Use chip name to avoid confusion */ + strcpy(name, (((lp->chipset == DC21040) ? "DC21040" : + ((lp->chipset == DC21041) ? "DC21041" : + ((lp->chipset == DC21140) ? "DC21140" : + ((lp->chipset == DC21142) ? "DC21142" : + ((lp->chipset == DC21143) ? "DC21143" : "UNKNOWN" + ))))))); + } + if (lp->chipset != DC21041) { + useSROM = TRUE; /* card is not recognisably DEC */ + } + } else if ((lp->chipset & ~0x00ff) == DC2114x) { + useSROM = TRUE; + } + + return status; +} + +/* +** Set up the Ethernet PROM counter to the start of the Ethernet address on +** the DC21040, else read the SROM for the other chips. +** The SROM may not be present in a multi-MAC card, so first read the +** MAC address and check for a bad address. If there is a bad one then exit +** immediately with the prior srom contents intact (the h/w address will +** be fixed up later). +*/ +static void +DevicePresent(u_long aprom_addr) +{ + int i, j=0; + struct bus_type *lp = &bus; + + if (lp->chipset == DC21040) { + if (lp->bus == EISA) { + enet_addr_rst(aprom_addr); /* Reset Ethernet Address ROM Pointer */ + } else { + outl(0, aprom_addr); /* Reset Ethernet Address ROM Pointer */ + } + } else { /* Read new srom */ + u_short tmp, *p = (short *)((char *)&lp->srom + SROM_HWADD); + for (i=0; i<(ETH_ALEN>>1); i++) { + tmp = srom_rd(aprom_addr, (SROM_HWADD>>1) + i); + *p = le16_to_cpu(tmp); + j += *p++; + } + if ((j == 0) || (j == 0x2fffd)) { + return; + } + + p=(short *)&lp->srom; + for (i=0; i<(sizeof(struct de4x5_srom)>>1); i++) { + tmp = srom_rd(aprom_addr, i); + *p++ = le16_to_cpu(tmp); + } + de4x5_dbg_srom((struct de4x5_srom *)&lp->srom); + } + + return; +} + +/* +** Since the write on the Enet PROM register doesn't seem to reset the PROM +** pointer correctly (at least on my DE425 EISA card), this routine should do +** it...from depca.c. +*/ +static void +enet_addr_rst(u_long aprom_addr) +{ + union { + struct { + u32 a; + u32 b; + } llsig; + char Sig[sizeof(u32) << 1]; + } dev; + short sigLength=0; + s8 data; + int i, j; + + dev.llsig.a = ETH_PROM_SIG; + dev.llsig.b = ETH_PROM_SIG; + sigLength = sizeof(u32) << 1; + + for (i=0,j=0;j<sigLength && i<PROBE_LENGTH+sigLength-1;i++) { + data = inb(aprom_addr); + if (dev.Sig[j] == data) { /* track signature */ + j++; + } else { /* lost signature; begin search again */ + if (data == dev.Sig[0]) { /* rare case.... */ + j=1; + } else { + j=0; + } + } + } + + return; +} + +/* +** For the bad status case and no SROM, then add one to the previous +** address. However, need to add one backwards in case we have 0xff +** as one or more of the bytes. Only the last 3 bytes should be checked +** as the first three are invariant - assigned to an organisation. +*/ +static int +get_hw_addr(struct device *dev) +{ + u_long iobase = dev->base_addr; + int broken, i, k, tmp, status = 0; + u_short j,chksum; + struct bus_type *lp = &bus; + + broken = de4x5_bad_srom(lp); + + for (i=0,k=0,j=0;j<3;j++) { + k <<= 1; + if (k > 0xffff) k-=0xffff; + + if (lp->bus == PCI) { + if (lp->chipset == DC21040) { + while ((tmp = inl(DE4X5_APROM)) < 0); + k += (u_char) tmp; + dev->dev_addr[i++] = (u_char) tmp; + while ((tmp = inl(DE4X5_APROM)) < 0); + k += (u_short) (tmp << 8); + dev->dev_addr[i++] = (u_char) tmp; + } else if (!broken) { + dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++; + dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++; + } else if ((broken == SMC) || (broken == ACCTON)) { + dev->dev_addr[i] = *((u_char *)&lp->srom + i); i++; + dev->dev_addr[i] = *((u_char *)&lp->srom + i); i++; + } + } else { + k += (u_char) (tmp = inb(EISA_APROM)); + dev->dev_addr[i++] = (u_char) tmp; + k += (u_short) ((tmp = inb(EISA_APROM)) << 8); + dev->dev_addr[i++] = (u_char) tmp; + } + + if (k > 0xffff) k-=0xffff; + } + if (k == 0xffff) k=0; + + if (lp->bus == PCI) { + if (lp->chipset == DC21040) { + while ((tmp = inl(DE4X5_APROM)) < 0); + chksum = (u_char) tmp; + while ((tmp = inl(DE4X5_APROM)) < 0); + chksum |= (u_short) (tmp << 8); + if ((k != chksum) && (dec_only)) status = -1; + } + } else { + chksum = (u_char) inb(EISA_APROM); + chksum |= (u_short) (inb(EISA_APROM) << 8); + if ((k != chksum) && (dec_only)) status = -1; + } + + /* If possible, try to fix a broken card - SMC only so far */ + srom_repair(dev, broken); + +#ifdef CONFIG_PMAC + /* + ** If the address starts with 00 a0, we have to bit-reverse + ** each byte of the address. + */ + if (dev->dev_addr[0] == 0 && dev->dev_addr[1] == 0xa0) { + for (i = 0; i < ETH_ALEN; ++i) { + int x = dev->dev_addr[i]; + x = ((x & 0xf) << 4) + ((x & 0xf0) >> 4); + x = ((x & 0x33) << 2) + ((x & 0xcc) >> 2); + dev->dev_addr[i] = ((x & 0x55) << 1) + ((x & 0xaa) >> 1); + } + } +#endif /* CONFIG_PMAC */ + + /* Test for a bad enet address */ + status = test_bad_enet(dev, status); + + return status; +} + +/* +** Test for enet addresses in the first 32 bytes. The built-in strncmp +** didn't seem to work here...? +*/ +static int +de4x5_bad_srom(struct bus_type *lp) +{ + int i, status = 0; + + for (i=0; i<sizeof(enet_det)/ETH_ALEN; i++) { + if (!de4x5_strncmp((char *)&lp->srom, (char *)&enet_det[i], 3) && + !de4x5_strncmp((char *)&lp->srom+0x10, (char *)&enet_det[i], 3)) { + if (i == 0) { + status = SMC; + } else if (i == 1) { + status = ACCTON; + } + break; + } + } + + return status; +} + +static int +de4x5_strncmp(char *a, char *b, int n) +{ + int ret=0; + + for (;n && !ret;n--) { + ret = *a++ - *b++; + } + + return ret; +} + +static void +srom_repair(struct device *dev, int card) +{ + struct bus_type *lp = &bus; + + switch(card) { + case SMC: + memset((char *)&bus.srom, 0, sizeof(struct de4x5_srom)); + memcpy(lp->srom.ieee_addr, (char *)dev->dev_addr, ETH_ALEN); + memcpy(lp->srom.info, (char *)&srom_repair_info[SMC-1], 100); + useSROM = TRUE; + break; + } + + return; +} + +/* +** Assume that the irq's do not follow the PCI spec - this is seems +** to be true so far (2 for 2). +*/ +static int +test_bad_enet(struct device *dev, int status) +{ + struct bus_type *lp = &bus; + int i, tmp; + + for (tmp=0,i=0; i<ETH_ALEN; i++) tmp += (u_char)dev->dev_addr[i]; + if ((tmp == 0) || (tmp == 0x5fa)) { + if ((lp->chipset == last.chipset) && + (lp->bus_num == last.bus) && (lp->bus_num > 0)) { + for (i=0; i<ETH_ALEN; i++) dev->dev_addr[i] = last.addr[i]; + for (i=ETH_ALEN-1; i>2; --i) { + dev->dev_addr[i] += 1; + if (dev->dev_addr[i] != 0) break; + } + for (i=0; i<ETH_ALEN; i++) last.addr[i] = dev->dev_addr[i]; + if (!an_exception(lp)) { + dev->irq = last.irq; + } + + status = 0; + } + } else if (!status) { + last.chipset = lp->chipset; + last.bus = lp->bus_num; + last.irq = dev->irq; + for (i=0; i<ETH_ALEN; i++) last.addr[i] = dev->dev_addr[i]; + } + + return status; +} + +/* +** List of board exceptions with correctly wired IRQs +*/ +static int +an_exception(struct bus_type *lp) +{ + if ((*(u_short *)lp->srom.sub_vendor_id == 0x00c0) && + (*(u_short *)lp->srom.sub_system_id == 0x95e0)) { + return -1; + } + + return 0; +} + +/* +** SROM Read +*/ +static short +srom_rd(u_long addr, u_char offset) +{ + sendto_srom(SROM_RD | SROM_SR, addr); + + srom_latch(SROM_RD | SROM_SR | DT_CS, addr); + srom_command(SROM_RD | SROM_SR | DT_IN | DT_CS, addr); + srom_address(SROM_RD | SROM_SR | DT_CS, addr, offset); + + return srom_data(SROM_RD | SROM_SR | DT_CS, addr); +} + +static void +srom_latch(u_int command, u_long addr) +{ + sendto_srom(command, addr); + sendto_srom(command | DT_CLK, addr); + sendto_srom(command, addr); + + return; +} + +static void +srom_command(u_int command, u_long addr) +{ + srom_latch(command, addr); + srom_latch(command, addr); + srom_latch((command & 0x0000ff00) | DT_CS, addr); + + return; +} + +static void +srom_address(u_int command, u_long addr, u_char offset) +{ + int i; + char a; + + a = (char)(offset << 2); + for (i=0; i<6; i++, a <<= 1) { + srom_latch(command | ((a < 0) ? DT_IN : 0), addr); + } + de4x5_us_delay(1); + + i = (getfrom_srom(addr) >> 3) & 0x01; + + return; +} + +static short +srom_data(u_int command, u_long addr) +{ + int i; + short word = 0; + s32 tmp; + + for (i=0; i<16; i++) { + sendto_srom(command | DT_CLK, addr); + tmp = getfrom_srom(addr); + sendto_srom(command, addr); + + word = (word << 1) | ((tmp >> 3) & 0x01); + } + + sendto_srom(command & 0x0000ff00, addr); + + return word; +} + +/* +static void +srom_busy(u_int command, u_long addr) +{ + sendto_srom((command & 0x0000ff00) | DT_CS, addr); + + while (!((getfrom_srom(addr) >> 3) & 0x01)) { + de4x5_ms_delay(1); + } + + sendto_srom(command & 0x0000ff00, addr); + + return; +} +*/ + +static void +sendto_srom(u_int command, u_long addr) +{ + outl(command, addr); + udelay(1); + + return; +} + +static int +getfrom_srom(u_long addr) +{ + s32 tmp; + + tmp = inl(addr); + udelay(1); + + return tmp; +} + +static int +srom_infoleaf_info(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i, count; + u_char *p; + + /* Find the infoleaf decoder function that matches this chipset */ + for (i=0; i<INFOLEAF_SIZE; i++) { + if (lp->chipset == infoleaf_array[i].chipset) break; + } + if (i == INFOLEAF_SIZE) { + lp->useSROM = FALSE; + printk("%s: Cannot find correct chipset for SROM decoding!\n", + dev->name); + return -ENXIO; + } + + lp->infoleaf_fn = infoleaf_array[i].fn; + + /* Find the information offset that this function should use */ + count = *((u_char *)&lp->srom + 19); + p = (u_char *)&lp->srom + 26; + + if (count > 1) { + for (i=count; i; --i, p+=3) { + if (lp->device == *p) break; + } + if (i == 0) { + lp->useSROM = FALSE; + printk("%s: Cannot find correct PCI device [%d] for SROM decoding!\n", + dev->name, lp->device); + return -ENXIO; + } + } + + lp->infoleaf_offset = TWIDDLE(p+1); + + return 0; +} + +/* +** This routine loads any type 1 or 3 MII info into the mii device +** struct and executes any type 5 code to reset PHY devices for this +** controller. +** The info for the MII devices will be valid since the index used +** will follow the discovery process from MII address 1-31 then 0. +*/ +static void +srom_init(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char *p = (u_char *)&lp->srom + lp->infoleaf_offset; + u_char count; + + p+=2; + if (lp->chipset == DC21140) { + lp->cache.gepc = (*p++ | GEP_CTRL); + gep_wr(lp->cache.gepc, dev); + } + + /* Block count */ + count = *p++; + + /* Jump the infoblocks to find types */ + for (;count; --count) { + if (*p < 128) { + p += COMPACT_LEN; + } else if (*(p+1) == 5) { + type5_infoblock(dev, 1, p); + p += ((*p & BLOCK_LEN) + 1); + } else if (*(p+1) == 4) { + p += ((*p & BLOCK_LEN) + 1); + } else if (*(p+1) == 3) { + type3_infoblock(dev, 1, p); + p += ((*p & BLOCK_LEN) + 1); + } else if (*(p+1) == 2) { + p += ((*p & BLOCK_LEN) + 1); + } else if (*(p+1) == 1) { + type1_infoblock(dev, 1, p); + p += ((*p & BLOCK_LEN) + 1); + } else { + p += ((*p & BLOCK_LEN) + 1); + } + } + + return; +} + +/* +** A generic routine that writes GEP control, data and reset information +** to the GEP register (21140) or csr15 GEP portion (2114[23]). +*/ +static void +srom_exec(struct device *dev, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + u_char count = (p ? *p++ : 0); + u_short *w = (u_short *)p; + + if (((lp->ibn != 1) && (lp->ibn != 3) && (lp->ibn != 5)) || !count) return; + + if (lp->chipset != DC21140) RESET_SIA; + + while (count--) { + gep_wr(((lp->chipset==DC21140) && (lp->ibn!=5) ? + *p++ : TWIDDLE(w++)), dev); + udelay(2000); /* 2ms per action */ + } + + if (lp->chipset != DC21140) { + outl(lp->cache.csr14, DE4X5_STRR); + outl(lp->cache.csr13, DE4X5_SICR); + } + + return; +} + +/* +** Basically this function is a NOP since it will never be called, +** unless I implement the DC21041 SROM functions. There's no need +** since the existing code will be satisfactory for all boards. +*/ +static int +dc21041_infoleaf(struct device *dev) +{ + return DE4X5_AUTOSENSE_MS; +} + +static int +dc21140_infoleaf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char count = 0; + u_char *p = (u_char *)&lp->srom + lp->infoleaf_offset; + int next_tick = DE4X5_AUTOSENSE_MS; + + /* Read the connection type */ + p+=2; + + /* GEP control */ + lp->cache.gepc = (*p++ | GEP_CTRL); + + /* Block count */ + count = *p++; + + /* Recursively figure out the info blocks */ + if (*p < 128) { + next_tick = dc_infoblock[COMPACT](dev, count, p); + } else { + next_tick = dc_infoblock[*(p+1)](dev, count, p); + } + + if (lp->tcount == count) { + lp->media = NC; + if (lp->media != lp->c_media) { + de4x5_dbg_media(dev); + lp->c_media = lp->media; + } + lp->media = INIT; + lp->tcount = 0; + lp->tx_enable = FALSE; + } + + return next_tick & ~TIMER_CB; +} + +static int +dc21142_infoleaf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char count = 0; + u_char *p = (u_char *)&lp->srom + lp->infoleaf_offset; + int next_tick = DE4X5_AUTOSENSE_MS; + + /* Read the connection type */ + p+=2; + + /* Block count */ + count = *p++; + + /* Recursively figure out the info blocks */ + if (*p < 128) { + next_tick = dc_infoblock[COMPACT](dev, count, p); + } else { + next_tick = dc_infoblock[*(p+1)](dev, count, p); + } + + if (lp->tcount == count) { + lp->media = NC; + if (lp->media != lp->c_media) { + de4x5_dbg_media(dev); + lp->c_media = lp->media; + } + lp->media = INIT; + lp->tcount = 0; + lp->tx_enable = FALSE; + } + + return next_tick & ~TIMER_CB; +} + +static int +dc21143_infoleaf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char count = 0; + u_char *p = (u_char *)&lp->srom + lp->infoleaf_offset; + int next_tick = DE4X5_AUTOSENSE_MS; + + /* Read the connection type */ + p+=2; + + /* Block count */ + count = *p++; + + /* Recursively figure out the info blocks */ + if (*p < 128) { + next_tick = dc_infoblock[COMPACT](dev, count, p); + } else { + next_tick = dc_infoblock[*(p+1)](dev, count, p); + } + if (lp->tcount == count) { + lp->media = NC; + if (lp->media != lp->c_media) { + de4x5_dbg_media(dev); + lp->c_media = lp->media; + } + lp->media = INIT; + lp->tcount = 0; + lp->tx_enable = FALSE; + } + + return next_tick & ~TIMER_CB; +} + +/* +** The compact infoblock is only designed for DC21140[A] chips, so +** we'll reuse the dc21140m_autoconf function. Non MII media only. +*/ +static int +compact_infoblock(struct device *dev, u_char count, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char flags, csr6; + + /* Recursively figure out the info blocks */ + if (--count > lp->tcount) { + if (*(p+COMPACT_LEN) < 128) { + return dc_infoblock[COMPACT](dev, count, p+COMPACT_LEN); + } else { + return dc_infoblock[*(p+COMPACT_LEN+1)](dev, count, p+COMPACT_LEN); + } + } + + if ((lp->media == INIT) && (lp->timeout < 0)) { + lp->ibn = COMPACT; + lp->active = 0; + gep_wr(lp->cache.gepc, dev); + lp->infoblock_media = (*p++) & COMPACT_MC; + lp->cache.gep = *p++; + csr6 = *p++; + flags = *p++; + + lp->asBitValid = (flags & 0x80) ? 0 : -1; + lp->defMedium = (flags & 0x40) ? -1 : 0; + lp->asBit = 1 << ((csr6 >> 1) & 0x07); + lp->asPolarity = ((csr6 & 0x80) ? -1 : 0) & lp->asBit; + lp->infoblock_csr6 = OMR_DEF | ((csr6 & 0x71) << 18); + lp->useMII = FALSE; + + de4x5_switch_mac_port(dev); + } + + return dc21140m_autoconf(dev); +} + +/* +** This block describes non MII media for the DC21140[A] only. +*/ +static int +type0_infoblock(struct device *dev, u_char count, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char flags, csr6, len = (*p & BLOCK_LEN)+1; + + /* Recursively figure out the info blocks */ + if (--count > lp->tcount) { + if (*(p+len) < 128) { + return dc_infoblock[COMPACT](dev, count, p+len); + } else { + return dc_infoblock[*(p+len+1)](dev, count, p+len); + } + } + + if ((lp->media == INIT) && (lp->timeout < 0)) { + lp->ibn = 0; + lp->active = 0; + gep_wr(lp->cache.gepc, dev); + p+=2; + lp->infoblock_media = (*p++) & BLOCK0_MC; + lp->cache.gep = *p++; + csr6 = *p++; + flags = *p++; + + lp->asBitValid = (flags & 0x80) ? 0 : -1; + lp->defMedium = (flags & 0x40) ? -1 : 0; + lp->asBit = 1 << ((csr6 >> 1) & 0x07); + lp->asPolarity = ((csr6 & 0x80) ? -1 : 0) & lp->asBit; + lp->infoblock_csr6 = OMR_DEF | ((csr6 & 0x71) << 18); + lp->useMII = FALSE; + + de4x5_switch_mac_port(dev); + } + + return dc21140m_autoconf(dev); +} + +/* These functions are under construction! */ + +static int +type1_infoblock(struct device *dev, u_char count, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char len = (*p & BLOCK_LEN)+1; + + /* Recursively figure out the info blocks */ + if (--count > lp->tcount) { + if (*(p+len) < 128) { + return dc_infoblock[COMPACT](dev, count, p+len); + } else { + return dc_infoblock[*(p+len+1)](dev, count, p+len); + } + } + + p += 2; + if (lp->state == INITIALISED) { + lp->ibn = 1; + lp->active = *p++; + lp->phy[lp->active].gep = (*p ? p : 0); p += (*p + 1); + lp->phy[lp->active].rst = (*p ? p : 0); p += (*p + 1); + lp->phy[lp->active].mc = TWIDDLE(p); p += 2; + lp->phy[lp->active].ana = TWIDDLE(p); p += 2; + lp->phy[lp->active].fdx = TWIDDLE(p); p += 2; + lp->phy[lp->active].ttm = TWIDDLE(p); + return 0; + } else if ((lp->media == INIT) && (lp->timeout < 0)) { + lp->ibn = 1; + lp->active = *p; + lp->infoblock_csr6 = OMR_MII_100; + lp->useMII = TRUE; + lp->infoblock_media = ANS; + + de4x5_switch_mac_port(dev); + } + + return dc21140m_autoconf(dev); +} + +static int +type2_infoblock(struct device *dev, u_char count, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char len = (*p & BLOCK_LEN)+1; + + /* Recursively figure out the info blocks */ + if (--count > lp->tcount) { + if (*(p+len) < 128) { + return dc_infoblock[COMPACT](dev, count, p+len); + } else { + return dc_infoblock[*(p+len+1)](dev, count, p+len); + } + } + + if ((lp->media == INIT) && (lp->timeout < 0)) { + lp->ibn = 2; + lp->active = 0; + p += 2; + lp->infoblock_media = (*p) & MEDIA_CODE; + + if ((*p++) & EXT_FIELD) { + lp->cache.csr13 = TWIDDLE(p); p += 2; + lp->cache.csr14 = TWIDDLE(p); p += 2; + lp->cache.csr15 = TWIDDLE(p); p += 2; + } else { + lp->cache.csr13 = CSR13; + lp->cache.csr14 = CSR14; + lp->cache.csr15 = CSR15; + } + lp->cache.gepc = ((s32)(TWIDDLE(p)) << 16); p += 2; + lp->cache.gep = ((s32)(TWIDDLE(p)) << 16); + lp->infoblock_csr6 = OMR_SIA; + lp->useMII = FALSE; + + de4x5_switch_mac_port(dev); + } + + return dc2114x_autoconf(dev); +} + +static int +type3_infoblock(struct device *dev, u_char count, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char len = (*p & BLOCK_LEN)+1; + + /* Recursively figure out the info blocks */ + if (--count > lp->tcount) { + if (*(p+len) < 128) { + return dc_infoblock[COMPACT](dev, count, p+len); + } else { + return dc_infoblock[*(p+len+1)](dev, count, p+len); + } + } + + p += 2; + if (lp->state == INITIALISED) { + lp->ibn = 3; + lp->active = *p++; + lp->phy[lp->active].gep = (*p ? p : 0); p += (2 * (*p) + 1); + lp->phy[lp->active].rst = (*p ? p : 0); p += (2 * (*p) + 1); + lp->phy[lp->active].mc = TWIDDLE(p); p += 2; + lp->phy[lp->active].ana = TWIDDLE(p); p += 2; + lp->phy[lp->active].fdx = TWIDDLE(p); p += 2; + lp->phy[lp->active].ttm = TWIDDLE(p); p += 2; + lp->phy[lp->active].mci = *p; + return 0; + } else if ((lp->media == INIT) && (lp->timeout < 0)) { + lp->ibn = 3; + lp->active = *p; + lp->infoblock_csr6 = OMR_MII_100; + lp->useMII = TRUE; + lp->infoblock_media = ANS; + + de4x5_switch_mac_port(dev); + } + + return dc2114x_autoconf(dev); +} + +static int +type4_infoblock(struct device *dev, u_char count, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char flags, csr6, len = (*p & BLOCK_LEN)+1; + + /* Recursively figure out the info blocks */ + if (--count > lp->tcount) { + if (*(p+len) < 128) { + return dc_infoblock[COMPACT](dev, count, p+len); + } else { + return dc_infoblock[*(p+len+1)](dev, count, p+len); + } + } + + if ((lp->media == INIT) && (lp->timeout < 0)) { + lp->ibn = 4; + lp->active = 0; + p+=2; + lp->infoblock_media = (*p++) & MEDIA_CODE; + lp->cache.csr13 = CSR13; /* Hard coded defaults */ + lp->cache.csr14 = CSR14; + lp->cache.csr15 = CSR15; + lp->cache.gepc = ((s32)(TWIDDLE(p)) << 16); p += 2; + lp->cache.gep = ((s32)(TWIDDLE(p)) << 16); p += 2; + csr6 = *p++; + flags = *p++; + + lp->asBitValid = (flags & 0x80) ? 0 : -1; + lp->defMedium = (flags & 0x40) ? -1 : 0; + lp->asBit = 1 << ((csr6 >> 1) & 0x07); + lp->asPolarity = ((csr6 & 0x80) ? -1 : 0) & lp->asBit; + lp->infoblock_csr6 = OMR_DEF | ((csr6 & 0x71) << 18); + lp->useMII = FALSE; + + de4x5_switch_mac_port(dev); + } + + return dc2114x_autoconf(dev); +} + +/* +** This block type provides information for resetting external devices +** (chips) through the General Purpose Register. +*/ +static int +type5_infoblock(struct device *dev, u_char count, u_char *p) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_char len = (*p & BLOCK_LEN)+1; + + /* Recursively figure out the info blocks */ + if (--count > lp->tcount) { + if (*(p+len) < 128) { + return dc_infoblock[COMPACT](dev, count, p+len); + } else { + return dc_infoblock[*(p+len+1)](dev, count, p+len); + } + } + + /* Must be initializing to run this code */ + if ((lp->state == INITIALISED) || (lp->media == INIT)) { + p+=2; + lp->rst = p; + srom_exec(dev, lp->rst); + } + + return DE4X5_AUTOSENSE_MS; +} + +/* +** MII Read/Write +*/ + +static int +mii_rd(u_char phyreg, u_char phyaddr, u_long ioaddr) +{ + mii_wdata(MII_PREAMBLE, 2, ioaddr); /* Start of 34 bit preamble... */ + mii_wdata(MII_PREAMBLE, 32, ioaddr); /* ...continued */ + mii_wdata(MII_STRD, 4, ioaddr); /* SFD and Read operation */ + mii_address(phyaddr, ioaddr); /* PHY address to be accessed */ + mii_address(phyreg, ioaddr); /* PHY Register to read */ + mii_ta(MII_STRD, ioaddr); /* Turn around time - 2 MDC */ + + return mii_rdata(ioaddr); /* Read data */ +} + +static void +mii_wr(int data, u_char phyreg, u_char phyaddr, u_long ioaddr) +{ + mii_wdata(MII_PREAMBLE, 2, ioaddr); /* Start of 34 bit preamble... */ + mii_wdata(MII_PREAMBLE, 32, ioaddr); /* ...continued */ + mii_wdata(MII_STWR, 4, ioaddr); /* SFD and Write operation */ + mii_address(phyaddr, ioaddr); /* PHY address to be accessed */ + mii_address(phyreg, ioaddr); /* PHY Register to write */ + mii_ta(MII_STWR, ioaddr); /* Turn around time - 2 MDC */ + data = mii_swap(data, 16); /* Swap data bit ordering */ + mii_wdata(data, 16, ioaddr); /* Write data */ + + return; +} + +static int +mii_rdata(u_long ioaddr) +{ + int i; + s32 tmp = 0; + + for (i=0; i<16; i++) { + tmp <<= 1; + tmp |= getfrom_mii(MII_MRD | MII_RD, ioaddr); + } + + return tmp; +} + +static void +mii_wdata(int data, int len, u_long ioaddr) +{ + int i; + + for (i=0; i<len; i++) { + sendto_mii(MII_MWR | MII_WR, data, ioaddr); + data >>= 1; + } + + return; +} + +static void +mii_address(u_char addr, u_long ioaddr) +{ + int i; + + addr = mii_swap(addr, 5); + for (i=0; i<5; i++) { + sendto_mii(MII_MWR | MII_WR, addr, ioaddr); + addr >>= 1; + } + + return; +} + +static void +mii_ta(u_long rw, u_long ioaddr) +{ + if (rw == MII_STWR) { + sendto_mii(MII_MWR | MII_WR, 1, ioaddr); + sendto_mii(MII_MWR | MII_WR, 0, ioaddr); + } else { + getfrom_mii(MII_MRD | MII_RD, ioaddr); /* Tri-state MDIO */ + } + + return; +} + +static int +mii_swap(int data, int len) +{ + int i, tmp = 0; + + for (i=0; i<len; i++) { + tmp <<= 1; + tmp |= (data & 1); + data >>= 1; + } + + return tmp; +} + +static void +sendto_mii(u32 command, int data, u_long ioaddr) +{ + u32 j; + + j = (data & 1) << 17; + outl(command | j, ioaddr); + udelay(1); + outl(command | MII_MDC | j, ioaddr); + udelay(1); + + return; +} + +static int +getfrom_mii(u32 command, u_long ioaddr) +{ + outl(command, ioaddr); + udelay(1); + outl(command | MII_MDC, ioaddr); + udelay(1); + + return ((inl(ioaddr) >> 19) & 1); +} + +/* +** Here's 3 ways to calculate the OUI from the ID registers. +*/ +static int +mii_get_oui(u_char phyaddr, u_long ioaddr) +{ +/* + union { + u_short reg; + u_char breg[2]; + } a; + int i, r2, r3, ret=0;*/ + int r2, r3; + + /* Read r2 and r3 */ + r2 = mii_rd(MII_ID0, phyaddr, ioaddr); + r3 = mii_rd(MII_ID1, phyaddr, ioaddr); + /* SEEQ and Cypress way * / + / * Shuffle r2 and r3 * / + a.reg=0; + r3 = ((r3>>10)|(r2<<6))&0x0ff; + r2 = ((r2>>2)&0x3fff); + + / * Bit reverse r3 * / + for (i=0;i<8;i++) { + ret<<=1; + ret |= (r3&1); + r3>>=1; + } + + / * Bit reverse r2 * / + for (i=0;i<16;i++) { + a.reg<<=1; + a.reg |= (r2&1); + r2>>=1; + } + + / * Swap r2 bytes * / + i=a.breg[0]; + a.breg[0]=a.breg[1]; + a.breg[1]=i; + + return ((a.reg<<8)|ret); */ /* SEEQ and Cypress way */ +/* return ((r2<<6)|(u_int)(r3>>10)); */ /* NATIONAL and BROADCOM way */ + return r2; /* (I did it) My way */ +} + +/* +** The SROM spec forces us to search addresses [1-31 0]. Bummer. +*/ +static int +mii_get_phy(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + int i, j, k, n, limit=sizeof(phy_info)/sizeof(struct phy_table); + int id; + + lp->active = 0; + lp->useMII = TRUE; + + /* Search the MII address space for possible PHY devices */ + for (n=0, lp->mii_cnt=0, i=1; !((i==1) && (n==1)); i=(++i)%DE4X5_MAX_MII) { + lp->phy[lp->active].addr = i; + if (i==0) n++; /* Count cycles */ + while (de4x5_reset_phy(dev)<0) udelay(100);/* Wait for reset */ + id = mii_get_oui(i, DE4X5_MII); + if ((id == 0) || (id == 65535)) continue; /* Valid ID? */ + for (j=0; j<limit; j++) { /* Search PHY table */ + if (id != phy_info[j].id) continue; /* ID match? */ + for (k=0; lp->phy[k].id && (k < DE4X5_MAX_PHY); k++); + if (k < DE4X5_MAX_PHY) { + memcpy((char *)&lp->phy[k], + (char *)&phy_info[j], sizeof(struct phy_table)); + lp->phy[k].addr = i; + lp->mii_cnt++; + lp->active++; + } else { + goto purgatory; /* Stop the search */ + } + break; + } + if ((j == limit) && (i < DE4X5_MAX_MII)) { + for (k=0; lp->phy[k].id && (k < DE4X5_MAX_PHY); k++); + lp->phy[k].addr = i; + lp->phy[k].id = id; + lp->phy[k].spd.reg = GENERIC_REG; /* ANLPA register */ + lp->phy[k].spd.mask = GENERIC_MASK; /* 100Mb/s technologies */ + lp->phy[k].spd.value = GENERIC_VALUE; /* TX & T4, H/F Duplex */ + lp->mii_cnt++; + lp->active++; + printk("%s: Using generic MII device control. If the board doesn't operate, \nplease mail the following dump to the author:\n", dev->name); + j = de4x5_debug; + de4x5_debug |= DEBUG_MII; + de4x5_dbg_mii(dev, k); + de4x5_debug = j; + printk("\n"); + } + } + purgatory: + lp->active = 0; + if (lp->phy[0].id) { /* Reset the PHY devices */ + for (k=0; lp->phy[k].id && (k < DE4X5_MAX_PHY); k++) { /*For each PHY*/ + mii_wr(MII_CR_RST, MII_CR, lp->phy[k].addr, DE4X5_MII); + while (mii_rd(MII_CR, lp->phy[k].addr, DE4X5_MII) & MII_CR_RST); + + de4x5_dbg_mii(dev, k); + } + } + if (!lp->mii_cnt) lp->useMII = FALSE; + + return lp->mii_cnt; +} + +static char * +build_setup_frame(struct device *dev, int mode) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i; + char *pa = lp->setup_frame; + + /* Initialise the setup frame */ + if (mode == ALL) { + memset(lp->setup_frame, 0, SETUP_FRAME_LEN); + } + + if (lp->setup_f == HASH_PERF) { + for (pa=lp->setup_frame+IMPERF_PA_OFFSET, i=0; i<ETH_ALEN; i++) { + *(pa + i) = dev->dev_addr[i]; /* Host address */ + if (i & 0x01) pa += 2; + } + *(lp->setup_frame + (HASH_TABLE_LEN >> 3) - 3) = 0x80; + } else { + for (i=0; i<ETH_ALEN; i++) { /* Host address */ + *(pa + (i&1)) = dev->dev_addr[i]; + if (i & 0x01) pa += 4; + } + for (i=0; i<ETH_ALEN; i++) { /* Broadcast address */ + *(pa + (i&1)) = (char) 0xff; + if (i & 0x01) pa += 4; + } + } + + return pa; /* Points to the next entry */ +} + +static void +enable_ast(struct device *dev, u32 time_out) +{ + timeout(dev, (void *)&de4x5_ast, (u_long)dev, time_out); + + return; +} + +static void +disable_ast(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + + del_timer(&lp->timer); + + return; +} + +static long +de4x5_switch_mac_port(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + s32 omr; + + STOP_DE4X5; + + /* Assert the OMR_PS bit in CSR6 */ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR | + OMR_FDX)); + omr |= lp->infoblock_csr6; + if (omr & OMR_PS) omr |= OMR_HBD; + outl(omr, DE4X5_OMR); + + /* Soft Reset */ + RESET_DE4X5; + + /* Restore the GEP - especially for COMPACT and Type 0 Infoblocks */ + if (lp->chipset == DC21140) { + gep_wr(lp->cache.gepc, dev); + gep_wr(lp->cache.gep, dev); + } else if ((lp->chipset & ~0x0ff) == DC2114x) { + reset_init_sia(dev, lp->cache.csr13, lp->cache.csr14, lp->cache.csr15); + } + + /* Restore CSR6 */ + outl(omr, DE4X5_OMR); + + /* Reset CSR8 */ + inl(DE4X5_MFC); + + return omr; +} + +static void +gep_wr(s32 data, struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if (lp->chipset == DC21140) { + outl(data, DE4X5_GEP); + } else if ((lp->chipset & ~0x00ff) == DC2114x) { + outl((data<<16) | lp->cache.csr15, DE4X5_SIGR); + } + + return; +} + +static int +gep_rd(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if (lp->chipset == DC21140) { + return inl(DE4X5_GEP); + } else if ((lp->chipset & ~0x00ff) == DC2114x) { + return (inl(DE4X5_SIGR) & 0x000fffff); + } + + return 0; +} + +static void +timeout(struct device *dev, void (*fn)(u_long data), u_long data, u_long msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int dt; + + /* First, cancel any pending timer events */ + del_timer(&lp->timer); + + /* Convert msec to ticks */ + dt = (msec * HZ) / 1000; + if (dt==0) dt=1; + + /* Set up timer */ + lp->timer.expires = jiffies + dt; + lp->timer.function = fn; + lp->timer.data = data; + add_timer(&lp->timer); + + return; +} + +static void +yawn(struct device *dev, int state) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if ((lp->chipset == DC21040) || (lp->chipset == DC21140)) return; + + if(lp->bus == EISA) { + switch(state) { + case WAKEUP: + outb(WAKEUP, PCI_CFPM); + de4x5_ms_delay(10); + break; + + case SNOOZE: + outb(SNOOZE, PCI_CFPM); + break; + + case SLEEP: + outl(0, DE4X5_SICR); + outb(SLEEP, PCI_CFPM); + break; + } + } else { + switch(state) { + case WAKEUP: + pcibios_write_config_byte(lp->bus_num, lp->device << 3, + PCI_CFDA_PSM, WAKEUP); + de4x5_ms_delay(10); + break; + + case SNOOZE: + pcibios_write_config_byte(lp->bus_num, lp->device << 3, + PCI_CFDA_PSM, SNOOZE); + break; + + case SLEEP: + outl(0, DE4X5_SICR); + pcibios_write_config_byte(lp->bus_num, lp->device << 3, + PCI_CFDA_PSM, SLEEP); + break; + } + } + + return; +} + +static void +de4x5_parse_params(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + char *p, *q, t; + + lp->params.fdx = 0; + lp->params.autosense = AUTO; + + if (args == NULL) return; + + if ((p = strstr(args, dev->name))) { + if (!(q = strstr(p+strlen(dev->name), "eth"))) q = p + strlen(p); + t = *q; + *q = '\0'; + + if (strstr(p, "fdx") || strstr(p, "FDX")) lp->params.fdx = 1; + + if (strstr(p, "autosense") || strstr(p, "AUTOSENSE")) { + if (strstr(p, "TP")) { + lp->params.autosense = TP; + } else if (strstr(p, "TP_NW")) { + lp->params.autosense = TP_NW; + } else if (strstr(p, "BNC")) { + lp->params.autosense = BNC; + } else if (strstr(p, "AUI")) { + lp->params.autosense = AUI; + } else if (strstr(p, "BNC_AUI")) { + lp->params.autosense = BNC; + } else if (strstr(p, "10Mb")) { + lp->params.autosense = _10Mb; + } else if (strstr(p, "100Mb")) { + lp->params.autosense = _100Mb; + } else if (strstr(p, "AUTO")) { + lp->params.autosense = AUTO; + } + } + *q = t; + } + + return; +} + +static void +de4x5_dbg_open(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i; + + if (de4x5_debug & DEBUG_OPEN) { + printk("%s: de4x5 opening with irq %d\n",dev->name,dev->irq); + printk("\tphysical address: "); + for (i=0;i<6;i++) { + printk("%2.2x:",(short)dev->dev_addr[i]); + } + printk("\n"); + printk("Descriptor head addresses:\n"); + printk("\t0x%8.8lx 0x%8.8lx\n",(u_long)lp->rx_ring,(u_long)lp->tx_ring); + printk("Descriptor addresses:\nRX: "); + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8lx ",(u_long)&lp->rx_ring[i].status); + } + } + printk("...0x%8.8lx\n",(u_long)&lp->rx_ring[i].status); + printk("TX: "); + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8lx ", (u_long)&lp->tx_ring[i].status); + } + } + printk("...0x%8.8lx\n", (u_long)&lp->tx_ring[i].status); + printk("Descriptor buffers:\nRX: "); + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8x ",le32_to_cpu(lp->rx_ring[i].buf)); + } + } + printk("...0x%8.8x\n",le32_to_cpu(lp->rx_ring[i].buf)); + printk("TX: "); + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8x ", le32_to_cpu(lp->tx_ring[i].buf)); + } + } + printk("...0x%8.8x\n", le32_to_cpu(lp->tx_ring[i].buf)); + printk("Ring size: \nRX: %d\nTX: %d\n", + (short)lp->rxRingSize, + (short)lp->txRingSize); + } + + return; +} + +static void +de4x5_dbg_mii(struct device *dev, int k) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + u_long iobase = dev->base_addr; + + if (de4x5_debug & DEBUG_MII) { + printk("\nMII device address: %d\n", lp->phy[k].addr); + printk("MII CR: %x\n",mii_rd(MII_CR,lp->phy[k].addr,DE4X5_MII)); + printk("MII SR: %x\n",mii_rd(MII_SR,lp->phy[k].addr,DE4X5_MII)); + printk("MII ID0: %x\n",mii_rd(MII_ID0,lp->phy[k].addr,DE4X5_MII)); + printk("MII ID1: %x\n",mii_rd(MII_ID1,lp->phy[k].addr,DE4X5_MII)); + if (lp->phy[k].id != BROADCOM_T4) { + printk("MII ANA: %x\n",mii_rd(0x04,lp->phy[k].addr,DE4X5_MII)); + printk("MII ANC: %x\n",mii_rd(0x05,lp->phy[k].addr,DE4X5_MII)); + } + printk("MII 16: %x\n",mii_rd(0x10,lp->phy[k].addr,DE4X5_MII)); + if (lp->phy[k].id != BROADCOM_T4) { + printk("MII 17: %x\n",mii_rd(0x11,lp->phy[k].addr,DE4X5_MII)); + printk("MII 18: %x\n",mii_rd(0x12,lp->phy[k].addr,DE4X5_MII)); + } else { + printk("MII 20: %x\n",mii_rd(0x14,lp->phy[k].addr,DE4X5_MII)); + } + } + + return; +} + +static void +de4x5_dbg_media(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + + if (lp->media != lp->c_media) { + if (de4x5_debug & DEBUG_MEDIA) { + printk("%s: media is %s%s\n", dev->name, + (lp->media == NC ? "unconnected, link down or incompatible connection" : + (lp->media == TP ? "TP" : + (lp->media == ANS ? "TP/Nway" : + (lp->media == BNC ? "BNC" : + (lp->media == AUI ? "AUI" : + (lp->media == BNC_AUI ? "BNC/AUI" : + (lp->media == EXT_SIA ? "EXT SIA" : + (lp->media == _100Mb ? "100Mb/s" : + (lp->media == _10Mb ? "10Mb/s" : + "???" + ))))))))), (lp->fdx?" full duplex.":".")); + } + lp->c_media = lp->media; + } + + return; +} + +static void +de4x5_dbg_srom(struct de4x5_srom *p) +{ + int i; + + if (de4x5_debug & DEBUG_SROM) { + printk("Sub-system Vendor ID: %04x\n", *((u_short *)p->sub_vendor_id)); + printk("Sub-system ID: %04x\n", *((u_short *)p->sub_system_id)); + printk("ID Block CRC: %02x\n", (u_char)(p->id_block_crc)); + printk("SROM version: %02x\n", (u_char)(p->version)); + printk("# controllers: %02x\n", (u_char)(p->num_controllers)); + + printk("Hardware Address: "); + for (i=0;i<ETH_ALEN-1;i++) { + printk("%02x:", (u_char)*(p->ieee_addr+i)); + } + printk("%02x\n", (u_char)*(p->ieee_addr+i)); + printk("CRC checksum: %04x\n", (u_short)(p->chksum)); + for (i=0; i<64; i++) { + printk("%3d %04x\n", i<<1, (u_short)*((u_short *)p+i)); + } + } + + return; +} + +static void +de4x5_dbg_rx(struct sk_buff *skb, int len) +{ + int i, j; + + if (de4x5_debug & DEBUG_RX) { + printk("R: %02x:%02x:%02x:%02x:%02x:%02x <- %02x:%02x:%02x:%02x:%02x:%02x len/SAP:%02x%02x [%d]\n", + (u_char)skb->data[0], + (u_char)skb->data[1], + (u_char)skb->data[2], + (u_char)skb->data[3], + (u_char)skb->data[4], + (u_char)skb->data[5], + (u_char)skb->data[6], + (u_char)skb->data[7], + (u_char)skb->data[8], + (u_char)skb->data[9], + (u_char)skb->data[10], + (u_char)skb->data[11], + (u_char)skb->data[12], + (u_char)skb->data[13], + len); + if (de4x5_debug & DEBUG_RX) { + for (j=0; len>0;j+=16, len-=16) { + printk(" %03x: ",j); + for (i=0; i<16 && i<len; i++) { + printk("%02x ",(u_char)skb->data[i+j]); + } + printk("\n"); + } + } + } + + return; +} + +/* +** Perform IOCTL call functions here. Some are privileged operations and the +** effective uid is checked in those cases. In the normal course of events +** this function is only used for my testing. +*/ +static int +de4x5_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + struct de4x5_ioctl *ioc = (struct de4x5_ioctl *) &rq->ifr_data; + u_long iobase = dev->base_addr; + int i, j, status = 0; + s32 omr; + union { + u8 addr[144]; + u16 sval[72]; + u32 lval[36]; + } tmp; + + switch(ioc->cmd) { + case DE4X5_GET_HWADDR: /* Get the hardware address */ + ioc->len = ETH_ALEN; + status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len); + if (status) + break; + for (i=0; i<ETH_ALEN; i++) { + tmp.addr[i] = dev->dev_addr[i]; + } + copy_to_user(ioc->data, tmp.addr, ioc->len); + + break; + case DE4X5_SET_HWADDR: /* Set the hardware address */ + status = verify_area(VERIFY_READ, (void *)ioc->data, ETH_ALEN); + if (status) + break; + status = -EPERM; + if (!suser()) + break; + status = 0; + copy_from_user(tmp.addr, ioc->data, ETH_ALEN); + for (i=0; i<ETH_ALEN; i++) { + dev->dev_addr[i] = tmp.addr[i]; + } + build_setup_frame(dev, PHYS_ADDR_ONLY); + /* Set up the descriptor and give ownership to the card */ + while (test_and_set_bit(0, (void *)&dev->tbusy) != 0); + load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | + SETUP_FRAME_LEN, NULL); + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + outl(POLL_DEMAND, DE4X5_TPD); /* Start the TX */ + dev->tbusy = 0; /* Unlock the TX ring */ + + break; + case DE4X5_SET_PROM: /* Set Promiscuous Mode */ + if (suser()) { + omr = inl(DE4X5_OMR); + omr |= OMR_PR; + outl(omr, DE4X5_OMR); + dev->flags |= IFF_PROMISC; + } else { + status = -EPERM; + } + + break; + case DE4X5_CLR_PROM: /* Clear Promiscuous Mode */ + if (suser()) { + omr = inl(DE4X5_OMR); + omr &= ~OMR_PR; + outb(omr, DE4X5_OMR); + dev->flags &= ~IFF_PROMISC; + } else { + status = -EPERM; + } + + break; + case DE4X5_SAY_BOO: /* Say "Boo!" to the kernel log file */ + printk("%s: Boo!\n", dev->name); + + break; + case DE4X5_MCA_EN: /* Enable pass all multicast addressing */ + if (suser()) { + omr = inl(DE4X5_OMR); + omr |= OMR_PM; + outl(omr, DE4X5_OMR); + } else { + status = -EPERM; + } + + break; + case DE4X5_GET_STATS: /* Get the driver statistics */ + ioc->len = sizeof(lp->pktStats); + status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len); + if (status) + break; + + cli(); + copy_to_user(ioc->data, &lp->pktStats, ioc->len); + sti(); + + break; + case DE4X5_CLR_STATS: /* Zero out the driver statistics */ + if (suser()) { + cli(); + memset(&lp->pktStats, 0, sizeof(lp->pktStats)); + sti(); + } else { + status = -EPERM; + } + + break; + case DE4X5_GET_OMR: /* Get the OMR Register contents */ + tmp.addr[0] = inl(DE4X5_OMR); + if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, 1))) { + copy_to_user(ioc->data, tmp.addr, 1); + } + + break; + case DE4X5_SET_OMR: /* Set the OMR Register contents */ + if (suser()) { + if (!(status = verify_area(VERIFY_READ, (void *)ioc->data, 1))) { + copy_from_user(tmp.addr, ioc->data, 1); + outl(tmp.addr[0], DE4X5_OMR); + } + } else { + status = -EPERM; + } + + break; + case DE4X5_GET_REG: /* Get the DE4X5 Registers */ + j = 0; + tmp.lval[0] = inl(DE4X5_STS); j+=4; + tmp.lval[1] = inl(DE4X5_BMR); j+=4; + tmp.lval[2] = inl(DE4X5_IMR); j+=4; + tmp.lval[3] = inl(DE4X5_OMR); j+=4; + tmp.lval[4] = inl(DE4X5_SISR); j+=4; + tmp.lval[5] = inl(DE4X5_SICR); j+=4; + tmp.lval[6] = inl(DE4X5_STRR); j+=4; + tmp.lval[7] = inl(DE4X5_SIGR); j+=4; + ioc->len = j; + if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) { + copy_to_user(ioc->data, tmp.addr, ioc->len); + } + break; + +#define DE4X5_DUMP 0x0f /* Dump the DE4X5 Status */ +/* + case DE4X5_DUMP: + j = 0; + tmp.addr[j++] = dev->irq; + for (i=0; i<ETH_ALEN; i++) { + tmp.addr[j++] = dev->dev_addr[i]; + } + tmp.addr[j++] = lp->rxRingSize; + tmp.lval[j>>2] = (long)lp->rx_ring; j+=4; + tmp.lval[j>>2] = (long)lp->tx_ring; j+=4; + + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (long)&lp->rx_ring[i].status; j+=4; + } + } + tmp.lval[j>>2] = (long)&lp->rx_ring[i].status; j+=4; + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (long)&lp->tx_ring[i].status; j+=4; + } + } + tmp.lval[j>>2] = (long)&lp->tx_ring[i].status; j+=4; + + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (s32)le32_to_cpu(lp->rx_ring[i].buf); j+=4; + } + } + tmp.lval[j>>2] = (s32)le32_to_cpu(lp->rx_ring[i].buf); j+=4; + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (s32)le32_to_cpu(lp->tx_ring[i].buf); j+=4; + } + } + tmp.lval[j>>2] = (s32)le32_to_cpu(lp->tx_ring[i].buf); j+=4; + + for (i=0;i<lp->rxRingSize;i++){ + tmp.lval[j>>2] = le32_to_cpu(lp->rx_ring[i].status); j+=4; + } + for (i=0;i<lp->txRingSize;i++){ + tmp.lval[j>>2] = le32_to_cpu(lp->tx_ring[i].status); j+=4; + } + + tmp.lval[j>>2] = inl(DE4X5_BMR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_TPD); j+=4; + tmp.lval[j>>2] = inl(DE4X5_RPD); j+=4; + tmp.lval[j>>2] = inl(DE4X5_RRBA); j+=4; + tmp.lval[j>>2] = inl(DE4X5_TRBA); j+=4; + tmp.lval[j>>2] = inl(DE4X5_STS); j+=4; + tmp.lval[j>>2] = inl(DE4X5_OMR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_IMR); j+=4; + tmp.lval[j>>2] = lp->chipset; j+=4; + if (lp->chipset == DC21140) { + tmp.lval[j>>2] = gep_rd(dev); j+=4; + } else { + tmp.lval[j>>2] = inl(DE4X5_SISR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_SICR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_STRR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_SIGR); j+=4; + } + tmp.lval[j>>2] = lp->phy[lp->active].id; j+=4; + if (lp->phy[lp->active].id && (!lp->useSROM || lp->useMII)) { + tmp.lval[j>>2] = lp->active; j+=4; + tmp.lval[j>>2]=mii_rd(MII_CR,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + tmp.lval[j>>2]=mii_rd(MII_SR,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + tmp.lval[j>>2]=mii_rd(MII_ID0,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + tmp.lval[j>>2]=mii_rd(MII_ID1,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + if (lp->phy[lp->active].id != BROADCOM_T4) { + tmp.lval[j>>2]=mii_rd(MII_ANA,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + tmp.lval[j>>2]=mii_rd(MII_ANLPA,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + } + tmp.lval[j>>2]=mii_rd(0x10,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + if (lp->phy[lp->active].id != BROADCOM_T4) { + tmp.lval[j>>2]=mii_rd(0x11,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + tmp.lval[j>>2]=mii_rd(0x12,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + } else { + tmp.lval[j>>2]=mii_rd(0x14,lp->phy[lp->active].addr,DE4X5_MII); j+=4; + } + } + + tmp.addr[j++] = lp->txRingSize; + tmp.addr[j++] = dev->tbusy; + + ioc->len = j; + if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) { + copy_to_user(ioc->data, tmp.addr, ioc->len); + } + + break; +*/ + default: + status = -EOPNOTSUPP; + } + + return status; +} + +#ifdef MODULE +/* +** Note now that module autoprobing is allowed under EISA and PCI. The +** IRQ lines will not be auto-detected; instead I'll rely on the BIOSes +** to "do the right thing". +*/ +#define LP(a) ((struct de4x5_private *)(a)) +static struct device *mdev = NULL; +static int io=0x0;/* EDIT THIS LINE FOR YOUR CONFIGURATION IF NEEDED */ +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,0) +MODULE_PARM(io, "i"); +#endif /* LINUX_VERSION_CODE */ + +int +init_module(void) +{ + int i, num, status = -EIO; + struct device *p; + + num = count_adapters(); + + for (i=0; i<num; i++) { + if ((p = insert_device(NULL, io, de4x5_probe)) == NULL) + return -ENOMEM; + + if (!mdev) mdev = p; + + if (register_netdev(p) != 0) { + kfree(p); + } else { + status = 0; /* At least one adapter will work */ + lastModule = p; + } + } + + return status; +} + +void +cleanup_module(void) +{ + while (mdev != NULL) { + mdev = unlink_modules(mdev); + } + + return; +} + +static struct device * +unlink_modules(struct device *p) +{ + struct device *next = NULL; + + if (p->priv) { /* Private areas allocated? */ + struct de4x5_private *lp = (struct de4x5_private *)p->priv; + + next = lp->next_module; + if (lp->cache.buf) { /* MAC buffers allocated? */ + kfree(lp->cache.buf); /* Free the MAC buffers */ + } + kfree(lp->cache.priv); /* Free the private area */ + release_region(p->base_addr, (lp->bus == PCI ? + DE4X5_PCI_TOTAL_SIZE : + DE4X5_EISA_TOTAL_SIZE)); + } + unregister_netdev(p); + kfree(p); /* Free the device structure */ + + return next; +} + +static int +count_adapters(void) +{ + int i, j=0; + u_char pb, dev_fn, dev_num; + u_short dev_id, vendor; + u_int class = DE4X5_CLASS_CODE; + u_int device; + +#if !defined(__sparc_v9__) && !defined(__powerpc__) && !defined(__alpha__) + char name[DE4X5_STRLEN]; + u_long iobase = 0x1000; + + for (i=1; i<MAX_EISA_SLOTS; i++, iobase+=EISA_SLOT_INC) { + if (EISA_signature(name, EISA_ID)) j++; + } +#endif + if (!pcibios_present()) return j; + + for (i=0; + (pcibios_find_class(class, i, &pb, &dev_fn)!= PCIBIOS_DEVICE_NOT_FOUND); + i++) { + dev_num = PCI_SLOT(dev_fn); + device = 0; + pcibios_read_config_word(pb, PCI_DEVICE, PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pb, PCI_DEVICE, PCI_DEVICE_ID, &dev_id); + device = dev_id; + device <<= 8; + if (is_DC21040 || is_DC21041 || is_DC21140 || is_DC2114x) j++; + } + + return j; +} + +/* +** If at end of eth device list and can't use current entry, malloc +** one up. If memory could not be allocated, print an error message. +*/ +__initfunc(static struct device * +insert_device(struct device *dev, u_long iobase, int (*init)(struct device *))) +{ + struct device *new; + + new = (struct device *)kmalloc(sizeof(struct device)+8, GFP_KERNEL); + if (new == NULL) { + printk("de4x5.c: Device not initialised, insufficient memory\n"); + return NULL; + } else { + memset((char *)new, 0, sizeof(struct device)+8); + new->name = (char *)(new + 1); + new->base_addr = iobase; /* assign the io address */ + new->init = init; /* initialisation routine */ + } + + return new; +} + +#endif /* MODULE */ + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/linux/include -Wall -Wstrict-prototypes -fomit-frame-pointer -fno-strength-reduce -malign-loops=2 -malign-jumps=2 -malign-functions=2 -O2 -m486 -c de4x5.c" + * + * compile-command: "gcc -D__KERNEL__ -DMODULE -I/linux/include -Wall -Wstrict-prototypes -fomit-frame-pointer -fno-strength-reduce -malign-loops=2 -malign-jumps=2 -malign-functions=2 -O2 -m486 -c de4x5.c" + * End: + */ diff --git a/linux/src/drivers/net/de4x5.h b/linux/src/drivers/net/de4x5.h new file mode 100644 index 00000000..c0c58ccf --- /dev/null +++ b/linux/src/drivers/net/de4x5.h @@ -0,0 +1,1028 @@ +/* + Copyright 1994 Digital Equipment Corporation. + + This software may be used and distributed according to the terms of the + GNU Public License, incorporated herein by reference. + + The author may be reached as davies@wanton.lkg.dec.com or Digital + Equipment Corporation, 550 King Street, Littleton MA 01460. + + ========================================================================= +*/ + +/* +** DC21040 CSR<1..15> Register Address Map +*/ +#define DE4X5_BMR iobase+(0x000 << lp->bus) /* Bus Mode Register */ +#define DE4X5_TPD iobase+(0x008 << lp->bus) /* Transmit Poll Demand Reg */ +#define DE4X5_RPD iobase+(0x010 << lp->bus) /* Receive Poll Demand Reg */ +#define DE4X5_RRBA iobase+(0x018 << lp->bus) /* RX Ring Base Address Reg */ +#define DE4X5_TRBA iobase+(0x020 << lp->bus) /* TX Ring Base Address Reg */ +#define DE4X5_STS iobase+(0x028 << lp->bus) /* Status Register */ +#define DE4X5_OMR iobase+(0x030 << lp->bus) /* Operation Mode Register */ +#define DE4X5_IMR iobase+(0x038 << lp->bus) /* Interrupt Mask Register */ +#define DE4X5_MFC iobase+(0x040 << lp->bus) /* Missed Frame Counter */ +#define DE4X5_APROM iobase+(0x048 << lp->bus) /* Ethernet Address PROM */ +#define DE4X5_BROM iobase+(0x048 << lp->bus) /* Boot ROM Register */ +#define DE4X5_SROM iobase+(0x048 << lp->bus) /* Serial ROM Register */ +#define DE4X5_MII iobase+(0x048 << lp->bus) /* MII Interface Register */ +#define DE4X5_DDR iobase+(0x050 << lp->bus) /* Data Diagnostic Register */ +#define DE4X5_FDR iobase+(0x058 << lp->bus) /* Full Duplex Register */ +#define DE4X5_GPT iobase+(0x058 << lp->bus) /* General Purpose Timer Reg.*/ +#define DE4X5_GEP iobase+(0x060 << lp->bus) /* General Purpose Register */ +#define DE4X5_SISR iobase+(0x060 << lp->bus) /* SIA Status Register */ +#define DE4X5_SICR iobase+(0x068 << lp->bus) /* SIA Connectivity Register */ +#define DE4X5_STRR iobase+(0x070 << lp->bus) /* SIA TX/RX Register */ +#define DE4X5_SIGR iobase+(0x078 << lp->bus) /* SIA General Register */ + +/* +** EISA Register Address Map +*/ +#define EISA_ID iobase+0x0c80 /* EISA ID Registers */ +#define EISA_ID0 iobase+0x0c80 /* EISA ID Register 0 */ +#define EISA_ID1 iobase+0x0c81 /* EISA ID Register 1 */ +#define EISA_ID2 iobase+0x0c82 /* EISA ID Register 2 */ +#define EISA_ID3 iobase+0x0c83 /* EISA ID Register 3 */ +#define EISA_CR iobase+0x0c84 /* EISA Control Register */ +#define EISA_REG0 iobase+0x0c88 /* EISA Configuration Register 0 */ +#define EISA_REG1 iobase+0x0c89 /* EISA Configuration Register 1 */ +#define EISA_REG2 iobase+0x0c8a /* EISA Configuration Register 2 */ +#define EISA_REG3 iobase+0x0c8f /* EISA Configuration Register 3 */ +#define EISA_APROM iobase+0x0c90 /* Ethernet Address PROM */ + +/* +** PCI/EISA Configuration Registers Address Map +*/ +#define PCI_CFID iobase+0x0008 /* PCI Configuration ID Register */ +#define PCI_CFCS iobase+0x000c /* PCI Command/Status Register */ +#define PCI_CFRV iobase+0x0018 /* PCI Revision Register */ +#define PCI_CFLT iobase+0x001c /* PCI Latency Timer Register */ +#define PCI_CBIO iobase+0x0028 /* PCI Base I/O Register */ +#define PCI_CBMA iobase+0x002c /* PCI Base Memory Address Register */ +#define PCI_CBER iobase+0x0030 /* PCI Expansion ROM Base Address Reg. */ +#define PCI_CFIT iobase+0x003c /* PCI Configuration Interrupt Register */ +#define PCI_CFDA iobase+0x0040 /* PCI Driver Area Register */ +#define PCI_CFDD iobase+0x0041 /* PCI Driver Dependent Area Register */ +#define PCI_CFPM iobase+0x0043 /* PCI Power Management Area Register */ + +/* +** EISA Configuration Register 0 bit definitions +*/ +#define ER0_BSW 0x80 /* EISA Bus Slave Width, 1: 32 bits */ +#define ER0_BMW 0x40 /* EISA Bus Master Width, 1: 32 bits */ +#define ER0_EPT 0x20 /* EISA PREEMPT Time, 0: 23 BCLKs */ +#define ER0_ISTS 0x10 /* Interrupt Status (X) */ +#define ER0_LI 0x08 /* Latch Interrupts */ +#define ER0_INTL 0x06 /* INTerrupt Level */ +#define ER0_INTT 0x01 /* INTerrupt Type, 0: Level, 1: Edge */ + +/* +** EISA Configuration Register 1 bit definitions +*/ +#define ER1_IAM 0xe0 /* ISA Address Mode */ +#define ER1_IAE 0x10 /* ISA Addressing Enable */ +#define ER1_UPIN 0x0f /* User Pins */ + +/* +** EISA Configuration Register 2 bit definitions +*/ +#define ER2_BRS 0xc0 /* Boot ROM Size */ +#define ER2_BRA 0x3c /* Boot ROM Address <16:13> */ + +/* +** EISA Configuration Register 3 bit definitions +*/ +#define ER3_BWE 0x40 /* Burst Write Enable */ +#define ER3_BRE 0x04 /* Burst Read Enable */ +#define ER3_LSR 0x02 /* Local Software Reset */ + +/* +** PCI Configuration ID Register (PCI_CFID). The Device IDs are left +** shifted 8 bits to allow detection of DC21142 and DC21143 variants with +** the configuration revision register step number. +*/ +#define CFID_DID 0xff00 /* Device ID */ +#define CFID_VID 0x00ff /* Vendor ID */ +#define DC21040_DID 0x0200 /* Unique Device ID # */ +#define DC21040_VID 0x1011 /* DC21040 Manufacturer */ +#define DC21041_DID 0x1400 /* Unique Device ID # */ +#define DC21041_VID 0x1011 /* DC21041 Manufacturer */ +#define DC21140_DID 0x0900 /* Unique Device ID # */ +#define DC21140_VID 0x1011 /* DC21140 Manufacturer */ +#define DC2114x_DID 0x1900 /* Unique Device ID # */ +#define DC2114x_VID 0x1011 /* DC2114[23] Manufacturer */ + +/* +** Chipset defines +*/ +#define DC21040 DC21040_DID +#define DC21041 DC21041_DID +#define DC21140 DC21140_DID +#define DC2114x DC2114x_DID +#define DC21142 (DC2114x_DID | 0x0010) +#define DC21143 (DC2114x_DID | 0x0030) + +#define is_DC21040 ((vendor == DC21040_VID) && (device == DC21040_DID)) +#define is_DC21041 ((vendor == DC21041_VID) && (device == DC21041_DID)) +#define is_DC21140 ((vendor == DC21140_VID) && (device == DC21140_DID)) +#define is_DC2114x ((vendor == DC2114x_VID) && (device == DC2114x_DID)) +#define is_DC21142 ((vendor == DC2114x_VID) && (device == DC21142)) +#define is_DC21143 ((vendor == DC2114x_VID) && (device == DC21143)) + +/* +** PCI Configuration Command/Status Register (PCI_CFCS) +*/ +#define CFCS_DPE 0x80000000 /* Detected Parity Error (S) */ +#define CFCS_SSE 0x40000000 /* Signal System Error (S) */ +#define CFCS_RMA 0x20000000 /* Receive Master Abort (S) */ +#define CFCS_RTA 0x10000000 /* Receive Target Abort (S) */ +#define CFCS_DST 0x06000000 /* DEVSEL Timing (S) */ +#define CFCS_DPR 0x01000000 /* Data Parity Report (S) */ +#define CFCS_FBB 0x00800000 /* Fast Back-To-Back (S) */ +#define CFCS_SEE 0x00000100 /* System Error Enable (C) */ +#define CFCS_PER 0x00000040 /* Parity Error Response (C) */ +#define CFCS_MO 0x00000004 /* Master Operation (C) */ +#define CFCS_MSA 0x00000002 /* Memory Space Access (C) */ +#define CFCS_IOSA 0x00000001 /* I/O Space Access (C) */ + +/* +** PCI Configuration Revision Register (PCI_CFRV) +*/ +#define CFRV_BC 0xff000000 /* Base Class */ +#define CFRV_SC 0x00ff0000 /* Subclass */ +#define CFRV_RN 0x000000f0 /* Revision Number */ +#define CFRV_SN 0x0000000f /* Step Number */ +#define BASE_CLASS 0x02000000 /* Indicates Network Controller */ +#define SUB_CLASS 0x00000000 /* Indicates Ethernet Controller */ +#define STEP_NUMBER 0x00000020 /* Increments for future chips */ +#define REV_NUMBER 0x00000003 /* 0x00, 0x01, 0x02, 0x03: Rev in Step */ +#define CFRV_MASK 0xffff0000 /* Register mask */ + +/* +** PCI Configuration Latency Timer Register (PCI_CFLT) +*/ +#define CFLT_BC 0x0000ff00 /* Latency Timer bits */ + +/* +** PCI Configuration Base I/O Address Register (PCI_CBIO) +*/ +#define CBIO_MASK -128 /* Base I/O Address Mask */ +#define CBIO_IOSI 0x00000001 /* I/O Space Indicator (RO, value is 1) */ + +/* +** PCI Configuration Card Information Structure Register (PCI_CCIS) +*/ +#define CCIS_ROMI 0xf0000000 /* ROM Image */ +#define CCIS_ASO 0x0ffffff8 /* Address Space Offset */ +#define CCIS_ASI 0x00000007 /* Address Space Indicator */ + +/* +** PCI Configuration Subsystem ID Register (PCI_SSID) +*/ +#define SSID_SSID 0xffff0000 /* Subsystem ID */ +#define SSID_SVID 0x0000ffff /* Subsystem Vendor ID */ + +/* +** PCI Configuration Expansion ROM Base Address Register (PCI_CBER) +*/ +#define CBER_MASK 0xfffffc00 /* Expansion ROM Base Address Mask */ +#define CBER_ROME 0x00000001 /* ROM Enable */ + +/* +** PCI Configuration Interrupt Register (PCI_CFIT) +*/ +#define CFIT_MXLT 0xff000000 /* MAX_LAT Value (0.25us periods) */ +#define CFIT_MNGT 0x00ff0000 /* MIN_GNT Value (0.25us periods) */ +#define CFIT_IRQP 0x0000ff00 /* Interrupt Pin */ +#define CFIT_IRQL 0x000000ff /* Interrupt Line */ + +/* +** PCI Configuration Power Management Area Register (PCI_CFPM) +*/ +#define SLEEP 0x80 /* Power Saving Sleep Mode */ +#define SNOOZE 0x40 /* Power Saving Snooze Mode */ +#define WAKEUP 0x00 /* Power Saving Wakeup */ + +#define PCI_CFDA_DSU 0x41 /* 8 bit Configuration Space Address */ +#define PCI_CFDA_PSM 0x43 /* 8 bit Configuration Space Address */ + +/* +** DC21040 Bus Mode Register (DE4X5_BMR) +*/ +#define BMR_RML 0x00200000 /* [Memory] Read Multiple */ +#define BMR_DBO 0x00100000 /* Descriptor Byte Ordering (Endian) */ +#define BMR_TAP 0x000e0000 /* Transmit Automatic Polling */ +#define BMR_DAS 0x00010000 /* Diagnostic Address Space */ +#define BMR_CAL 0x0000c000 /* Cache Alignment */ +#define BMR_PBL 0x00003f00 /* Programmable Burst Length */ +#define BMR_BLE 0x00000080 /* Big/Little Endian */ +#define BMR_DSL 0x0000007c /* Descriptor Skip Length */ +#define BMR_BAR 0x00000002 /* Bus ARbitration */ +#define BMR_SWR 0x00000001 /* Software Reset */ + + /* Timings here are for 10BASE-T/AUI only*/ +#define TAP_NOPOLL 0x00000000 /* No automatic polling */ +#define TAP_200US 0x00020000 /* TX automatic polling every 200us */ +#define TAP_800US 0x00040000 /* TX automatic polling every 800us */ +#define TAP_1_6MS 0x00060000 /* TX automatic polling every 1.6ms */ +#define TAP_12_8US 0x00080000 /* TX automatic polling every 12.8us */ +#define TAP_25_6US 0x000a0000 /* TX automatic polling every 25.6us */ +#define TAP_51_2US 0x000c0000 /* TX automatic polling every 51.2us */ +#define TAP_102_4US 0x000e0000 /* TX automatic polling every 102.4us */ + +#define CAL_NOUSE 0x00000000 /* Not used */ +#define CAL_8LONG 0x00004000 /* 8-longword alignment */ +#define CAL_16LONG 0x00008000 /* 16-longword alignment */ +#define CAL_32LONG 0x0000c000 /* 32-longword alignment */ + +#define PBL_0 0x00000000 /* DMA burst length = amount in RX FIFO */ +#define PBL_1 0x00000100 /* 1 longword DMA burst length */ +#define PBL_2 0x00000200 /* 2 longwords DMA burst length */ +#define PBL_4 0x00000400 /* 4 longwords DMA burst length */ +#define PBL_8 0x00000800 /* 8 longwords DMA burst length */ +#define PBL_16 0x00001000 /* 16 longwords DMA burst length */ +#define PBL_32 0x00002000 /* 32 longwords DMA burst length */ + +#define DSL_0 0x00000000 /* 0 longword / descriptor */ +#define DSL_1 0x00000004 /* 1 longword / descriptor */ +#define DSL_2 0x00000008 /* 2 longwords / descriptor */ +#define DSL_4 0x00000010 /* 4 longwords / descriptor */ +#define DSL_8 0x00000020 /* 8 longwords / descriptor */ +#define DSL_16 0x00000040 /* 16 longwords / descriptor */ +#define DSL_32 0x00000080 /* 32 longwords / descriptor */ + +/* +** DC21040 Transmit Poll Demand Register (DE4X5_TPD) +*/ +#define TPD 0x00000001 /* Transmit Poll Demand */ + +/* +** DC21040 Receive Poll Demand Register (DE4X5_RPD) +*/ +#define RPD 0x00000001 /* Receive Poll Demand */ + +/* +** DC21040 Receive Ring Base Address Register (DE4X5_RRBA) +*/ +#define RRBA 0xfffffffc /* RX Descriptor List Start Address */ + +/* +** DC21040 Transmit Ring Base Address Register (DE4X5_TRBA) +*/ +#define TRBA 0xfffffffc /* TX Descriptor List Start Address */ + +/* +** Status Register (DE4X5_STS) +*/ +#define STS_GPI 0x04000000 /* General Purpose Port Interrupt */ +#define STS_BE 0x03800000 /* Bus Error Bits */ +#define STS_TS 0x00700000 /* Transmit Process State */ +#define STS_RS 0x000e0000 /* Receive Process State */ +#define STS_NIS 0x00010000 /* Normal Interrupt Summary */ +#define STS_AIS 0x00008000 /* Abnormal Interrupt Summary */ +#define STS_ER 0x00004000 /* Early Receive */ +#define STS_FBE 0x00002000 /* Fatal Bus Error */ +#define STS_SE 0x00002000 /* System Error */ +#define STS_LNF 0x00001000 /* Link Fail */ +#define STS_FD 0x00000800 /* Full-Duplex Short Frame Received */ +#define STS_TM 0x00000800 /* Timer Expired (DC21041) */ +#define STS_ETI 0x00000400 /* Early Transmit Interupt */ +#define STS_AT 0x00000400 /* AUI/TP Pin */ +#define STS_RWT 0x00000200 /* Receive Watchdog Time-Out */ +#define STS_RPS 0x00000100 /* Receive Process Stopped */ +#define STS_RU 0x00000080 /* Receive Buffer Unavailable */ +#define STS_RI 0x00000040 /* Receive Interrupt */ +#define STS_UNF 0x00000020 /* Transmit Underflow */ +#define STS_LNP 0x00000010 /* Link Pass */ +#define STS_ANC 0x00000010 /* Autonegotiation Complete */ +#define STS_TJT 0x00000008 /* Transmit Jabber Time-Out */ +#define STS_TU 0x00000004 /* Transmit Buffer Unavailable */ +#define STS_TPS 0x00000002 /* Transmit Process Stopped */ +#define STS_TI 0x00000001 /* Transmit Interrupt */ + +#define EB_PAR 0x00000000 /* Parity Error */ +#define EB_MA 0x00800000 /* Master Abort */ +#define EB_TA 0x01000000 /* Target Abort */ +#define EB_RES0 0x01800000 /* Reserved */ +#define EB_RES1 0x02000000 /* Reserved */ + +#define TS_STOP 0x00000000 /* Stopped */ +#define TS_FTD 0x00100000 /* Fetch Transmit Descriptor */ +#define TS_WEOT 0x00200000 /* Wait for End Of Transmission */ +#define TS_QDAT 0x00300000 /* Queue skb data into TX FIFO */ +#define TS_RES 0x00400000 /* Reserved */ +#define TS_SPKT 0x00500000 /* Setup Packet */ +#define TS_SUSP 0x00600000 /* Suspended */ +#define TS_CLTD 0x00700000 /* Close Transmit Descriptor */ + +#define RS_STOP 0x00000000 /* Stopped */ +#define RS_FRD 0x00020000 /* Fetch Receive Descriptor */ +#define RS_CEOR 0x00040000 /* Check for End of Receive Packet */ +#define RS_WFRP 0x00060000 /* Wait for Receive Packet */ +#define RS_SUSP 0x00080000 /* Suspended */ +#define RS_CLRD 0x000a0000 /* Close Receive Descriptor */ +#define RS_FLUSH 0x000c0000 /* Flush RX FIFO */ +#define RS_QRFS 0x000e0000 /* Queue RX FIFO into RX Skb */ + +#define INT_CANCEL 0x0001ffff /* For zeroing all interrupt sources */ + +/* +** Operation Mode Register (DE4X5_OMR) +*/ +#define OMR_SC 0x80000000 /* Special Capture Effect Enable */ +#define OMR_RA 0x40000000 /* Receive All */ +#define OMR_SDP 0x02000000 /* SD Polarity - MUST BE ASSERTED */ +#define OMR_SCR 0x01000000 /* Scrambler Mode */ +#define OMR_PCS 0x00800000 /* PCS Function */ +#define OMR_TTM 0x00400000 /* Transmit Threshold Mode */ +#define OMR_SF 0x00200000 /* Store and Forward */ +#define OMR_HBD 0x00080000 /* HeartBeat Disable */ +#define OMR_PS 0x00040000 /* Port Select */ +#define OMR_CA 0x00020000 /* Capture Effect Enable */ +#define OMR_BP 0x00010000 /* Back Pressure */ +#define OMR_TR 0x0000c000 /* Threshold Control Bits */ +#define OMR_ST 0x00002000 /* Start/Stop Transmission Command */ +#define OMR_FC 0x00001000 /* Force Collision Mode */ +#define OMR_OM 0x00000c00 /* Operating Mode */ +#define OMR_FDX 0x00000200 /* Full Duplex Mode */ +#define OMR_FKD 0x00000100 /* Flaky Oscillator Disable */ +#define OMR_PM 0x00000080 /* Pass All Multicast */ +#define OMR_PR 0x00000040 /* Promiscuous Mode */ +#define OMR_SB 0x00000020 /* Start/Stop Backoff Counter */ +#define OMR_IF 0x00000010 /* Inverse Filtering */ +#define OMR_PB 0x00000008 /* Pass Bad Frames */ +#define OMR_HO 0x00000004 /* Hash Only Filtering Mode */ +#define OMR_SR 0x00000002 /* Start/Stop Receive */ +#define OMR_HP 0x00000001 /* Hash/Perfect Receive Filtering Mode */ + +#define TR_72 0x00000000 /* Threshold set to 72 (128) bytes */ +#define TR_96 0x00004000 /* Threshold set to 96 (256) bytes */ +#define TR_128 0x00008000 /* Threshold set to 128 (512) bytes */ +#define TR_160 0x0000c000 /* Threshold set to 160 (1024) bytes */ + +#define OMR_DEF (OMR_SDP) +#define OMR_SIA (OMR_SDP | OMR_TTM) +#define OMR_SYM (OMR_SDP | OMR_SCR | OMR_PCS | OMR_HBD | OMR_PS) +#define OMR_MII_10 (OMR_SDP | OMR_TTM | OMR_PS) +#define OMR_MII_100 (OMR_SDP | OMR_HBD | OMR_PS) + +/* +** DC21040 Interrupt Mask Register (DE4X5_IMR) +*/ +#define IMR_GPM 0x04000000 /* General Purpose Port Mask */ +#define IMR_NIM 0x00010000 /* Normal Interrupt Summary Mask */ +#define IMR_AIM 0x00008000 /* Abnormal Interrupt Summary Mask */ +#define IMR_ERM 0x00004000 /* Early Receive Mask */ +#define IMR_FBM 0x00002000 /* Fatal Bus Error Mask */ +#define IMR_SEM 0x00002000 /* System Error Mask */ +#define IMR_LFM 0x00001000 /* Link Fail Mask */ +#define IMR_FDM 0x00000800 /* Full-Duplex (Short Frame) Mask */ +#define IMR_TMM 0x00000800 /* Timer Expired Mask (DC21041) */ +#define IMR_ETM 0x00000400 /* Early Transmit Interrupt Mask */ +#define IMR_ATM 0x00000400 /* AUI/TP Switch Mask */ +#define IMR_RWM 0x00000200 /* Receive Watchdog Time-Out Mask */ +#define IMR_RSM 0x00000100 /* Receive Stopped Mask */ +#define IMR_RUM 0x00000080 /* Receive Buffer Unavailable Mask */ +#define IMR_RIM 0x00000040 /* Receive Interrupt Mask */ +#define IMR_UNM 0x00000020 /* Underflow Interrupt Mask */ +#define IMR_ANM 0x00000010 /* Autonegotiation Complete Mask */ +#define IMR_LPM 0x00000010 /* Link Pass */ +#define IMR_TJM 0x00000008 /* Transmit Time-Out Jabber Mask */ +#define IMR_TUM 0x00000004 /* Transmit Buffer Unavailable Mask */ +#define IMR_TSM 0x00000002 /* Transmission Stopped Mask */ +#define IMR_TIM 0x00000001 /* Transmit Interrupt Mask */ + +/* +** Missed Frames and FIFO Overflow Counters (DE4X5_MFC) +*/ +#define MFC_FOCO 0x10000000 /* FIFO Overflow Counter Overflow Bit */ +#define MFC_FOC 0x0ffe0000 /* FIFO Overflow Counter Bits */ +#define MFC_OVFL 0x00010000 /* Missed Frames Counter Overflow Bit */ +#define MFC_CNTR 0x0000ffff /* Missed Frames Counter Bits */ +#define MFC_FOCM 0x1ffe0000 /* FIFO Overflow Counter Mask */ + +/* +** DC21040 Ethernet Address PROM (DE4X5_APROM) +*/ +#define APROM_DN 0x80000000 /* Data Not Valid */ +#define APROM_DT 0x000000ff /* Address Byte */ + +/* +** DC21041 Boot/Ethernet Address ROM (DE4X5_BROM) +*/ +#define BROM_MODE 0x00008000 /* MODE_1: 0, MODE_0: 1 (read only) */ +#define BROM_RD 0x00004000 /* Read from Boot ROM */ +#define BROM_WR 0x00002000 /* Write to Boot ROM */ +#define BROM_BR 0x00001000 /* Select Boot ROM when set */ +#define BROM_SR 0x00000800 /* Select Serial ROM when set */ +#define BROM_REG 0x00000400 /* External Register Select */ +#define BROM_DT 0x000000ff /* Data Byte */ + +/* +** DC21041 Serial/Ethernet Address ROM (DE4X5_SROM, DE4X5_MII) +*/ +#define MII_MDI 0x00080000 /* MII Management Data In */ +#define MII_MDO 0x00060000 /* MII Management Mode/Data Out */ +#define MII_MRD 0x00040000 /* MII Management Define Read Mode */ +#define MII_MWR 0x00000000 /* MII Management Define Write Mode */ +#define MII_MDT 0x00020000 /* MII Management Data Out */ +#define MII_MDC 0x00010000 /* MII Management Clock */ +#define MII_RD 0x00004000 /* Read from MII */ +#define MII_WR 0x00002000 /* Write to MII */ +#define MII_SEL 0x00000800 /* Select MII when RESET */ + +#define SROM_MODE 0x00008000 /* MODE_1: 0, MODE_0: 1 (read only) */ +#define SROM_RD 0x00004000 /* Read from Boot ROM */ +#define SROM_WR 0x00002000 /* Write to Boot ROM */ +#define SROM_BR 0x00001000 /* Select Boot ROM when set */ +#define SROM_SR 0x00000800 /* Select Serial ROM when set */ +#define SROM_REG 0x00000400 /* External Register Select */ +#define SROM_DT 0x000000ff /* Data Byte */ + +#define DT_OUT 0x00000008 /* Serial Data Out */ +#define DT_IN 0x00000004 /* Serial Data In */ +#define DT_CLK 0x00000002 /* Serial ROM Clock */ +#define DT_CS 0x00000001 /* Serial ROM Chip Select */ + +#define MII_PREAMBLE 0xffffffff /* MII Management Preamble */ +#define MII_TEST 0xaaaaaaaa /* MII Test Signal */ +#define MII_STRD 0x06 /* Start of Frame+Op Code: use low nibble */ +#define MII_STWR 0x0a /* Start of Frame+Op Code: use low nibble */ + +#define MII_CR 0x00 /* MII Management Control Register */ +#define MII_SR 0x01 /* MII Management Status Register */ +#define MII_ID0 0x02 /* PHY Identifier Register 0 */ +#define MII_ID1 0x03 /* PHY Identifier Register 1 */ +#define MII_ANA 0x04 /* Auto Negotiation Advertisement */ +#define MII_ANLPA 0x05 /* Auto Negotiation Link Partner Ability */ +#define MII_ANE 0x06 /* Auto Negotiation Expansion */ +#define MII_ANP 0x07 /* Auto Negotiation Next Page TX */ + +#define DE4X5_MAX_MII 32 /* Maximum address of MII PHY devices */ + +/* +** MII Management Control Register +*/ +#define MII_CR_RST 0x8000 /* RESET the PHY chip */ +#define MII_CR_LPBK 0x4000 /* Loopback enable */ +#define MII_CR_SPD 0x2000 /* 0: 10Mb/s; 1: 100Mb/s */ +#define MII_CR_10 0x0000 /* Set 10Mb/s */ +#define MII_CR_100 0x2000 /* Set 100Mb/s */ +#define MII_CR_ASSE 0x1000 /* Auto Speed Select Enable */ +#define MII_CR_PD 0x0800 /* Power Down */ +#define MII_CR_ISOL 0x0400 /* Isolate Mode */ +#define MII_CR_RAN 0x0200 /* Restart Auto Negotiation */ +#define MII_CR_FDM 0x0100 /* Full Duplex Mode */ +#define MII_CR_CTE 0x0080 /* Collision Test Enable */ + +/* +** MII Management Status Register +*/ +#define MII_SR_T4C 0x8000 /* 100BASE-T4 capable */ +#define MII_SR_TXFD 0x4000 /* 100BASE-TX Full Duplex capable */ +#define MII_SR_TXHD 0x2000 /* 100BASE-TX Half Duplex capable */ +#define MII_SR_TFD 0x1000 /* 10BASE-T Full Duplex capable */ +#define MII_SR_THD 0x0800 /* 10BASE-T Half Duplex capable */ +#define MII_SR_ASSC 0x0020 /* Auto Speed Selection Complete*/ +#define MII_SR_RFD 0x0010 /* Remote Fault Detected */ +#define MII_SR_ANC 0x0008 /* Auto Negotiation capable */ +#define MII_SR_LKS 0x0004 /* Link Status */ +#define MII_SR_JABD 0x0002 /* Jabber Detect */ +#define MII_SR_XC 0x0001 /* Extended Capabilities */ + +/* +** MII Management Auto Negotiation Advertisement Register +*/ +#define MII_ANA_TAF 0x03e0 /* Technology Ability Field */ +#define MII_ANA_T4AM 0x0200 /* T4 Technology Ability Mask */ +#define MII_ANA_TXAM 0x0180 /* TX Technology Ability Mask */ +#define MII_ANA_FDAM 0x0140 /* Full Duplex Technology Ability Mask */ +#define MII_ANA_HDAM 0x02a0 /* Half Duplex Technology Ability Mask */ +#define MII_ANA_100M 0x0380 /* 100Mb Technology Ability Mask */ +#define MII_ANA_10M 0x0060 /* 10Mb Technology Ability Mask */ +#define MII_ANA_CSMA 0x0001 /* CSMA-CD Capable */ + +/* +** MII Management Auto Negotiation Remote End Register +*/ +#define MII_ANLPA_NP 0x8000 /* Next Page (Enable) */ +#define MII_ANLPA_ACK 0x4000 /* Remote Acknowledge */ +#define MII_ANLPA_RF 0x2000 /* Remote Fault */ +#define MII_ANLPA_TAF 0x03e0 /* Technology Ability Field */ +#define MII_ANLPA_T4AM 0x0200 /* T4 Technology Ability Mask */ +#define MII_ANLPA_TXAM 0x0180 /* TX Technology Ability Mask */ +#define MII_ANLPA_FDAM 0x0140 /* Full Duplex Technology Ability Mask */ +#define MII_ANLPA_HDAM 0x02a0 /* Half Duplex Technology Ability Mask */ +#define MII_ANLPA_100M 0x0380 /* 100Mb Technology Ability Mask */ +#define MII_ANLPA_10M 0x0060 /* 10Mb Technology Ability Mask */ +#define MII_ANLPA_CSMA 0x0001 /* CSMA-CD Capable */ + +/* +** SROM Media Definitions (ABG SROM Section) +*/ +#define MEDIA_NWAY 0x0080 /* Nway (Auto Negotiation) on PHY */ +#define MEDIA_MII 0x0040 /* MII Present on the adapter */ +#define MEDIA_FIBRE 0x0008 /* Fibre Media present */ +#define MEDIA_AUI 0x0004 /* AUI Media present */ +#define MEDIA_TP 0x0002 /* TP Media present */ +#define MEDIA_BNC 0x0001 /* BNC Media present */ + +/* +** SROM Definitions (Digital Semiconductor Format) +*/ +#define SROM_SSVID 0x0000 /* Sub-system Vendor ID offset */ +#define SROM_SSID 0x0002 /* Sub-system ID offset */ +#define SROM_CISPL 0x0004 /* CardBus CIS Pointer low offset */ +#define SROM_CISPH 0x0006 /* CardBus CIS Pointer high offset */ +#define SROM_IDCRC 0x0010 /* ID Block CRC offset*/ +#define SROM_RSVD2 0x0011 /* ID Reserved 2 offset */ +#define SROM_SFV 0x0012 /* SROM Format Version offset */ +#define SROM_CCNT 0x0013 /* Controller Count offset */ +#define SROM_HWADD 0x0014 /* Hardware Address offset */ +#define SROM_MRSVD 0x007c /* Manufacturer Reserved offset*/ +#define SROM_CRC 0x007e /* SROM CRC offset */ + +/* +** SROM Media Connection Definitions +*/ +#define SROM_10BT 0x0000 /* 10BASE-T half duplex */ +#define SROM_10BTN 0x0100 /* 10BASE-T with Nway */ +#define SROM_10BTF 0x0204 /* 10BASE-T full duplex */ +#define SROM_10BTNLP 0x0400 /* 10BASE-T without Link Pass test */ +#define SROM_10B2 0x0001 /* 10BASE-2 (BNC) */ +#define SROM_10B5 0x0002 /* 10BASE-5 (AUI) */ +#define SROM_100BTH 0x0003 /* 100BASE-T half duplex */ +#define SROM_100BTF 0x0205 /* 100BASE-T full duplex */ +#define SROM_100BT4 0x0006 /* 100BASE-T4 */ +#define SROM_100BFX 0x0007 /* 100BASE-FX half duplex (Fiber) */ +#define SROM_M10BT 0x0009 /* MII 10BASE-T half duplex */ +#define SROM_M10BTF 0x020a /* MII 10BASE-T full duplex */ +#define SROM_M100BT 0x000d /* MII 100BASE-T half duplex */ +#define SROM_M100BTF 0x020e /* MII 100BASE-T full duplex */ +#define SROM_M100BT4 0x000f /* MII 100BASE-T4 */ +#define SROM_M100BF 0x0010 /* MII 100BASE-FX half duplex */ +#define SROM_M100BFF 0x0211 /* MII 100BASE-FX full duplex */ +#define SROM_PDA 0x0800 /* Powerup & Dynamic Autosense */ +#define SROM_PAO 0x8800 /* Powerup Autosense Only */ +#define SROM_NSMI 0xffff /* No Selected Media Information */ + +/* +** SROM Media Definitions +*/ +#define SROM_10BASET 0x0000 /* 10BASE-T half duplex */ +#define SROM_10BASE2 0x0001 /* 10BASE-2 (BNC) */ +#define SROM_10BASE5 0x0002 /* 10BASE-5 (AUI) */ +#define SROM_100BASET 0x0003 /* 100BASE-T half duplex */ +#define SROM_10BASETF 0x0004 /* 10BASE-T full duplex */ +#define SROM_100BASETF 0x0005 /* 100BASE-T full duplex */ +#define SROM_100BASET4 0x0006 /* 100BASE-T4 */ +#define SROM_100BASEF 0x0007 /* 100BASE-FX half duplex */ +#define SROM_100BASEFF 0x0008 /* 100BASE-FX full duplex */ + +#define BLOCK_LEN 0x7f /* Extended blocks length mask */ +#define EXT_FIELD 0x40 /* Extended blocks extension field bit */ +#define MEDIA_CODE 0x3f /* Extended blocks media code mask */ + +/* +** SROM Compact Format Block Masks +*/ +#define COMPACT_FI 0x80 /* Format Indicator */ +#define COMPACT_LEN 0x04 /* Length */ +#define COMPACT_MC 0x3f /* Media Code */ + +/* +** SROM Extended Format Block Type 0 Masks +*/ +#define BLOCK0_FI 0x80 /* Format Indicator */ +#define BLOCK0_MCS 0x80 /* Media Code byte Sign */ +#define BLOCK0_MC 0x3f /* Media Code */ + +/* +** DC21040 Full Duplex Register (DE4X5_FDR) +*/ +#define FDR_FDACV 0x0000ffff /* Full Duplex Auto Configuration Value */ + +/* +** DC21041 General Purpose Timer Register (DE4X5_GPT) +*/ +#define GPT_CON 0x00010000 /* One shot: 0, Continuous: 1 */ +#define GPT_VAL 0x0000ffff /* Timer Value */ + +/* +** DC21140 General Purpose Register (DE4X5_GEP) (hardware dependent bits) +*/ +/* Valid ONLY for DE500 hardware */ +#define GEP_LNP 0x00000080 /* Link Pass (input) */ +#define GEP_SLNK 0x00000040 /* SYM LINK (input) */ +#define GEP_SDET 0x00000020 /* Signal Detect (input) */ +#define GEP_HRST 0x00000010 /* Hard RESET (to PHY) (output) */ +#define GEP_FDXD 0x00000008 /* Full Duplex Disable (output) */ +#define GEP_PHYL 0x00000004 /* PHY Loopback (output) */ +#define GEP_FLED 0x00000002 /* Force Activity LED on (output) */ +#define GEP_MODE 0x00000001 /* 0: 10Mb/s, 1: 100Mb/s */ +#define GEP_INIT 0x0000011f /* Setup inputs (0) and outputs (1) */ +#define GEP_CTRL 0x00000100 /* GEP control bit */ + +/* +** SIA Register Defaults +*/ +#define CSR13 0x00000001 +#define CSR14 0x0003ff7f /* Autonegotiation disabled */ +#define CSR15 0x00000008 + +/* +** SIA Status Register (DE4X5_SISR) +*/ +#define SISR_LPC 0xffff0000 /* Link Partner's Code Word */ +#define SISR_LPN 0x00008000 /* Link Partner Negotiable */ +#define SISR_ANS 0x00007000 /* Auto Negotiation Arbitration State */ +#define SISR_NSN 0x00000800 /* Non Stable NLPs Detected (DC21041) */ +#define SISR_TRF 0x00000800 /* Transmit Remote Fault */ +#define SISR_NSND 0x00000400 /* Non Stable NLPs Detected (DC21142) */ +#define SISR_ANR_FDS 0x00000400 /* Auto Negotiate Restart/Full Duplex Sel.*/ +#define SISR_TRA 0x00000200 /* 10BASE-T Receive Port Activity */ +#define SISR_NRA 0x00000200 /* Non Selected Port Receive Activity */ +#define SISR_ARA 0x00000100 /* AUI Receive Port Activity */ +#define SISR_SRA 0x00000100 /* Selected Port Receive Activity */ +#define SISR_DAO 0x00000080 /* PLL All One */ +#define SISR_DAZ 0x00000040 /* PLL All Zero */ +#define SISR_DSP 0x00000020 /* PLL Self-Test Pass */ +#define SISR_DSD 0x00000010 /* PLL Self-Test Done */ +#define SISR_APS 0x00000008 /* Auto Polarity State */ +#define SISR_LKF 0x00000004 /* Link Fail Status */ +#define SISR_LS10 0x00000004 /* 10Mb/s Link Fail Status */ +#define SISR_NCR 0x00000002 /* Network Connection Error */ +#define SISR_LS100 0x00000002 /* 100Mb/s Link Fail Status */ +#define SISR_PAUI 0x00000001 /* AUI_TP Indication */ +#define SISR_MRA 0x00000001 /* MII Receive Port Activity */ + +#define ANS_NDIS 0x00000000 /* Nway disable */ +#define ANS_TDIS 0x00001000 /* Transmit Disable */ +#define ANS_ADET 0x00002000 /* Ability Detect */ +#define ANS_ACK 0x00003000 /* Acknowledge */ +#define ANS_CACK 0x00004000 /* Complete Acknowledge */ +#define ANS_NWOK 0x00005000 /* Nway OK - FLP Link Good */ +#define ANS_LCHK 0x00006000 /* Link Check */ + +#define SISR_RST 0x00000301 /* CSR12 reset */ +#define SISR_ANR 0x00001301 /* Autonegotiation restart */ + +/* +** SIA Connectivity Register (DE4X5_SICR) +*/ +#define SICR_SDM 0xffff0000 /* SIA Diagnostics Mode */ +#define SICR_OE57 0x00008000 /* Output Enable 5 6 7 */ +#define SICR_OE24 0x00004000 /* Output Enable 2 4 */ +#define SICR_OE13 0x00002000 /* Output Enable 1 3 */ +#define SICR_IE 0x00001000 /* Input Enable */ +#define SICR_EXT 0x00000000 /* SIA MUX Select External SIA Mode */ +#define SICR_D_SIA 0x00000400 /* SIA MUX Select Diagnostics - SIA Sigs */ +#define SICR_DPLL 0x00000800 /* SIA MUX Select Diagnostics - DPLL Sigs*/ +#define SICR_APLL 0x00000a00 /* SIA MUX Select Diagnostics - DPLL Sigs*/ +#define SICR_D_RxM 0x00000c00 /* SIA MUX Select Diagnostics - RxM Sigs */ +#define SICR_M_RxM 0x00000d00 /* SIA MUX Select Diagnostics - RxM Sigs */ +#define SICR_LNKT 0x00000e00 /* SIA MUX Select Diagnostics - Link Test*/ +#define SICR_SEL 0x00000f00 /* SIA MUX Select AUI or TP with LEDs */ +#define SICR_ASE 0x00000080 /* APLL Start Enable*/ +#define SICR_SIM 0x00000040 /* Serial Interface Input Multiplexer */ +#define SICR_ENI 0x00000020 /* Encoder Input Multiplexer */ +#define SICR_EDP 0x00000010 /* SIA PLL External Input Enable */ +#define SICR_AUI 0x00000008 /* 10Base-T (0) or AUI (1) */ +#define SICR_CAC 0x00000004 /* CSR Auto Configuration */ +#define SICR_PS 0x00000002 /* Pin AUI/TP Selection */ +#define SICR_SRL 0x00000001 /* SIA Reset */ +#define SIA_RESET 0x00000000 /* SIA Reset Value */ + +/* +** SIA Transmit and Receive Register (DE4X5_STRR) +*/ +#define STRR_TAS 0x00008000 /* 10Base-T/AUI Autosensing Enable */ +#define STRR_SPP 0x00004000 /* Set Polarity Plus */ +#define STRR_APE 0x00002000 /* Auto Polarity Enable */ +#define STRR_LTE 0x00001000 /* Link Test Enable */ +#define STRR_SQE 0x00000800 /* Signal Quality Enable */ +#define STRR_CLD 0x00000400 /* Collision Detect Enable */ +#define STRR_CSQ 0x00000200 /* Collision Squelch Enable */ +#define STRR_RSQ 0x00000100 /* Receive Squelch Enable */ +#define STRR_ANE 0x00000080 /* Auto Negotiate Enable */ +#define STRR_HDE 0x00000040 /* Half Duplex Enable */ +#define STRR_CPEN 0x00000030 /* Compensation Enable */ +#define STRR_LSE 0x00000008 /* Link Pulse Send Enable */ +#define STRR_DREN 0x00000004 /* Driver Enable */ +#define STRR_LBK 0x00000002 /* Loopback Enable */ +#define STRR_ECEN 0x00000001 /* Encoder Enable */ +#define STRR_RESET 0xffffffff /* Reset value for STRR */ + +/* +** SIA General Register (DE4X5_SIGR) +*/ +#define SIGR_RMI 0x40000000 /* Receive Match Interrupt */ +#define SIGR_GI1 0x20000000 /* General Port Interrupt 1 */ +#define SIGR_GI0 0x10000000 /* General Port Interrupt 0 */ +#define SIGR_CWE 0x08000000 /* Control Write Enable */ +#define SIGR_RME 0x04000000 /* Receive Match Enable */ +#define SIGR_GEI1 0x02000000 /* GEP Interrupt Enable on Port 1 */ +#define SIGR_GEI0 0x01000000 /* GEP Interrupt Enable on Port 0 */ +#define SIGR_LGS3 0x00800000 /* LED/GEP3 Select */ +#define SIGR_LGS2 0x00400000 /* LED/GEP2 Select */ +#define SIGR_LGS1 0x00200000 /* LED/GEP1 Select */ +#define SIGR_LGS0 0x00100000 /* LED/GEP0 Select */ +#define SIGR_MD 0x000f0000 /* General Purpose Mode and Data */ +#define SIGR_LV2 0x00008000 /* General Purpose LED2 value */ +#define SIGR_LE2 0x00004000 /* General Purpose LED2 enable */ +#define SIGR_FRL 0x00002000 /* Force Receiver Low */ +#define SIGR_DPST 0x00001000 /* PLL Self Test Start */ +#define SIGR_LSD 0x00000800 /* LED Stretch Disable */ +#define SIGR_FLF 0x00000400 /* Force Link Fail */ +#define SIGR_FUSQ 0x00000200 /* Force Unsquelch */ +#define SIGR_TSCK 0x00000100 /* Test Clock */ +#define SIGR_LV1 0x00000080 /* General Purpose LED1 value */ +#define SIGR_LE1 0x00000040 /* General Purpose LED1 enable */ +#define SIGR_RWR 0x00000020 /* Receive Watchdog Release */ +#define SIGR_RWD 0x00000010 /* Receive Watchdog Disable */ +#define SIGR_ABM 0x00000008 /* BNC: 0, AUI:1 */ +#define SIGR_JCK 0x00000004 /* Jabber Clock */ +#define SIGR_HUJ 0x00000002 /* Host Unjab */ +#define SIGR_JBD 0x00000001 /* Jabber Disable */ +#define SIGR_RESET 0xffff0000 /* Reset value for SIGR */ + +/* +** Receive Descriptor Bit Summary +*/ +#define R_OWN 0x80000000 /* Own Bit */ +#define RD_FF 0x40000000 /* Filtering Fail */ +#define RD_FL 0x3fff0000 /* Frame Length */ +#define RD_ES 0x00008000 /* Error Summary */ +#define RD_LE 0x00004000 /* Length Error */ +#define RD_DT 0x00003000 /* Data Type */ +#define RD_RF 0x00000800 /* Runt Frame */ +#define RD_MF 0x00000400 /* Multicast Frame */ +#define RD_FS 0x00000200 /* First Descriptor */ +#define RD_LS 0x00000100 /* Last Descriptor */ +#define RD_TL 0x00000080 /* Frame Too Long */ +#define RD_CS 0x00000040 /* Collision Seen */ +#define RD_FT 0x00000020 /* Frame Type */ +#define RD_RJ 0x00000010 /* Receive Watchdog */ +#define RD_RE 0x00000008 /* Report on MII Error */ +#define RD_DB 0x00000004 /* Dribbling Bit */ +#define RD_CE 0x00000002 /* CRC Error */ +#define RD_OF 0x00000001 /* Overflow */ + +#define RD_RER 0x02000000 /* Receive End Of Ring */ +#define RD_RCH 0x01000000 /* Second Address Chained */ +#define RD_RBS2 0x003ff800 /* Buffer 2 Size */ +#define RD_RBS1 0x000007ff /* Buffer 1 Size */ + +/* +** Transmit Descriptor Bit Summary +*/ +#define T_OWN 0x80000000 /* Own Bit */ +#define TD_ES 0x00008000 /* Error Summary */ +#define TD_TO 0x00004000 /* Transmit Jabber Time-Out */ +#define TD_LO 0x00000800 /* Loss Of Carrier */ +#define TD_NC 0x00000400 /* No Carrier */ +#define TD_LC 0x00000200 /* Late Collision */ +#define TD_EC 0x00000100 /* Excessive Collisions */ +#define TD_HF 0x00000080 /* Heartbeat Fail */ +#define TD_CC 0x00000078 /* Collision Counter */ +#define TD_LF 0x00000004 /* Link Fail */ +#define TD_UF 0x00000002 /* Underflow Error */ +#define TD_DE 0x00000001 /* Deferred */ + +#define TD_IC 0x80000000 /* Interrupt On Completion */ +#define TD_LS 0x40000000 /* Last Segment */ +#define TD_FS 0x20000000 /* First Segment */ +#define TD_FT1 0x10000000 /* Filtering Type */ +#define TD_SET 0x08000000 /* Setup Packet */ +#define TD_AC 0x04000000 /* Add CRC Disable */ +#define TD_TER 0x02000000 /* Transmit End Of Ring */ +#define TD_TCH 0x01000000 /* Second Address Chained */ +#define TD_DPD 0x00800000 /* Disabled Padding */ +#define TD_FT0 0x00400000 /* Filtering Type */ +#define TD_TBS2 0x003ff800 /* Buffer 2 Size */ +#define TD_TBS1 0x000007ff /* Buffer 1 Size */ + +#define PERFECT_F 0x00000000 +#define HASH_F TD_FT0 +#define INVERSE_F TD_FT1 +#define HASH_O_F (TD_FT1 | TD_F0) + +/* +** Media / mode state machine definitions +** User selectable: +*/ +#define TP 0x0001 /* 10Base-T */ +#define TP_NW 0x0002 /* 10Base-T with Nway */ +#define BNC 0x0004 /* Thinwire */ +#define AUI 0x0008 /* Thickwire */ +#define BNC_AUI 0x0010 /* BNC/AUI on DC21040 indistinguishable */ +#define _10Mb 0x0040 /* 10Mb/s Ethernet */ +#define _100Mb 0x0080 /* 100Mb/s Ethernet */ +#define AUTO 0x4000 /* Auto sense the media or speed */ + +/* +** Internal states +*/ +#define NC 0x0000 /* No Connection */ +#define ANS 0x0020 /* Intermediate AutoNegotiation State */ +#define SPD_DET 0x0100 /* Parallel speed detection */ +#define INIT 0x0200 /* Initial state */ +#define EXT_SIA 0x0400 /* External SIA for motherboard chip */ +#define ANS_SUSPECT 0x0802 /* Suspect the ANS (TP) port is down */ +#define TP_SUSPECT 0x0803 /* Suspect the TP port is down */ +#define BNC_AUI_SUSPECT 0x0804 /* Suspect the BNC or AUI port is down */ +#define EXT_SIA_SUSPECT 0x0805 /* Suspect the EXT SIA port is down */ +#define BNC_SUSPECT 0x0806 /* Suspect the BNC port is down */ +#define AUI_SUSPECT 0x0807 /* Suspect the AUI port is down */ +#define MII 0x1000 /* MII on the 21143 */ + +#define TIMER_CB 0x80000000 /* Timer callback detection */ + +/* +** DE4X5 DEBUG Options +*/ +#define DEBUG_NONE 0x0000 /* No DEBUG messages */ +#define DEBUG_VERSION 0x0001 /* Print version message */ +#define DEBUG_MEDIA 0x0002 /* Print media messages */ +#define DEBUG_TX 0x0004 /* Print TX (queue_pkt) messages */ +#define DEBUG_RX 0x0008 /* Print RX (de4x5_rx) messages */ +#define DEBUG_SROM 0x0010 /* Print SROM messages */ +#define DEBUG_MII 0x0020 /* Print MII messages */ +#define DEBUG_OPEN 0x0040 /* Print de4x5_open() messages */ +#define DEBUG_CLOSE 0x0080 /* Print de4x5_close() messages */ +#define DEBUG_PCICFG 0x0100 +#define DEBUG_ALL 0x01ff + +/* +** Miscellaneous +*/ +#define PCI 0 +#define EISA 1 + +#define HASH_TABLE_LEN 512 /* Bits */ +#define HASH_BITS 0x01ff /* 9 LS bits */ + +#define SETUP_FRAME_LEN 192 /* Bytes */ +#define IMPERF_PA_OFFSET 156 /* Bytes */ + +#define POLL_DEMAND 1 + +#define LOST_MEDIA_THRESHOLD 3 + +#define MASK_INTERRUPTS 1 +#define UNMASK_INTERRUPTS 0 + +#define DE4X5_STRLEN 8 + +#define DE4X5_INIT 0 /* Initialisation time */ +#define DE4X5_RUN 1 /* Run time */ + +#define DE4X5_SAVE_STATE 0 +#define DE4X5_RESTORE_STATE 1 + +/* +** Address Filtering Modes +*/ +#define PERFECT 0 /* 16 perfect physical addresses */ +#define HASH_PERF 1 /* 1 perfect, 512 multicast addresses */ +#define PERFECT_REJ 2 /* Reject 16 perfect physical addresses */ +#define ALL_HASH 3 /* Hashes all physical & multicast addrs */ + +#define ALL 0 /* Clear out all the setup frame */ +#define PHYS_ADDR_ONLY 1 /* Update the physical address only */ + +/* +** Booleans +*/ +#define NO 0 +#define FALSE 0 + +#define YES ~0 +#define TRUE ~0 + +/* +** Adapter state +*/ +#define INITIALISED 0 /* After h/w initialised and mem alloc'd */ +#define CLOSED 1 /* Ready for opening */ +#define OPEN 2 /* Running */ + +/* +** Various wait times +*/ +#define PDET_LINK_WAIT 1200 /* msecs to wait for link detect bits */ +#define ANS_FINISH_WAIT 1000 /* msecs to wait for link detect bits */ + +/* +** IEEE OUIs for various PHY vendor/chip combos - Reg 2 values only. Since +** the vendors seem split 50-50 on how to calculate the OUI register values +** anyway, just reading Reg2 seems reasonable for now [see de4x5_get_oui()]. +*/ +#define NATIONAL_TX 0x2000 +#define BROADCOM_T4 0x03e0 +#define SEEQ_T4 0x0016 +#define CYPRESS_T4 0x0014 + +/* +** Speed Selection stuff +*/ +#define SET_10Mb {\ + if ((lp->phy[lp->active].id) && (!lp->useSROM || lp->useMII)) {\ + omr = inl(DE4X5_OMR) & ~(OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX);\ + if ((lp->tmp != MII_SR_ASSC) || (lp->autosense != AUTO)) {\ + mii_wr(MII_CR_10|(lp->fdx?MII_CR_FDM:0), MII_CR, lp->phy[lp->active].addr, DE4X5_MII);\ + }\ + omr |= ((lp->fdx ? OMR_FDX : 0) | OMR_TTM);\ + outl(omr, DE4X5_OMR);\ + if (!lp->useSROM) lp->cache.gep = 0;\ + } else if (lp->useSROM && !lp->useMII) {\ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX));\ + omr |= (lp->fdx ? OMR_FDX : 0);\ + outl(omr | (lp->infoblock_csr6 & ~(OMR_SCR | OMR_HBD)), DE4X5_OMR);\ + } else {\ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX));\ + omr |= (lp->fdx ? OMR_FDX : 0);\ + outl(omr | OMR_SDP | OMR_TTM, DE4X5_OMR);\ + lp->cache.gep = (lp->fdx ? 0 : GEP_FDXD);\ + gep_wr(lp->cache.gep, dev);\ + }\ +} + +#define SET_100Mb {\ + if ((lp->phy[lp->active].id) && (!lp->useSROM || lp->useMII)) {\ + int fdx=0;\ + if (lp->phy[lp->active].id == NATIONAL_TX) {\ + mii_wr(mii_rd(0x18, lp->phy[lp->active].addr, DE4X5_MII) & ~0x2000,\ + 0x18, lp->phy[lp->active].addr, DE4X5_MII);\ + }\ + omr = inl(DE4X5_OMR) & ~(OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX);\ + sr = mii_rd(MII_SR, lp->phy[lp->active].addr, DE4X5_MII);\ + if (!(sr & MII_ANA_T4AM) && lp->fdx) fdx=1;\ + if ((lp->tmp != MII_SR_ASSC) || (lp->autosense != AUTO)) {\ + mii_wr(MII_CR_100|(fdx?MII_CR_FDM:0), MII_CR, lp->phy[lp->active].addr, DE4X5_MII);\ + }\ + if (fdx) omr |= OMR_FDX;\ + outl(omr, DE4X5_OMR);\ + if (!lp->useSROM) lp->cache.gep = 0;\ + } else if (lp->useSROM && !lp->useMII) {\ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX));\ + omr |= (lp->fdx ? OMR_FDX : 0);\ + outl(omr | lp->infoblock_csr6, DE4X5_OMR);\ + } else {\ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX));\ + omr |= (lp->fdx ? OMR_FDX : 0);\ + outl(omr | OMR_SDP | OMR_PS | OMR_HBD | OMR_PCS | OMR_SCR, DE4X5_OMR);\ + lp->cache.gep = (lp->fdx ? 0 : GEP_FDXD) | GEP_MODE;\ + gep_wr(lp->cache.gep, dev);\ + }\ +} + +/* FIX ME so I don't jam 10Mb networks */ +#define SET_100Mb_PDET {\ + if ((lp->phy[lp->active].id) && (!lp->useSROM || lp->useMII)) {\ + mii_wr(MII_CR_100|MII_CR_ASSE, MII_CR, lp->phy[lp->active].addr, DE4X5_MII);\ + omr = (inl(DE4X5_OMR) & ~(OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX));\ + outl(omr, DE4X5_OMR);\ + } else if (lp->useSROM && !lp->useMII) {\ + omr = (inl(DE4X5_OMR) & ~(OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX));\ + outl(omr, DE4X5_OMR);\ + } else {\ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR | OMR_FDX));\ + outl(omr | OMR_SDP | OMR_PS | OMR_HBD | OMR_PCS, DE4X5_OMR);\ + lp->cache.gep = (GEP_FDXD | GEP_MODE);\ + gep_wr(lp->cache.gep, dev);\ + }\ +} + +/* +** Include the IOCTL stuff +*/ +#include <linux/sockios.h> + +#define DE4X5IOCTL SIOCDEVPRIVATE + +struct de4x5_ioctl { + unsigned short cmd; /* Command to run */ + unsigned short len; /* Length of the data buffer */ + unsigned char *data; /* Pointer to the data buffer */ +}; + +/* +** Recognised commands for the driver +*/ +#define DE4X5_GET_HWADDR 0x01 /* Get the hardware address */ +#define DE4X5_SET_HWADDR 0x02 /* Set the hardware address */ +#define DE4X5_SET_PROM 0x03 /* Set Promiscuous Mode */ +#define DE4X5_CLR_PROM 0x04 /* Clear Promiscuous Mode */ +#define DE4X5_SAY_BOO 0x05 /* Say "Boo!" to the kernel log file */ +#define DE4X5_GET_MCA 0x06 /* Get a multicast address */ +#define DE4X5_SET_MCA 0x07 /* Set a multicast address */ +#define DE4X5_CLR_MCA 0x08 /* Clear a multicast address */ +#define DE4X5_MCA_EN 0x09 /* Enable a multicast address group */ +#define DE4X5_GET_STATS 0x0a /* Get the driver statistics */ +#define DE4X5_CLR_STATS 0x0b /* Zero out the driver statistics */ +#define DE4X5_GET_OMR 0x0c /* Get the OMR Register contents */ +#define DE4X5_SET_OMR 0x0d /* Set the OMR Register contents */ +#define DE4X5_GET_REG 0x0e /* Get the DE4X5 Registers */ + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) diff --git a/linux/src/drivers/net/de600.c b/linux/src/drivers/net/de600.c new file mode 100644 index 00000000..2488cd76 --- /dev/null +++ b/linux/src/drivers/net/de600.c @@ -0,0 +1,853 @@ +static const char *version = + "de600.c: $Revision: 1.1 $, Bjorn Ekwall (bj0rn@blox.se)\n"; +/* + * de600.c + * + * Linux driver for the D-Link DE-600 Ethernet pocket adapter. + * + * Portions (C) Copyright 1993, 1994 by Bjorn Ekwall + * The Author may be reached as bj0rn@blox.se + * + * Based on adapter information gathered from DE600.ASM by D-Link Inc., + * as included on disk C in the v.2.11 of PC/TCP from FTP Software. + * For DE600.asm: + * Portions (C) Copyright 1990 D-Link, Inc. + * Copyright, 1988-1992, Russell Nelson, Crynwr Software + * + * Adapted to the sample network driver core for linux, + * written by: Donald Becker <becker@super.org> + * C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715 + * + * compile-command: + * "gcc -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer \ + * -m486 -c de600.c + * + **************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + **************************************************************/ +/* Add another "; SLOW_DOWN_IO" here if your adapter won't work OK: */ +#define DE600_SLOW_DOWN SLOW_DOWN_IO; SLOW_DOWN_IO; SLOW_DOWN_IO + + /* + * If you still have trouble reading/writing to the adapter, + * modify the following "#define": (see <asm/io.h> for more info) +#define REALLY_SLOW_IO + */ +#define SLOW_IO_BY_JUMPING /* Looks "better" than dummy write to port 0x80 :-) */ + +/* + * If you want to enable automatic continuous checking for the DE600, + * keep this #define enabled. + * It doesn't cost much per packet, so I think it is worth it! + * If you disagree, comment away the #define, and live with it... + * + */ +#define CHECK_LOST_DE600 + +/* + * Enable this #define if you want the adapter to do a "ifconfig down" on + * itself when we have detected that something is possibly wrong with it. + * The default behaviour is to retry with "adapter_init()" until success. + * This should be used for debugging purposes only. + * (Depends on the CHECK_LOST_DE600 above) + * + */ +#define SHUTDOWN_WHEN_LOST + +/* + * See comment at "de600_rspace()"! + * This is an *ugly* hack, but for now it achieves its goal of + * faking a TCP flow-control that will not flood the poor DE600. + * + * Tricks TCP to announce a small max window (max 2 fast packets please :-) + * + * Comment away at your own risk! + * + * Update: Use the more general per-device maxwindow parameter instead. + */ +#undef FAKE_SMALL_MAX + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifdef DE600_DEBUG +#define PRINTK(x) if (de600_debug >= 2) printk x +#else +#define DE600_DEBUG 0 +#define PRINTK(x) /**/ +#endif +unsigned int de600_debug = DE600_DEBUG; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <linux/in.h> +#include <linux/ptrace.h> +#include <asm/system.h> +#include <linux/errno.h> + +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#ifdef FAKE_SMALL_MAX +static unsigned long de600_rspace(struct sock *sk); +#include <net/sock.h> +#endif + +#define netstats enet_statistics +typedef unsigned char byte; + +/************************************************** + * * + * Definition of D-Link Ethernet Pocket adapter * + * * + **************************************************/ +/* + * D-Link Ethernet pocket adapter ports + */ +/* + * OK, so I'm cheating, but there are an awful lot of + * reads and writes in order to get anything in and out + * of the DE-600 with 4 bits at a time in the parallel port, + * so every saved instruction really helps :-) + * + * That is, I don't care what the device struct says + * but hope that Space.c will keep the rest of the drivers happy. + */ +#ifndef DE600_IO +#define DE600_IO 0x378 +#endif + +#define DATA_PORT (DE600_IO) +#define STATUS_PORT (DE600_IO + 1) +#define COMMAND_PORT (DE600_IO + 2) + +#ifndef DE600_IRQ +#define DE600_IRQ 7 +#endif +/* + * It really should look like this, and autoprobing as well... + * +#define DATA_PORT (dev->base_addr + 0) +#define STATUS_PORT (dev->base_addr + 1) +#define COMMAND_PORT (dev->base_addr + 2) +#define DE600_IRQ dev->irq + */ + +/* + * D-Link COMMAND_PORT commands + */ +#define SELECT_NIC 0x04 /* select Network Interface Card */ +#define SELECT_PRN 0x1c /* select Printer */ +#define NML_PRN 0xec /* normal Printer situation */ +#define IRQEN 0x10 /* enable IRQ line */ + +/* + * D-Link STATUS_PORT + */ +#define RX_BUSY 0x80 +#define RX_GOOD 0x40 +#define TX_FAILED16 0x10 +#define TX_BUSY 0x08 + +/* + * D-Link DATA_PORT commands + * command in low 4 bits + * data in high 4 bits + * select current data nibble with HI_NIBBLE bit + */ +#define WRITE_DATA 0x00 /* write memory */ +#define READ_DATA 0x01 /* read memory */ +#define STATUS 0x02 /* read status register */ +#define COMMAND 0x03 /* write command register (see COMMAND below) */ +#define NULL_COMMAND 0x04 /* null command */ +#define RX_LEN 0x05 /* read received packet length */ +#define TX_ADDR 0x06 /* set adapter transmit memory address */ +#define RW_ADDR 0x07 /* set adapter read/write memory address */ +#define HI_NIBBLE 0x08 /* read/write the high nibble of data, + or-ed with rest of command */ + +/* + * command register, accessed through DATA_PORT with low bits = COMMAND + */ +#define RX_ALL 0x01 /* PROMISCUOUS */ +#define RX_BP 0x02 /* default: BROADCAST & PHYSICAL ADDRESS */ +#define RX_MBP 0x03 /* MULTICAST, BROADCAST & PHYSICAL ADDRESS */ + +#define TX_ENABLE 0x04 /* bit 2 */ +#define RX_ENABLE 0x08 /* bit 3 */ + +#define RESET 0x80 /* set bit 7 high */ +#define STOP_RESET 0x00 /* set bit 7 low */ + +/* + * data to command register + * (high 4 bits in write to DATA_PORT) + */ +#define RX_PAGE2_SELECT 0x10 /* bit 4, only 2 pages to select */ +#define RX_BASE_PAGE 0x20 /* bit 5, always set when specifying RX_ADDR */ +#define FLIP_IRQ 0x40 /* bit 6 */ + +/* + * D-Link adapter internal memory: + * + * 0-2K 1:st transmit page (send from pointer up to 2K) + * 2-4K 2:nd transmit page (send from pointer up to 4K) + * + * 4-6K 1:st receive page (data from 4K upwards) + * 6-8K 2:nd receive page (data from 6K upwards) + * + * 8K+ Adapter ROM (contains magic code and last 3 bytes of Ethernet address) + */ +#define MEM_2K 0x0800 /* 2048 */ +#define MEM_4K 0x1000 /* 4096 */ +#define MEM_6K 0x1800 /* 6144 */ +#define NODE_ADDRESS 0x2000 /* 8192 */ + +#define RUNT 60 /* Too small Ethernet packet */ + +/************************************************** + * * + * End of definition * + * * + **************************************************/ + +/* + * Index to functions, as function prototypes. + */ +/* Routines used internally. (See "convenience macros") */ +static byte de600_read_status(struct device *dev); +static byte de600_read_byte(unsigned char type, struct device *dev); + +/* Put in the device structure. */ +static int de600_open(struct device *dev); +static int de600_close(struct device *dev); +static struct netstats *get_stats(struct device *dev); +static int de600_start_xmit(struct sk_buff *skb, struct device *dev); + +/* Dispatch from interrupts. */ +static void de600_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int de600_tx_intr(struct device *dev, int irq_status); +static void de600_rx_intr(struct device *dev); + +/* Initialization */ +static void trigger_interrupt(struct device *dev); +int de600_probe(struct device *dev); +static int adapter_init(struct device *dev); + +/* + * D-Link driver variables: + */ +static volatile int rx_page = 0; + +#define TX_PAGES 2 +static volatile int tx_fifo[TX_PAGES]; +static volatile int tx_fifo_in = 0; +static volatile int tx_fifo_out = 0; +static volatile int free_tx_pages = TX_PAGES; +static int was_down = 0; + +/* + * Convenience macros/functions for D-Link adapter + */ + +#define select_prn() outb_p(SELECT_PRN, COMMAND_PORT); DE600_SLOW_DOWN +#define select_nic() outb_p(SELECT_NIC, COMMAND_PORT); DE600_SLOW_DOWN + +/* Thanks for hints from Mark Burton <markb@ordern.demon.co.uk> */ +#define de600_put_byte(data) ( \ + outb_p(((data) << 4) | WRITE_DATA , DATA_PORT), \ + outb_p(((data) & 0xf0) | WRITE_DATA | HI_NIBBLE, DATA_PORT)) + +/* + * The first two outb_p()'s below could perhaps be deleted if there + * would be more delay in the last two. Not certain about it yet... + */ +#define de600_put_command(cmd) ( \ + outb_p(( rx_page << 4) | COMMAND , DATA_PORT), \ + outb_p(( rx_page & 0xf0) | COMMAND | HI_NIBBLE, DATA_PORT), \ + outb_p(((rx_page | cmd) << 4) | COMMAND , DATA_PORT), \ + outb_p(((rx_page | cmd) & 0xf0) | COMMAND | HI_NIBBLE, DATA_PORT)) + +#define de600_setup_address(addr,type) ( \ + outb_p((((addr) << 4) & 0xf0) | type , DATA_PORT), \ + outb_p(( (addr) & 0xf0) | type | HI_NIBBLE, DATA_PORT), \ + outb_p((((addr) >> 4) & 0xf0) | type , DATA_PORT), \ + outb_p((((addr) >> 8) & 0xf0) | type | HI_NIBBLE, DATA_PORT)) + +#define rx_page_adr() ((rx_page & RX_PAGE2_SELECT)?(MEM_6K):(MEM_4K)) + +/* Flip bit, only 2 pages */ +#define next_rx_page() (rx_page ^= RX_PAGE2_SELECT) + +#define tx_page_adr(a) (((a) + 1) * MEM_2K) + +static inline byte +de600_read_status(struct device *dev) +{ + byte status; + + outb_p(STATUS, DATA_PORT); + status = inb(STATUS_PORT); + outb_p(NULL_COMMAND | HI_NIBBLE, DATA_PORT); + + return status; +} + +static inline byte +de600_read_byte(unsigned char type, struct device *dev) { /* dev used by macros */ + byte lo; + + (void)outb_p((type), DATA_PORT); + lo = ((unsigned char)inb(STATUS_PORT)) >> 4; + (void)outb_p((type) | HI_NIBBLE, DATA_PORT); + return ((unsigned char)inb(STATUS_PORT) & (unsigned char)0xf0) | lo; +} + +/* + * Open/initialize the board. This is called (in the current kernel) + * after booting when 'ifconfig <dev->name> $IP_ADDR' is run (in rc.inet1). + * + * This routine should set everything up anew at each open, even + * registers that "should" only need to be set once at boot, so that + * there is a non-reboot way to recover if something goes wrong. + */ +static int +de600_open(struct device *dev) +{ + if (request_irq(DE600_IRQ, de600_interrupt, 0, "de600", NULL)) { + printk ("%s: unable to get IRQ %d\n", dev->name, DE600_IRQ); + return 1; + } + irq2dev_map[DE600_IRQ] = dev; + + MOD_INC_USE_COUNT; + dev->start = 1; + if (adapter_init(dev)) { + return 1; + } + + return 0; +} + +/* + * The inverse routine to de600_open(). + */ +static int +de600_close(struct device *dev) +{ + select_nic(); + rx_page = 0; + de600_put_command(RESET); + de600_put_command(STOP_RESET); + de600_put_command(0); + select_prn(); + + if (dev->start) { + free_irq(DE600_IRQ, NULL); + irq2dev_map[DE600_IRQ] = NULL; + dev->start = 0; + MOD_DEC_USE_COUNT; + } + return 0; +} + +static struct netstats * +get_stats(struct device *dev) +{ + return (struct netstats *)(dev->priv); +} + +static inline void +trigger_interrupt(struct device *dev) +{ + de600_put_command(FLIP_IRQ); + select_prn(); + DE600_SLOW_DOWN; + select_nic(); + de600_put_command(0); +} + +/* + * Copy a buffer to the adapter transmit page memory. + * Start sending. + */ +static int +de600_start_xmit(struct sk_buff *skb, struct device *dev) +{ + int transmit_from; + int len; + int tickssofar; + byte *buffer = skb->data; + + /* + * If some higher layer thinks we've missed a + * tx-done interrupt we are passed NULL. + * Caution: dev_tint() handles the cli()/sti() itself. + */ + + if (skb == NULL) { + dev_tint(dev); + return 0; + } + + if (free_tx_pages <= 0) { /* Do timeouts, to avoid hangs. */ + tickssofar = jiffies - dev->trans_start; + + if (tickssofar < 5) + return 1; + + /* else */ + printk("%s: transmit timed out (%d), %s?\n", + dev->name, + tickssofar, + "network cable problem" + ); + /* Restart the adapter. */ + if (adapter_init(dev)) { + return 1; + } + } + + /* Start real output */ + PRINTK(("de600_start_xmit:len=%d, page %d/%d\n", skb->len, tx_fifo_in, free_tx_pages)); + + if ((len = skb->len) < RUNT) + len = RUNT; + + cli(); + select_nic(); + tx_fifo[tx_fifo_in] = transmit_from = tx_page_adr(tx_fifo_in) - len; + tx_fifo_in = (tx_fifo_in + 1) % TX_PAGES; /* Next free tx page */ + +#ifdef CHECK_LOST_DE600 + /* This costs about 40 instructions per packet... */ + de600_setup_address(NODE_ADDRESS, RW_ADDR); + de600_read_byte(READ_DATA, dev); + if (was_down || (de600_read_byte(READ_DATA, dev) != 0xde)) { + if (adapter_init(dev)) { + sti(); + return 1; + } + } +#endif + + de600_setup_address(transmit_from, RW_ADDR); + for ( ; len > 0; --len, ++buffer) + de600_put_byte(*buffer); + + if (free_tx_pages-- == TX_PAGES) { /* No transmission going on */ + dev->trans_start = jiffies; + dev->tbusy = 0; /* allow more packets into adapter */ + /* Send page and generate a faked interrupt */ + de600_setup_address(transmit_from, TX_ADDR); + de600_put_command(TX_ENABLE); + } + else { + dev->tbusy = !free_tx_pages; + select_prn(); + } + + sti(); /* interrupts back on */ + +#ifdef FAKE_SMALL_MAX + /* This will "patch" the socket TCP proto at an early moment */ + if (skb->sk && (skb->sk->protocol == IPPROTO_TCP) && + (skb->sk->prot->rspace != &de600_rspace)) + skb->sk->prot->rspace = de600_rspace; /* Ugh! */ +#endif + + dev_kfree_skb (skb, FREE_WRITE); + + return 0; +} + +/* + * The typical workload of the driver: + * Handle the network interface interrupts. + */ +static void +de600_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = irq2dev_map[irq]; + byte irq_status; + int retrig = 0; + int boguscount = 0; + + /* This might just as well be deleted now, no crummy drivers present :-) */ + if ((dev == NULL) || (dev->start == 0) || (DE600_IRQ != irq)) { + printk("%s: bogus interrupt %d\n", dev?dev->name:"DE-600", irq); + return; + } + + dev->interrupt = 1; + select_nic(); + irq_status = de600_read_status(dev); + + do { + PRINTK(("de600_interrupt (%02X)\n", irq_status)); + + if (irq_status & RX_GOOD) + de600_rx_intr(dev); + else if (!(irq_status & RX_BUSY)) + de600_put_command(RX_ENABLE); + + /* Any transmission in progress? */ + if (free_tx_pages < TX_PAGES) + retrig = de600_tx_intr(dev, irq_status); + else + retrig = 0; + + irq_status = de600_read_status(dev); + } while ( (irq_status & RX_GOOD) || ((++boguscount < 100) && retrig) ); + /* + * Yeah, it _looks_ like busy waiting, smells like busy waiting + * and I know it's not PC, but please, it will only occur once + * in a while and then only for a loop or so (< 1ms for sure!) + */ + + /* Enable adapter interrupts */ + dev->interrupt = 0; + select_prn(); + + if (retrig) + trigger_interrupt(dev); + + sti(); + return; +} + +static int +de600_tx_intr(struct device *dev, int irq_status) +{ + /* + * Returns 1 if tx still not done + */ + + mark_bh(NET_BH); + /* Check if current transmission is done yet */ + if (irq_status & TX_BUSY) + return 1; /* tx not done, try again */ + + /* else */ + /* If last transmission OK then bump fifo index */ + if (!(irq_status & TX_FAILED16)) { + tx_fifo_out = (tx_fifo_out + 1) % TX_PAGES; + ++free_tx_pages; + ((struct netstats *)(dev->priv))->tx_packets++; + dev->tbusy = 0; + } + + /* More to send, or resend last packet? */ + if ((free_tx_pages < TX_PAGES) || (irq_status & TX_FAILED16)) { + dev->trans_start = jiffies; + de600_setup_address(tx_fifo[tx_fifo_out], TX_ADDR); + de600_put_command(TX_ENABLE); + return 1; + } + /* else */ + + return 0; +} + +/* + * We have a good packet, get it out of the adapter. + */ +static void +de600_rx_intr(struct device *dev) +{ + struct sk_buff *skb; + int i; + int read_from; + int size; + register unsigned char *buffer; + + cli(); + /* Get size of received packet */ + size = de600_read_byte(RX_LEN, dev); /* low byte */ + size += (de600_read_byte(RX_LEN, dev) << 8); /* high byte */ + size -= 4; /* Ignore trailing 4 CRC-bytes */ + + /* Tell adapter where to store next incoming packet, enable receiver */ + read_from = rx_page_adr(); + next_rx_page(); + de600_put_command(RX_ENABLE); + sti(); + + if ((size < 32) || (size > 1535)) { + printk("%s: Bogus packet size %d.\n", dev->name, size); + if (size > 10000) + adapter_init(dev); + return; + } + + skb = dev_alloc_skb(size+2); + sti(); + if (skb == NULL) { + printk("%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, size); + return; + } + /* else */ + + skb->dev = dev; + skb_reserve(skb,2); /* Align */ + + /* 'skb->data' points to the start of sk_buff data area. */ + buffer = skb_put(skb,size); + + /* copy the packet into the buffer */ + de600_setup_address(read_from, RW_ADDR); + for (i = size; i > 0; --i, ++buffer) + *buffer = de600_read_byte(READ_DATA, dev); + + ((struct netstats *)(dev->priv))->rx_packets++; /* count all receives */ + + skb->protocol=eth_type_trans(skb,dev); + + netif_rx(skb); + /* + * If any worth-while packets have been received, netif_rx() + * has done a mark_bh(INET_BH) for us and will work on them + * when we get to the bottom-half routine. + */ +} + +int +de600_probe(struct device *dev) +{ + int i; + static struct netstats de600_netstats; + /*dev->priv = kmalloc(sizeof(struct netstats), GFP_KERNEL);*/ + + printk("%s: D-Link DE-600 pocket adapter", dev->name); + /* Alpha testers must have the version number to report bugs. */ + if (de600_debug > 1) + printk(version); + + /* probe for adapter */ + rx_page = 0; + select_nic(); + (void)de600_read_status(dev); + de600_put_command(RESET); + de600_put_command(STOP_RESET); + if (de600_read_status(dev) & 0xf0) { + printk(": not at I/O %#3x.\n", DATA_PORT); + return ENODEV; + } + + /* + * Maybe we found one, + * have to check if it is a D-Link DE-600 adapter... + */ + + /* Get the adapter ethernet address from the ROM */ + de600_setup_address(NODE_ADDRESS, RW_ADDR); + for (i = 0; i < ETH_ALEN; i++) { + dev->dev_addr[i] = de600_read_byte(READ_DATA, dev); + dev->broadcast[i] = 0xff; + } + + /* Check magic code */ + if ((dev->dev_addr[1] == 0xde) && (dev->dev_addr[2] == 0x15)) { + /* OK, install real address */ + dev->dev_addr[0] = 0x00; + dev->dev_addr[1] = 0x80; + dev->dev_addr[2] = 0xc8; + dev->dev_addr[3] &= 0x0f; + dev->dev_addr[3] |= 0x70; + } else { + printk(" not identified in the printer port\n"); + return ENODEV; + } + +#if 0 /* Not yet */ + if (check_region(DE600_IO, 3)) { + printk(", port 0x%x busy\n", DE600_IO); + return EBUSY; + } +#endif + request_region(DE600_IO, 3, "de600"); + + printk(", Ethernet Address: %02X", dev->dev_addr[0]); + for (i = 1; i < ETH_ALEN; i++) + printk(":%02X",dev->dev_addr[i]); + printk("\n"); + + /* Initialize the device structure. */ + /*dev->priv = kmalloc(sizeof(struct netstats), GFP_KERNEL);*/ + dev->priv = &de600_netstats; + + memset(dev->priv, 0, sizeof(struct netstats)); + dev->get_stats = get_stats; + + dev->open = de600_open; + dev->stop = de600_close; + dev->hard_start_xmit = &de600_start_xmit; + + ether_setup(dev); + + dev->flags&=~IFF_MULTICAST; + + select_prn(); + return 0; +} + +static int +adapter_init(struct device *dev) +{ + int i; + long flags; + + save_flags(flags); + cli(); + + select_nic(); + rx_page = 0; /* used by RESET */ + de600_put_command(RESET); + de600_put_command(STOP_RESET); +#ifdef CHECK_LOST_DE600 + /* Check if it is still there... */ + /* Get the some bytes of the adapter ethernet address from the ROM */ + de600_setup_address(NODE_ADDRESS, RW_ADDR); + de600_read_byte(READ_DATA, dev); + if ((de600_read_byte(READ_DATA, dev) != 0xde) || + (de600_read_byte(READ_DATA, dev) != 0x15)) { + /* was: if (de600_read_status(dev) & 0xf0) { */ + printk("Something has happened to the DE-600! Please check it" +#ifdef SHUTDOWN_WHEN_LOST + " and do a new ifconfig" +#endif /* SHUTDOWN_WHEN_LOST */ + "!\n"); +#ifdef SHUTDOWN_WHEN_LOST + /* Goodbye, cruel world... */ + dev->flags &= ~IFF_UP; + de600_close(dev); +#endif /* SHUTDOWN_WHEN_LOST */ + was_down = 1; + dev->tbusy = 1; /* Transmit busy... */ + restore_flags(flags); + return 1; /* failed */ + } +#endif /* CHECK_LOST_DE600 */ + if (was_down) { + printk("Thanks, I feel much better now!\n"); + was_down = 0; + } + + dev->tbusy = 0; /* Transmit busy... */ + dev->interrupt = 0; + tx_fifo_in = 0; + tx_fifo_out = 0; + free_tx_pages = TX_PAGES; + + /* set the ether address. */ + de600_setup_address(NODE_ADDRESS, RW_ADDR); + for (i = 0; i < ETH_ALEN; i++) + de600_put_byte(dev->dev_addr[i]); + + /* where to start saving incoming packets */ + rx_page = RX_BP | RX_BASE_PAGE; + de600_setup_address(MEM_4K, RW_ADDR); + /* Enable receiver */ + de600_put_command(RX_ENABLE); + select_prn(); + restore_flags(flags); + + return 0; /* OK */ +} + +#ifdef FAKE_SMALL_MAX +/* + * The new router code (coming soon 8-) ) will fix this properly. + */ +#define DE600_MIN_WINDOW 1024 +#define DE600_MAX_WINDOW 2048 +#define DE600_TCP_WINDOW_DIFF 1024 +/* + * Copied from "net/inet/sock.c" + * + * Sets a lower max receive window in order to achieve <= 2 + * packets arriving at the adapter in fast succession. + * (No way that a DE-600 can keep up with a net saturated + * with packets homing in on it :-( ) + * + * Since there are only 2 receive buffers in the DE-600 + * and it takes some time to copy from the adapter, + * this is absolutely necessary for any TCP performance whatsoever! + * + * Note that the returned window info will never be smaller than + * DE600_MIN_WINDOW, i.e. 1024 + * This differs from the standard function, that can return an + * arbitrarily small window! + */ +#define min(a,b) ((a)<(b)?(a):(b)) +static unsigned long +de600_rspace(struct sock *sk) +{ + int amt; + + if (sk != NULL) { +/* + * Hack! You might want to play with commenting away the following line, + * if you know what you do! + sk->max_unacked = DE600_MAX_WINDOW - DE600_TCP_WINDOW_DIFF; + */ + + if (sk->rmem_alloc >= sk->rcvbuf-2*DE600_MIN_WINDOW) return(0); + amt = min((sk->rcvbuf-sk->rmem_alloc)/2/*-DE600_MIN_WINDOW*/, DE600_MAX_WINDOW); + if (amt < 0) return(0); + return(amt); + } + return(0); +} +#endif + +#ifdef MODULE +static char nullname[8]; +static struct device de600_dev = { + nullname, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, de600_probe }; + +int +init_module(void) +{ + if (register_netdev(&de600_dev) != 0) + return -EIO; + return 0; +} + +void +cleanup_module(void) +{ + unregister_netdev(&de600_dev); + release_region(DE600_IO, 3); +} +#endif /* MODULE */ +/* + * Local variables: + * kernel-compile-command: "gcc -D__KERNEL__ -Ilinux/include -I../../net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de600.c" + * module-compile-command: "gcc -D__KERNEL__ -DMODULE -Ilinux/include -I../../net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de600.c" + * compile-command: "gcc -D__KERNEL__ -DMODULE -Ilinux/include -I../../net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de600.c" + * End: + */ diff --git a/linux/src/drivers/net/de620.c b/linux/src/drivers/net/de620.c new file mode 100644 index 00000000..ec639101 --- /dev/null +++ b/linux/src/drivers/net/de620.c @@ -0,0 +1,1045 @@ +/* + * de620.c $Revision: 1.1 $ BETA + * + * + * Linux driver for the D-Link DE-620 Ethernet pocket adapter. + * + * Portions (C) Copyright 1993, 1994 by Bjorn Ekwall <bj0rn@blox.se> + * + * Based on adapter information gathered from DOS packetdriver + * sources from D-Link Inc: (Special thanks to Henry Ngai of D-Link.) + * Portions (C) Copyright D-Link SYSTEM Inc. 1991, 1992 + * Copyright, 1988, Russell Nelson, Crynwr Software + * + * Adapted to the sample network driver core for linux, + * written by: Donald Becker <becker@super.org> + * (Now at <becker@cesdis.gsfc.nasa.gov> + * + * Valuable assistance from: + * J. Joshua Kopper <kopper@rtsg.mot.com> + * Olav Kvittem <Olav.Kvittem@uninett.no> + * Germano Caronni <caronni@nessie.cs.id.ethz.ch> + * Jeremy Fitzhardinge <jeremy@suite.sw.oz.au> + * + *****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ +static const char *version = + "de620.c: $Revision: 1.1 $, Bjorn Ekwall <bj0rn@blox.se>\n"; + +/*********************************************************************** + * + * "Tuning" section. + * + * Compile-time options: (see below for descriptions) + * -DDE620_IO=0x378 (lpt1) + * -DDE620_IRQ=7 (lpt1) + * -DDE602_DEBUG=... + * -DSHUTDOWN_WHEN_LOST + * -DCOUNT_LOOPS + * -DLOWSPEED + * -DREAD_DELAY + * -DWRITE_DELAY + */ + +/* + * This driver assumes that the printer port is a "normal", + * dumb, uni-directional port! + * If your port is "fancy" in any way, please try to set it to "normal" + * with your BIOS setup. I have no access to machines with bi-directional + * ports, so I can't test such a driver :-( + * (Yes, I _know_ it is possible to use DE620 with bidirectional ports...) + * + * There are some clones of DE620 out there, with different names. + * If the current driver does not recognize a clone, try to change + * the following #define to: + * + * #define DE620_CLONE 1 + */ +#define DE620_CLONE 0 + +/* + * If the adapter has problems with high speeds, enable this #define + * otherwise full printerport speed will be attempted. + * + * You can tune the READ_DELAY/WRITE_DELAY below if you enable LOWSPEED + * +#define LOWSPEED + */ + +#ifndef READ_DELAY +#define READ_DELAY 100 /* adapter internal read delay in 100ns units */ +#endif + +#ifndef WRITE_DELAY +#define WRITE_DELAY 100 /* adapter internal write delay in 100ns units */ +#endif + +/* + * Enable this #define if you want the adapter to do a "ifconfig down" on + * itself when we have detected that something is possibly wrong with it. + * The default behaviour is to retry with "adapter_init()" until success. + * This should be used for debugging purposes only. + * +#define SHUTDOWN_WHEN_LOST + */ + +/* + * Enable debugging by "-DDE620_DEBUG=3" when compiling, + * OR in "./CONFIG" + * OR by enabling the following #define + * + * use 0 for production, 1 for verification, >2 for debug + * +#define DE620_DEBUG 3 + */ + +#ifdef LOWSPEED +/* + * Enable this #define if you want to see debugging output that show how long + * we have to wait before the DE-620 is ready for the next read/write/command. + * +#define COUNT_LOOPS + */ +#endif + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <linux/in.h> +#include <linux/ptrace.h> +#include <asm/system.h> +#include <linux/errno.h> + +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* Constant definitions for the DE-620 registers, commands and bits */ +#include "de620.h" + +#define netstats enet_statistics +typedef unsigned char byte; + +/******************************************************* + * * + * Definition of D-Link DE-620 Ethernet Pocket adapter * + * See also "de620.h" * + * * + *******************************************************/ +#ifndef DE620_IO /* Compile-time configurable */ +#define DE620_IO 0x378 +#endif + +#ifndef DE620_IRQ /* Compile-time configurable */ +#define DE620_IRQ 7 +#endif + +#define DATA_PORT (dev->base_addr) +#define STATUS_PORT (dev->base_addr + 1) +#define COMMAND_PORT (dev->base_addr + 2) + +#define RUNT 60 /* Too small Ethernet packet */ +#define GIANT 1514 /* largest legal size packet, no fcs */ + +#ifdef DE620_DEBUG /* Compile-time configurable */ +#define PRINTK(x) if (de620_debug >= 2) printk x +#else +#define DE620_DEBUG 0 +#define PRINTK(x) /**/ +#endif + + +/* + * Force media with insmod: + * insmod de620.o bnc=1 + * or + * insmod de620.o utp=1 + * + * Force io and/or irq with insmod: + * insmod de620.o io=0x378 irq=7 + * + * Make a clone skip the Ethernet-address range check: + * insmod de620.o clone=1 + */ +static int bnc = 0; +static int utp = 0; +static int io = DE620_IO; +static int irq = DE620_IRQ; +static int clone = DE620_CLONE; + +static unsigned int de620_debug = DE620_DEBUG; + +/*********************************************** + * * + * Index to functions, as function prototypes. * + * * + ***********************************************/ + +/* + * Routines used internally. (See also "convenience macros.. below") + */ + +/* Put in the device structure. */ +static int de620_open(struct device *); +static int de620_close(struct device *); +static struct netstats *get_stats(struct device *); +static void de620_set_multicast_list(struct device *); +static int de620_start_xmit(struct sk_buff *, struct device *); + +/* Dispatch from interrupts. */ +static void de620_interrupt(int, void *, struct pt_regs *); +static int de620_rx_intr(struct device *); + +/* Initialization */ +static int adapter_init(struct device *); +int de620_probe(struct device *); +static int read_eeprom(struct device *); + + +/* + * D-Link driver variables: + */ +#define SCR_DEF NIBBLEMODE |INTON | SLEEP | AUTOTX +#define TCR_DEF RXPB /* not used: | TXSUCINT | T16INT */ +#define DE620_RX_START_PAGE 12 /* 12 pages (=3k) reserved for tx */ +#define DEF_NIC_CMD IRQEN | ICEN | DS1 + +static volatile byte NIC_Cmd; +static volatile byte next_rx_page; +static byte first_rx_page; +static byte last_rx_page; +static byte EIPRegister; + +static struct nic { + byte NodeID[6]; + byte RAM_Size; + byte Model; + byte Media; + byte SCR; +} nic_data; + +/********************************************************** + * * + * Convenience macros/functions for D-Link DE-620 adapter * + * * + **********************************************************/ +#define de620_tx_buffs(dd) (inb(STATUS_PORT) & (TXBF0 | TXBF1)) +#define de620_flip_ds(dd) NIC_Cmd ^= DS0 | DS1; outb(NIC_Cmd, COMMAND_PORT); + +/* Check for ready-status, and return a nibble (high 4 bits) for data input */ +#ifdef COUNT_LOOPS +static int tot_cnt; +#endif +static inline byte +de620_ready(struct device *dev) +{ + byte value; + register short int cnt = 0; + + while ((((value = inb(STATUS_PORT)) & READY) == 0) && (cnt <= 1000)) + ++cnt; + +#ifdef COUNT_LOOPS + tot_cnt += cnt; +#endif + return value & 0xf0; /* nibble */ +} + +static inline void +de620_send_command(struct device *dev, byte cmd) +{ + de620_ready(dev); + if (cmd == W_DUMMY) + outb(NIC_Cmd, COMMAND_PORT); + + outb(cmd, DATA_PORT); + + outb(NIC_Cmd ^ CS0, COMMAND_PORT); + de620_ready(dev); + outb(NIC_Cmd, COMMAND_PORT); +} + +static inline void +de620_put_byte(struct device *dev, byte value) +{ + /* The de620_ready() makes 7 loops, on the average, on a DX2/66 */ + de620_ready(dev); + outb(value, DATA_PORT); + de620_flip_ds(dev); +} + +static inline byte +de620_read_byte(struct device *dev) +{ + byte value; + + /* The de620_ready() makes 7 loops, on the average, on a DX2/66 */ + value = de620_ready(dev); /* High nibble */ + de620_flip_ds(dev); + value |= de620_ready(dev) >> 4; /* Low nibble */ + return value; +} + +static inline void +de620_write_block(struct device *dev, byte *buffer, int count) +{ +#ifndef LOWSPEED + byte uflip = NIC_Cmd ^ (DS0 | DS1); + byte dflip = NIC_Cmd; +#else /* LOWSPEED */ +#ifdef COUNT_LOOPS + int bytes = count; +#endif /* COUNT_LOOPS */ +#endif /* LOWSPEED */ + +#ifdef LOWSPEED +#ifdef COUNT_LOOPS + tot_cnt = 0; +#endif /* COUNT_LOOPS */ + /* No further optimization useful, the limit is in the adapter. */ + for ( ; count > 0; --count, ++buffer) { + de620_put_byte(dev,*buffer); + } + de620_send_command(dev,W_DUMMY); +#ifdef COUNT_LOOPS + /* trial debug output: loops per byte in de620_ready() */ + printk("WRITE(%d)\n", tot_cnt/((bytes?bytes:1))); +#endif /* COUNT_LOOPS */ +#else /* not LOWSPEED */ + for ( ; count > 0; count -=2) { + outb(*buffer++, DATA_PORT); + outb(uflip, COMMAND_PORT); + outb(*buffer++, DATA_PORT); + outb(dflip, COMMAND_PORT); + } + de620_send_command(dev,W_DUMMY); +#endif /* LOWSPEED */ +} + +static inline void +de620_read_block(struct device *dev, byte *data, int count) +{ +#ifndef LOWSPEED + byte value; + byte uflip = NIC_Cmd ^ (DS0 | DS1); + byte dflip = NIC_Cmd; +#else /* LOWSPEED */ +#ifdef COUNT_LOOPS + int bytes = count; + + tot_cnt = 0; +#endif /* COUNT_LOOPS */ +#endif /* LOWSPEED */ + +#ifdef LOWSPEED + /* No further optimization useful, the limit is in the adapter. */ + while (count-- > 0) { + *data++ = de620_read_byte(dev); + de620_flip_ds(dev); + } +#ifdef COUNT_LOOPS + /* trial debug output: loops per byte in de620_ready() */ + printk("READ(%d)\n", tot_cnt/(2*(bytes?bytes:1))); +#endif /* COUNT_LOOPS */ +#else /* not LOWSPEED */ + while (count-- > 0) { + value = inb(STATUS_PORT) & 0xf0; /* High nibble */ + outb(uflip, COMMAND_PORT); + *data++ = value | inb(STATUS_PORT) >> 4; /* Low nibble */ + outb(dflip , COMMAND_PORT); + } +#endif /* LOWSPEED */ +} + +static inline void +de620_set_delay(struct device *dev) +{ + de620_ready(dev); + outb(W_DFR, DATA_PORT); + outb(NIC_Cmd ^ CS0, COMMAND_PORT); + + de620_ready(dev); +#ifdef LOWSPEED + outb(WRITE_DELAY, DATA_PORT); +#else + outb(0, DATA_PORT); +#endif + de620_flip_ds(dev); + + de620_ready(dev); +#ifdef LOWSPEED + outb(READ_DELAY, DATA_PORT); +#else + outb(0, DATA_PORT); +#endif + de620_flip_ds(dev); +} + +static inline void +de620_set_register(struct device *dev, byte reg, byte value) +{ + de620_ready(dev); + outb(reg, DATA_PORT); + outb(NIC_Cmd ^ CS0, COMMAND_PORT); + + de620_put_byte(dev, value); +} + +static inline byte +de620_get_register(struct device *dev, byte reg) +{ + byte value; + + de620_send_command(dev,reg); + value = de620_read_byte(dev); + de620_send_command(dev,W_DUMMY); + + return value; +} + +/********************************************************************* + * + * Open/initialize the board. + * + * This routine should set everything up anew at each open, even + * registers that "should" only need to be set once at boot, so that + * there is a non-reboot way to recover if something goes wrong. + * + */ +static int +de620_open(struct device *dev) +{ + if (request_irq(dev->irq, de620_interrupt, 0, "de620", NULL)) { + printk ("%s: unable to get IRQ %d\n", dev->name, dev->irq); + return 1; + } + irq2dev_map[dev->irq] = dev; + + MOD_INC_USE_COUNT; + if (adapter_init(dev)) { + return 1; + } + dev->start = 1; + return 0; +} + +/************************************************ + * + * The inverse routine to de620_open(). + * + */ +static int +de620_close(struct device *dev) +{ + /* disable recv */ + de620_set_register(dev, W_TCR, RXOFF); + + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = NULL; + + dev->start = 0; + MOD_DEC_USE_COUNT; + return 0; +} + +/********************************************* + * + * Return current statistics + * + */ +static struct netstats * +get_stats(struct device *dev) +{ + return (struct netstats *)(dev->priv); +} + +/********************************************* + * + * Set or clear the multicast filter for this adaptor. + * (no real multicast implemented for the DE-620, but she can be promiscuous...) + * + */ + +static void de620_set_multicast_list(struct device *dev) +{ + if (dev->mc_count || dev->flags&(IFF_ALLMULTI|IFF_PROMISC)) + { /* Enable promiscuous mode */ + /* + * We must make the kernel realise we had to move + * into promisc mode or we start all out war on + * the cable. - AC + */ + dev->flags|=IFF_PROMISC; + + de620_set_register(dev, W_TCR, (TCR_DEF & ~RXPBM) | RXALL); + } + else + { /* Disable promiscuous mode, use normal mode */ + de620_set_register(dev, W_TCR, TCR_DEF); + } +} + +/******************************************************* + * + * Copy a buffer to the adapter transmit page memory. + * Start sending. + */ +static int +de620_start_xmit(struct sk_buff *skb, struct device *dev) +{ + unsigned long flags; + int len; + int tickssofar; + byte *buffer = skb->data; + byte using_txbuf; + + /* + * If some higher layer thinks we've missed a + * tx-done interrupt we are passed NULL. + * Caution: dev_tint() handles the cli()/sti() itself. + */ + + if (skb == NULL) { + dev_tint(dev); + return 0; + } + + using_txbuf = de620_tx_buffs(dev); /* Peek at the adapter */ + dev->tbusy = (using_txbuf == (TXBF0 | TXBF1)); /* Boolean! */ + + if (dev->tbusy) { /* Do timeouts, to avoid hangs. */ + tickssofar = jiffies - dev->trans_start; + + if (tickssofar < 5) + return 1; + + /* else */ + printk("%s: transmit timed out (%d), %s?\n", + dev->name, + tickssofar, + "network cable problem" + ); + /* Restart the adapter. */ + if (adapter_init(dev)) /* maybe close it */ + return 1; + } + + if ((len = skb->len) < RUNT) + len = RUNT; + if (len & 1) /* send an even number of bytes */ + ++len; + + /* Start real output */ + save_flags(flags); + cli(); + + PRINTK(("de620_start_xmit: len=%d, bufs 0x%02x\n", + (int)skb->len, using_txbuf)); + + /* select a free tx buffer. if there is one... */ + switch (using_txbuf) { + default: /* both are free: use TXBF0 */ + case TXBF1: /* use TXBF0 */ + de620_send_command(dev,W_CR | RW0); + using_txbuf |= TXBF0; + break; + + case TXBF0: /* use TXBF1 */ + de620_send_command(dev,W_CR | RW1); + using_txbuf |= TXBF1; + break; + + case (TXBF0 | TXBF1): /* NONE!!! */ + printk("de620: Ouch! No tx-buffer available!\n"); + restore_flags(flags); + return 1; + break; + } + de620_write_block(dev, buffer, len); + + dev->trans_start = jiffies; + dev->tbusy = (using_txbuf == (TXBF0 | TXBF1)); /* Boolean! */ + + ((struct netstats *)(dev->priv))->tx_packets++; + + restore_flags(flags); /* interrupts maybe back on */ + + dev_kfree_skb (skb, FREE_WRITE); + + return 0; +} + +/***************************************************** + * + * Handle the network interface interrupts. + * + */ +static void +de620_interrupt(int irq_in, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = irq2dev_map[irq_in]; + byte irq_status; + int bogus_count = 0; + int again = 0; + + /* This might be deleted now, no crummy drivers present :-) Or..? */ + if ((dev == NULL) || (irq != irq_in)) { + printk("%s: bogus interrupt %d\n", dev?dev->name:"de620", irq_in); + return; + } + + cli(); + dev->interrupt = 1; + + /* Read the status register (_not_ the status port) */ + irq_status = de620_get_register(dev, R_STS); + + PRINTK(("de620_interrupt (%2.2X)\n", irq_status)); + + if (irq_status & RXGOOD) { + do { + again = de620_rx_intr(dev); + PRINTK(("again=%d\n", again)); + } + while (again && (++bogus_count < 100)); + } + + dev->tbusy = (de620_tx_buffs(dev) == (TXBF0 | TXBF1)); /* Boolean! */ + + dev->interrupt = 0; + sti(); + return; +} + +/************************************** + * + * Get a packet from the adapter + * + * Send it "upstairs" + * + */ +static int +de620_rx_intr(struct device *dev) +{ + struct header_buf { + byte status; + byte Rx_NextPage; + unsigned short Rx_ByteCount; + } header_buf; + struct sk_buff *skb; + int size; + byte *buffer; + byte pagelink; + byte curr_page; + + PRINTK(("de620_rx_intr: next_rx_page = %d\n", next_rx_page)); + + /* Tell the adapter that we are going to read data, and from where */ + de620_send_command(dev, W_CR | RRN); + de620_set_register(dev, W_RSA1, next_rx_page); + de620_set_register(dev, W_RSA0, 0); + + /* Deep breath, and away we goooooo */ + de620_read_block(dev, (byte *)&header_buf, sizeof(struct header_buf)); + PRINTK(("page status=0x%02x, nextpage=%d, packetsize=%d\n", + header_buf.status, header_buf.Rx_NextPage, header_buf.Rx_ByteCount)); + + /* Plausible page header? */ + pagelink = header_buf.Rx_NextPage; + if ((pagelink < first_rx_page) || (last_rx_page < pagelink)) { + /* Ouch... Forget it! Skip all and start afresh... */ + printk("%s: Ring overrun? Restoring...\n", dev->name); + /* You win some, you loose some. And sometimes plenty... */ + adapter_init(dev); + ((struct netstats *)(dev->priv))->rx_over_errors++; + return 0; + } + + /* OK, this look good, so far. Let's see if it's consistent... */ + /* Let's compute the start of the next packet, based on where we are */ + pagelink = next_rx_page + + ((header_buf.Rx_ByteCount + (4 - 1 + 0x100)) >> 8); + + /* Are we going to wrap around the page counter? */ + if (pagelink > last_rx_page) + pagelink -= (last_rx_page - first_rx_page + 1); + + /* Is the _computed_ next page number equal to what the adapter says? */ + if (pagelink != header_buf.Rx_NextPage) { + /* Naah, we'll skip this packet. Probably bogus data as well */ + printk("%s: Page link out of sync! Restoring...\n", dev->name); + next_rx_page = header_buf.Rx_NextPage; /* at least a try... */ + de620_send_command(dev, W_DUMMY); + de620_set_register(dev, W_NPRF, next_rx_page); + ((struct netstats *)(dev->priv))->rx_over_errors++; + return 0; + } + next_rx_page = pagelink; + + size = header_buf.Rx_ByteCount - 4; + if ((size < RUNT) || (GIANT < size)) { + printk("%s: Illegal packet size: %d!\n", dev->name, size); + } + else { /* Good packet? */ + skb = dev_alloc_skb(size+2); + if (skb == NULL) { /* Yeah, but no place to put it... */ + printk("%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, size); + ((struct netstats *)(dev->priv))->rx_dropped++; + } + else { /* Yep! Go get it! */ + skb_reserve(skb,2); /* Align */ + skb->dev = dev; + skb->free = 1; + /* skb->data points to the start of sk_buff data area */ + buffer = skb_put(skb,size); + /* copy the packet into the buffer */ + de620_read_block(dev, buffer, size); + PRINTK(("Read %d bytes\n", size)); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); /* deliver it "upstairs" */ + /* count all receives */ + ((struct netstats *)(dev->priv))->rx_packets++; + } + } + + /* Let's peek ahead to see if we have read the last current packet */ + /* NOTE! We're _not_ checking the 'EMPTY'-flag! This seems better... */ + curr_page = de620_get_register(dev, R_CPR); + de620_set_register(dev, W_NPRF, next_rx_page); + PRINTK(("next_rx_page=%d CPR=%d\n", next_rx_page, curr_page)); + + return (next_rx_page != curr_page); /* That was slightly tricky... */ +} + +/********************************************* + * + * Reset the adapter to a known state + * + */ +static int +adapter_init(struct device *dev) +{ + int i; + static int was_down = 0; + + if ((nic_data.Model == 3) || (nic_data.Model == 0)) { /* CT */ + EIPRegister = NCTL0; + if (nic_data.Media != 1) + EIPRegister |= NIS0; /* not BNC */ + } + else if (nic_data.Model == 2) { /* UTP */ + EIPRegister = NCTL0 | NIS0; + } + + if (utp) + EIPRegister = NCTL0 | NIS0; + if (bnc) + EIPRegister = NCTL0; + + de620_send_command(dev, W_CR | RNOP | CLEAR); + de620_send_command(dev, W_CR | RNOP); + + de620_set_register(dev, W_SCR, SCR_DEF); + /* disable recv to wait init */ + de620_set_register(dev, W_TCR, RXOFF); + + /* Set the node ID in the adapter */ + for (i = 0; i < 6; ++i) { /* W_PARn = 0xaa + n */ + de620_set_register(dev, W_PAR0 + i, dev->dev_addr[i]); + } + + de620_set_register(dev, W_EIP, EIPRegister); + + next_rx_page = first_rx_page = DE620_RX_START_PAGE; + if (nic_data.RAM_Size) + last_rx_page = nic_data.RAM_Size - 1; + else /* 64k RAM */ + last_rx_page = 255; + + de620_set_register(dev, W_SPR, first_rx_page); /* Start Page Register*/ + de620_set_register(dev, W_EPR, last_rx_page); /* End Page Register */ + de620_set_register(dev, W_CPR, first_rx_page);/*Current Page Register*/ + de620_send_command(dev, W_NPR | first_rx_page); /* Next Page Register*/ + de620_send_command(dev, W_DUMMY); + de620_set_delay(dev); + + /* Final sanity check: Anybody out there? */ + /* Let's hope some bits from the statusregister make a good check */ +#define CHECK_MASK ( 0 | TXSUC | T16 | 0 | RXCRC | RXSHORT | 0 | 0 ) +#define CHECK_OK ( 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 ) + /* success: X 0 0 X 0 0 X X */ + /* ignore: EEDI RXGOOD COLS LNKS*/ + + if (((i = de620_get_register(dev, R_STS)) & CHECK_MASK) != CHECK_OK) { + printk("Something has happened to the DE-620! Please check it" +#ifdef SHUTDOWN_WHEN_LOST + " and do a new ifconfig" +#endif + "! (%02x)\n", i); +#ifdef SHUTDOWN_WHEN_LOST + /* Goodbye, cruel world... */ + dev->flags &= ~IFF_UP; + de620_close(dev); +#endif + was_down = 1; + return 1; /* failed */ + } + if (was_down) { + printk("Thanks, I feel much better now!\n"); + was_down = 0; + } + + /* All OK, go ahead... */ + de620_set_register(dev, W_TCR, TCR_DEF); + + return 0; /* all ok */ +} + +/****************************************************************************** + * + * Only start-up code below + * + */ +/**************************************** + * + * Check if there is a DE-620 connected + */ +int +de620_probe(struct device *dev) +{ + static struct netstats de620_netstats; + int i; + byte checkbyte = 0xa5; + + /* + * This is where the base_addr and irq gets set. + * Tunable at compile-time and insmod-time + */ + dev->base_addr = io; + dev->irq = irq; + + if (de620_debug) + printk(version); + + printk("D-Link DE-620 pocket adapter"); + + /* Initially, configure basic nibble mode, so we can read the EEPROM */ + NIC_Cmd = DEF_NIC_CMD; + de620_set_register(dev, W_EIP, EIPRegister); + + /* Anybody out there? */ + de620_set_register(dev, W_CPR, checkbyte); + checkbyte = de620_get_register(dev, R_CPR); + + if ((checkbyte != 0xa5) || (read_eeprom(dev) != 0)) { + printk(" not identified in the printer port\n"); + return ENODEV; + } + +#if 0 /* Not yet */ + if (check_region(dev->base_addr, 3)) { + printk(", port 0x%x busy\n", dev->base_addr); + return EBUSY; + } +#endif + request_region(dev->base_addr, 3, "de620"); + + /* else, got it! */ + printk(", Ethernet Address: %2.2X", + dev->dev_addr[0] = nic_data.NodeID[0]); + for (i = 1; i < ETH_ALEN; i++) { + printk(":%2.2X", dev->dev_addr[i] = nic_data.NodeID[i]); + dev->broadcast[i] = 0xff; + } + + printk(" (%dk RAM,", + (nic_data.RAM_Size) ? (nic_data.RAM_Size >> 2) : 64); + + if (nic_data.Media == 1) + printk(" BNC)\n"); + else + printk(" UTP)\n"); + + /* Initialize the device structure. */ + /*dev->priv = kmalloc(sizeof(struct netstats), GFP_KERNEL);*/ + dev->priv = &de620_netstats; + + memset(dev->priv, 0, sizeof(struct netstats)); + dev->get_stats = get_stats; + dev->open = de620_open; + dev->stop = de620_close; + dev->hard_start_xmit = &de620_start_xmit; + dev->set_multicast_list = &de620_set_multicast_list; + /* base_addr and irq are already set, see above! */ + + ether_setup(dev); + + /* dump eeprom */ + if (de620_debug) { + printk("\nEEPROM contents:\n"); + printk("RAM_Size = 0x%02X\n", nic_data.RAM_Size); + printk("NodeID = %02X:%02X:%02X:%02X:%02X:%02X\n", + nic_data.NodeID[0], nic_data.NodeID[1], + nic_data.NodeID[2], nic_data.NodeID[3], + nic_data.NodeID[4], nic_data.NodeID[5]); + printk("Model = %d\n", nic_data.Model); + printk("Media = %d\n", nic_data.Media); + printk("SCR = 0x%02x\n", nic_data.SCR); + } + + return 0; +} + +/********************************** + * + * Read info from on-board EEPROM + * + * Note: Bitwise serial I/O to/from the EEPROM vi the status _register_! + */ +#define sendit(dev,data) de620_set_register(dev, W_EIP, data | EIPRegister); + +static unsigned short +ReadAWord(struct device *dev, int from) +{ + unsigned short data; + int nbits; + + /* cs [__~~] SET SEND STATE */ + /* di [____] */ + /* sck [_~~_] */ + sendit(dev, 0); sendit(dev, 1); sendit(dev, 5); sendit(dev, 4); + + /* Send the 9-bit address from where we want to read the 16-bit word */ + for (nbits = 9; nbits > 0; --nbits, from <<= 1) { + if (from & 0x0100) { /* bit set? */ + /* cs [~~~~] SEND 1 */ + /* di [~~~~] */ + /* sck [_~~_] */ + sendit(dev, 6); sendit(dev, 7); sendit(dev, 7); sendit(dev, 6); + } + else { + /* cs [~~~~] SEND 0 */ + /* di [____] */ + /* sck [_~~_] */ + sendit(dev, 4); sendit(dev, 5); sendit(dev, 5); sendit(dev, 4); + } + } + + /* Shift in the 16-bit word. The bits appear serially in EEDI (=0x80) */ + for (data = 0, nbits = 16; nbits > 0; --nbits) { + /* cs [~~~~] SEND 0 */ + /* di [____] */ + /* sck [_~~_] */ + sendit(dev, 4); sendit(dev, 5); sendit(dev, 5); sendit(dev, 4); + data = (data << 1) | ((de620_get_register(dev, R_STS) & EEDI) >> 7); + } + /* cs [____] RESET SEND STATE */ + /* di [____] */ + /* sck [_~~_] */ + sendit(dev, 0); sendit(dev, 1); sendit(dev, 1); sendit(dev, 0); + + return data; +} + +static int +read_eeprom(struct device *dev) +{ + unsigned short wrd; + + /* D-Link Ethernet addresses are in the series 00:80:c8:7X:XX:XX:XX */ + wrd = ReadAWord(dev, 0x1aa); /* bytes 0 + 1 of NodeID */ + if (!clone && (wrd != htons(0x0080))) /* Valid D-Link ether sequence? */ + return -1; /* Nope, not a DE-620 */ + nic_data.NodeID[0] = wrd & 0xff; + nic_data.NodeID[1] = wrd >> 8; + + wrd = ReadAWord(dev, 0x1ab); /* bytes 2 + 3 of NodeID */ + if (!clone && ((wrd & 0xff) != 0xc8)) /* Valid D-Link ether sequence? */ + return -1; /* Nope, not a DE-620 */ + nic_data.NodeID[2] = wrd & 0xff; + nic_data.NodeID[3] = wrd >> 8; + + wrd = ReadAWord(dev, 0x1ac); /* bytes 4 + 5 of NodeID */ + nic_data.NodeID[4] = wrd & 0xff; + nic_data.NodeID[5] = wrd >> 8; + + wrd = ReadAWord(dev, 0x1ad); /* RAM size in pages (256 bytes). 0 = 64k */ + nic_data.RAM_Size = (wrd >> 8); + + wrd = ReadAWord(dev, 0x1ae); /* hardware model (CT = 3) */ + nic_data.Model = (wrd & 0xff); + + wrd = ReadAWord(dev, 0x1af); /* media (indicates BNC/UTP) */ + nic_data.Media = (wrd & 0xff); + + wrd = ReadAWord(dev, 0x1a8); /* System Configuration Register */ + nic_data.SCR = (wrd >> 8); + + return 0; /* no errors */ +} + +/****************************************************************************** + * + * Loadable module skeleton + * + */ +#ifdef MODULE +static char nullname[8] = ""; +static struct device de620_dev = { + nullname, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, de620_probe }; + +int +init_module(void) +{ + if (register_netdev(&de620_dev) != 0) + return -EIO; + return 0; +} + +void +cleanup_module(void) +{ + unregister_netdev(&de620_dev); + release_region(de620_dev.base_addr, 3); +} +#endif /* MODULE */ + +/* + * (add '-DMODULE' when compiling as loadable module) + * + * compile-command: + * gcc -D__KERNEL__ -Wall -Wstrict-prototypes -O2 \ + * -fomit-frame-pointer -m486 \ + * -I/usr/src/linux/include -I../../net/inet -c de620.c +*/ +/* + * Local variables: + * kernel-compile-command: "gcc -D__KERNEL__ -Ilinux/include -I../../net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de620.c" + * module-compile-command: "gcc -D__KERNEL__ -DMODULE -Ilinux/include -I../../net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de620.c" + * compile-command: "gcc -D__KERNEL__ -DMODULE -Ilinux/include -I../../net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de620.c" + * End: + */ diff --git a/linux/src/drivers/net/de620.h b/linux/src/drivers/net/de620.h new file mode 100644 index 00000000..e8d9a88f --- /dev/null +++ b/linux/src/drivers/net/de620.h @@ -0,0 +1,117 @@ +/********************************************************* + * * + * Definition of D-Link DE-620 Ethernet Pocket adapter * + * * + *********************************************************/ + +/* DE-620's CMD port Command */ +#define CS0 0x08 /* 1->0 command strobe */ +#define ICEN 0x04 /* 0=enable DL3520 host interface */ +#define DS0 0x02 /* 1->0 data strobe 0 */ +#define DS1 0x01 /* 1->0 data strobe 1 */ + +#define WDIR 0x20 /* general 0=read 1=write */ +#define RDIR 0x00 /* (not 100% confirm ) */ +#define PS2WDIR 0x00 /* ps/2 mode 1=read, 0=write */ +#define PS2RDIR 0x20 + +#define IRQEN 0x10 /* 1 = enable printer IRQ line */ +#define SELECTIN 0x08 /* 1 = select printer */ +#define INITP 0x04 /* 0 = initial printer */ +#define AUTOFEED 0x02 /* 1 = printer auto form feed */ +#define STROBE 0x01 /* 0->1 data strobe */ + +#define RESET 0x08 +#define NIS0 0x20 /* 0 = BNC, 1 = UTP */ +#define NCTL0 0x10 + +/* DE-620 DIC Command */ +#define W_DUMMY 0x00 /* DIC reserved command */ +#define W_CR 0x20 /* DIC write command register */ +#define W_NPR 0x40 /* DIC write Next Page Register */ +#define W_TBR 0x60 /* DIC write Tx Byte Count 1 reg */ +#define W_RSA 0x80 /* DIC write Remote Start Addr 1 */ + +/* DE-620's STAT port bits 7-4 */ +#define EMPTY 0x80 /* 1 = receive buffer empty */ +#define INTLEVEL 0x40 /* 1 = interrupt level is high */ +#define TXBF1 0x20 /* 1 = transmit buffer 1 is in use */ +#define TXBF0 0x10 /* 1 = transmit buffer 0 is in use */ +#define READY 0x08 /* 1 = h/w ready to accept cmd/data */ + +/* IDC 1 Command */ +#define W_RSA1 0xa0 /* write remote start address 1 */ +#define W_RSA0 0xa1 /* write remote start address 0 */ +#define W_NPRF 0xa2 /* write next page register NPR15-NPR8 */ +#define W_DFR 0xa3 /* write delay factor register */ +#define W_CPR 0xa4 /* write current page register */ +#define W_SPR 0xa5 /* write start page register */ +#define W_EPR 0xa6 /* write end page register */ +#define W_SCR 0xa7 /* write system configuration register */ +#define W_TCR 0xa8 /* write Transceiver Configuration reg */ +#define W_EIP 0xa9 /* write EEPM Interface port */ +#define W_PAR0 0xaa /* write physical address register 0 */ +#define W_PAR1 0xab /* write physical address register 1 */ +#define W_PAR2 0xac /* write physical address register 2 */ +#define W_PAR3 0xad /* write physical address register 3 */ +#define W_PAR4 0xae /* write physical address register 4 */ +#define W_PAR5 0xaf /* write physical address register 5 */ + +/* IDC 2 Command */ +#define R_STS 0xc0 /* read status register */ +#define R_CPR 0xc1 /* read current page register */ +#define R_BPR 0xc2 /* read boundary page register */ +#define R_TDR 0xc3 /* read time domain reflectometry reg */ + +/* STATUS Register */ +#define EEDI 0x80 /* EEPM DO pin */ +#define TXSUC 0x40 /* tx success */ +#define T16 0x20 /* tx fail 16 times */ +#define TS1 0x40 /* 0=Tx success, 1=T16 */ +#define TS0 0x20 /* 0=Tx success, 1=T16 */ +#define RXGOOD 0x10 /* rx a good packet */ +#define RXCRC 0x08 /* rx a CRC error packet */ +#define RXSHORT 0x04 /* rx a short packet */ +#define COLS 0x02 /* coaxial collision status */ +#define LNKS 0x01 /* UTP link status */ + +/* Command Register */ +#define CLEAR 0x10 /* reset part of hardware */ +#define NOPER 0x08 /* No Operation */ +#define RNOP 0x08 +#define RRA 0x06 /* After RR then auto-advance NPR & BPR(=NPR-1) */ +#define RRN 0x04 /* Normal Remote Read mode */ +#define RW1 0x02 /* Remote Write tx buffer 1 ( page 6 - 11 ) */ +#define RW0 0x00 /* Remote Write tx buffer 0 ( page 0 - 5 ) */ +#define TXEN 0x01 /* 0->1 tx enable */ + +/* System Configuration Register */ +#define TESTON 0x80 /* test host data transfer reliability */ +#define SLEEP 0x40 /* sleep mode */ +#if 0 +#define FASTMODE 0x04 /* fast mode for intel 82360SL fast mode */ +#define BYTEMODE 0x02 /* byte mode */ +#else +#define FASTMODE 0x20 /* fast mode for intel 82360SL fast mode */ +#define BYTEMODE 0x10 /* byte mode */ +#endif +#define NIBBLEMODE 0x00 /* nibble mode */ +#define IRQINV 0x08 /* turn off IRQ line inverter */ +#define IRQNML 0x00 /* turn on IRQ line inverter */ +#define INTON 0x04 +#define AUTOFFSET 0x02 /* auto shift address to TPR+12 */ +#define AUTOTX 0x01 /* auto tx when leave RW mode */ + +/* Transceiver Configuration Register */ +#define JABBER 0x80 /* generate jabber condition */ +#define TXSUCINT 0x40 /* enable tx success interrupt */ +#define T16INT 0x20 /* enable T16 interrupt */ +#define RXERRPKT 0x10 /* accept CRC error or short packet */ +#define EXTERNALB2 0x0C /* external loopback 2 */ +#define EXTERNALB1 0x08 /* external loopback 1 */ +#define INTERNALB 0x04 /* internal loopback */ +#define NMLOPERATE 0x00 /* normal operation */ +#define RXPBM 0x03 /* rx physical, broadcast, multicast */ +#define RXPB 0x02 /* rx physical, broadcast */ +#define RXALL 0x01 /* rx all packet */ +#define RXOFF 0x00 /* rx disable */ diff --git a/linux/src/drivers/net/depca.c b/linux/src/drivers/net/depca.c new file mode 100644 index 00000000..8cf6fc80 --- /dev/null +++ b/linux/src/drivers/net/depca.c @@ -0,0 +1,1890 @@ +/* depca.c: A DIGITAL DEPCA & EtherWORKS ethernet driver for linux. + + Written 1994, 1995 by David C. Davies. + + + Copyright 1994 David C. Davies + and + United States Government + (as represented by the Director, National Security Agency). + + Copyright 1995 Digital Equipment Corporation. + + + This software may be used and distributed according to the terms of + the GNU Public License, incorporated herein by reference. + + This driver is written for the Digital Equipment Corporation series + of DEPCA and EtherWORKS ethernet cards: + + DEPCA (the original) + DE100 + DE101 + DE200 Turbo + DE201 Turbo + DE202 Turbo (TP BNC) + DE210 + DE422 (EISA) + + The driver has been tested on DE100, DE200 and DE202 cards in a + relatively busy network. The DE422 has been tested a little. + + This driver will NOT work for the DE203, DE204 and DE205 series of + cards, since they have a new custom ASIC in place of the AMD LANCE + chip. See the 'ewrk3.c' driver in the Linux source tree for running + those cards. + + I have benchmarked the driver with a DE100 at 595kB/s to (542kB/s from) + a DECstation 5000/200. + + The author may be reached at davies@maniac.ultranet.com + + ========================================================================= + + The driver was originally based on the 'lance.c' driver from Donald + Becker which is included with the standard driver distribution for + linux. V0.4 is a complete re-write with only the kernel interface + remaining from the original code. + + 1) Lance.c code in /linux/drivers/net/ + 2) "Ethernet/IEEE 802.3 Family. 1992 World Network Data Book/Handbook", + AMD, 1992 [(800) 222-9323]. + 3) "Am79C90 CMOS Local Area Network Controller for Ethernet (C-LANCE)", + AMD, Pub. #17881, May 1993. + 4) "Am79C960 PCnet-ISA(tm), Single-Chip Ethernet Controller for ISA", + AMD, Pub. #16907, May 1992 + 5) "DEC EtherWORKS LC Ethernet Controller Owners Manual", + Digital Equipment corporation, 1990, Pub. #EK-DE100-OM.003 + 6) "DEC EtherWORKS Turbo Ethernet Controller Owners Manual", + Digital Equipment corporation, 1990, Pub. #EK-DE200-OM.003 + 7) "DEPCA Hardware Reference Manual", Pub. #EK-DEPCA-PR + Digital Equipment Corporation, 1989 + 8) "DEC EtherWORKS Turbo_(TP BNC) Ethernet Controller Owners Manual", + Digital Equipment corporation, 1991, Pub. #EK-DE202-OM.001 + + + Peter Bauer's depca.c (V0.5) was referred to when debugging V0.1 of this + driver. + + The original DEPCA card requires that the ethernet ROM address counter + be enabled to count and has an 8 bit NICSR. The ROM counter enabling is + only done when a 0x08 is read as the first address octet (to minimise + the chances of writing over some other hardware's I/O register). The + NICSR accesses have been changed to byte accesses for all the cards + supported by this driver, since there is only one useful bit in the MSB + (remote boot timeout) and it is not used. Also, there is a maximum of + only 48kB network RAM for this card. My thanks to Torbjorn Lindh for + help debugging all this (and holding my feet to the fire until I got it + right). + + The DE200 series boards have on-board 64kB RAM for use as a shared + memory network buffer. Only the DE100 cards make use of a 2kB buffer + mode which has not been implemented in this driver (only the 32kB and + 64kB modes are supported [16kB/48kB for the original DEPCA]). + + At the most only 2 DEPCA cards can be supported on the ISA bus because + there is only provision for two I/O base addresses on each card (0x300 + and 0x200). The I/O address is detected by searching for a byte sequence + in the Ethernet station address PROM at the expected I/O address for the + Ethernet PROM. The shared memory base address is 'autoprobed' by + looking for the self test PROM and detecting the card name. When a + second DEPCA is detected, information is placed in the base_addr + variable of the next device structure (which is created if necessary), + thus enabling ethif_probe initialization for the device. More than 2 + EISA cards can be supported, but care will be needed assigning the + shared memory to ensure that each slot has the correct IRQ, I/O address + and shared memory address assigned. + + ************************************************************************ + + NOTE: If you are using two ISA DEPCAs, it is important that you assign + the base memory addresses correctly. The driver autoprobes I/O 0x300 + then 0x200. The base memory address for the first device must be less + than that of the second so that the auto probe will correctly assign the + I/O and memory addresses on the same card. I can't think of a way to do + this unambiguously at the moment, since there is nothing on the cards to + tie I/O and memory information together. + + I am unable to test 2 cards together for now, so this code is + unchecked. All reports, good or bad, are welcome. + + ************************************************************************ + + The board IRQ setting must be at an unused IRQ which is auto-probed + using Donald Becker's autoprobe routines. DEPCA and DE100 board IRQs are + {2,3,4,5,7}, whereas the DE200 is at {5,9,10,11,15}. Note that IRQ2 is + really IRQ9 in machines with 16 IRQ lines. + + No 16MB memory limitation should exist with this driver as DMA is not + used and the common memory area is in low memory on the network card (my + current system has 20MB and I've not had problems yet). + + The ability to load this driver as a loadable module has been added. To + utilise this ability, you have to do <8 things: + + 0) have a copy of the loadable modules code installed on your system. + 1) copy depca.c from the /linux/drivers/net directory to your favourite + temporary directory. + 2) if you wish, edit the source code near line 1530 to reflect the I/O + address and IRQ you're using (see also 5). + 3) compile depca.c, but include -DMODULE in the command line to ensure + that the correct bits are compiled (see end of source code). + 4) if you are wanting to add a new card, goto 5. Otherwise, recompile a + kernel with the depca configuration turned off and reboot. + 5) insmod depca.o [irq=7] [io=0x200] [mem=0xd0000] [adapter_name=DE100] + [Alan Cox: Changed the code to allow command line irq/io assignments] + [Dave Davies: Changed the code to allow command line mem/name + assignments] + 6) run the net startup bits for your eth?? interface manually + (usually /etc/rc.inet[12] at boot time). + 7) enjoy! + + Note that autoprobing is not allowed in loadable modules - the system is + already up and running and you're messing with interrupts. + + To unload a module, turn off the associated interface + 'ifconfig eth?? down' then 'rmmod depca'. + + To assign a base memory address for the shared memory when running as a + loadable module, see 5 above. To include the adapter name (if you have + no PROM but know the card name) also see 5 above. Note that this last + option will not work with kernel built-in depca's. + + The shared memory assignment for a loadable module makes sense to avoid + the 'memory autoprobe' picking the wrong shared memory (for the case of + 2 depca's in a PC). + + + TO DO: + ------ + + + Revision History + ---------------- + + Version Date Description + + 0.1 25-jan-94 Initial writing. + 0.2 27-jan-94 Added LANCE TX hardware buffer chaining. + 0.3 1-feb-94 Added multiple DEPCA support. + 0.31 4-feb-94 Added DE202 recognition. + 0.32 19-feb-94 Tidy up. Improve multi-DEPCA support. + 0.33 25-feb-94 Fix DEPCA ethernet ROM counter enable. + Add jabber packet fix from murf@perftech.com + and becker@super.org + 0.34 7-mar-94 Fix DEPCA max network memory RAM & NICSR access. + 0.35 8-mar-94 Added DE201 recognition. Tidied up. + 0.351 30-apr-94 Added EISA support. Added DE422 recognition. + 0.36 16-may-94 DE422 fix released. + 0.37 22-jul-94 Added MODULE support + 0.38 15-aug-94 Added DBR ROM switch in depca_close(). + Multi DEPCA bug fix. + 0.38axp 15-sep-94 Special version for Alpha AXP Linux V1.0. + 0.381 12-dec-94 Added DE101 recognition, fix multicast bug. + 0.382 9-feb-95 Fix recognition bug reported by <bkm@star.rl.ac.uk>. + 0.383 22-feb-95 Fix for conflict with VESA SCSI reported by + <stromain@alf.dec.com> + 0.384 17-mar-95 Fix a ring full bug reported by <bkm@star.rl.ac.uk> + 0.385 3-apr-95 Fix a recognition bug reported by + <ryan.niemi@lastfrontier.com> + 0.386 21-apr-95 Fix the last fix...sorry, must be galloping senility + 0.40 25-May-95 Rewrite for portability & updated. + ALPHA support from <jestabro@amt.tay1.dec.com> + 0.41 26-Jun-95 Added verify_area() calls in depca_ioctl() from + suggestion by <heiko@colossus.escape.de> + 0.42 27-Dec-95 Add 'mem' shared memory assignment for loadable + modules. + Add 'adapter_name' for loadable modules when no PROM. + Both above from a suggestion by + <pchen@woodruffs121.residence.gatech.edu>. + Add new multicasting code. + 0.421 22-Apr-96 Fix alloc_device() bug <jari@markkus2.fimr.fi> + 0.422 29-Apr-96 Fix depca_hw_init() bug <jari@markkus2.fimr.fi> + 0.423 7-Jun-96 Fix module load bug <kmg@barco.be> + 0.43 16-Aug-96 Update alloc_device() to conform to de4x5.c + + ========================================================================= +*/ + +static const char *version = "depca.c:v0.43 96/8/16 davies@maniac.ultranet.com\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/segment.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unistd.h> +#include <linux/ctype.h> + +#include "depca.h" + +#ifdef DEPCA_DEBUG +static int depca_debug = DEPCA_DEBUG; +#else +static int depca_debug = 1; +#endif + +#define DEPCA_NDA 0xffe0 /* No Device Address */ + +/* +** Ethernet PROM defines +*/ +#define PROBE_LENGTH 32 +#define ETH_PROM_SIG 0xAA5500FFUL + +/* +** Set the number of Tx and Rx buffers. Ensure that the memory requested +** here is <= to the amount of shared memory set up by the board switches. +** The number of descriptors MUST BE A POWER OF 2. +** +** total_memory = NUM_RX_DESC*(8+RX_BUFF_SZ) + NUM_TX_DESC*(8+TX_BUFF_SZ) +*/ +#define NUM_RX_DESC 8 /* Number of RX descriptors */ +#define NUM_TX_DESC 8 /* Number of TX descriptors */ +#define RX_BUFF_SZ 1536 /* Buffer size for each Rx buffer */ +#define TX_BUFF_SZ 1536 /* Buffer size for each Tx buffer */ + +#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */ +#define CRC_POLYNOMIAL_LE 0xedb88320UL /* Ethernet CRC, little endian */ + +/* +** EISA bus defines +*/ +#define DEPCA_EISA_IO_PORTS 0x0c00 /* I/O port base address, slot 0 */ +#define MAX_EISA_SLOTS 16 +#define EISA_SLOT_INC 0x1000 + +/* +** ISA Bus defines +*/ +#define DEPCA_RAM_BASE_ADDRESSES {0xc0000,0xd0000,0xe0000,0x00000} +#define DEPCA_IO_PORTS {0x300, 0x200, 0} +#define DEPCA_TOTAL_SIZE 0x10 +static short mem_chkd = 0; + +/* +** Name <-> Adapter mapping +*/ +#define DEPCA_SIGNATURE {"DEPCA",\ + "DE100","DE101",\ + "DE200","DE201","DE202",\ + "DE210",\ + "DE422",\ + ""} +static enum {DEPCA, de100, de101, de200, de201, de202, de210, de422, unknown} adapter; + +/* +** Miscellaneous info... +*/ +#define DEPCA_STRLEN 16 +#define MAX_NUM_DEPCAS 2 + +/* +** Memory Alignment. Each descriptor is 4 longwords long. To force a +** particular alignment on the TX descriptor, adjust DESC_SKIP_LEN and +** DESC_ALIGN. ALIGN aligns the start address of the private memory area +** and hence the RX descriptor ring's first entry. +*/ +#define ALIGN4 ((u_long)4 - 1) /* 1 longword align */ +#define ALIGN8 ((u_long)8 - 1) /* 2 longword (quadword) align */ +#define ALIGN ALIGN8 /* Keep the LANCE happy... */ + +/* +** The DEPCA Rx and Tx ring descriptors. +*/ +struct depca_rx_desc { + volatile s32 base; + s16 buf_length; /* This length is negative 2's complement! */ + s16 msg_length; /* This length is "normal". */ +}; + +struct depca_tx_desc { + volatile s32 base; + s16 length; /* This length is negative 2's complement! */ + s16 misc; /* Errors and TDR info */ +}; + +#define LA_MASK 0x0000ffff /* LANCE address mask for mapping network RAM + to LANCE memory address space */ + +/* +** The Lance initialization block, described in databook, in common memory. +*/ +struct depca_init { + u16 mode; /* Mode register */ + u8 phys_addr[ETH_ALEN]; /* Physical ethernet address */ + u8 mcast_table[8]; /* Multicast Hash Table. */ + u32 rx_ring; /* Rx ring base pointer & ring length */ + u32 tx_ring; /* Tx ring base pointer & ring length */ +}; + +#define DEPCA_PKT_STAT_SZ 16 +#define DEPCA_PKT_BIN_SZ 128 /* Should be >=100 unless you + increase DEPCA_PKT_STAT_SZ */ +struct depca_private { + char devname[DEPCA_STRLEN]; /* Device Product String */ + char adapter_name[DEPCA_STRLEN];/* /proc/ioports string */ + char adapter; /* Adapter type */ + struct depca_rx_desc *rx_ring; /* Pointer to start of RX descriptor ring */ + struct depca_tx_desc *tx_ring; /* Pointer to start of TX descriptor ring */ + struct depca_init init_block;/* Shadow Initialization block */ + char *rx_memcpy[NUM_RX_DESC]; /* CPU virt address of sh'd memory buffs */ + char *tx_memcpy[NUM_TX_DESC]; /* CPU virt address of sh'd memory buffs */ + u_long bus_offset; /* (E)ISA bus address offset vs LANCE */ + u_long sh_mem; /* Physical start addr of shared mem area */ + u_long dma_buffs; /* LANCE Rx and Tx buffers start address. */ + int rx_new, tx_new; /* The next free ring entry */ + int rx_old, tx_old; /* The ring entries to be free()ed. */ + struct enet_statistics stats; + struct { /* Private stats counters */ + u32 bins[DEPCA_PKT_STAT_SZ]; + u32 unicast; + u32 multicast; + u32 broadcast; + u32 excessive_collisions; + u32 tx_underruns; + u32 excessive_underruns; + } pktStats; + int txRingMask; /* TX ring mask */ + int rxRingMask; /* RX ring mask */ + s32 rx_rlen; /* log2(rxRingMask+1) for the descriptors */ + s32 tx_rlen; /* log2(txRingMask+1) for the descriptors */ +}; + +/* +** The transmit ring full condition is described by the tx_old and tx_new +** pointers by: +** tx_old = tx_new Empty ring +** tx_old = tx_new+1 Full ring +** tx_old+txRingMask = tx_new Full ring (wrapped condition) +*/ +#define TX_BUFFS_AVAIL ((lp->tx_old<=lp->tx_new)?\ + lp->tx_old+lp->txRingMask-lp->tx_new:\ + lp->tx_old -lp->tx_new-1) + +/* +** Public Functions +*/ +static int depca_open(struct device *dev); +static int depca_start_xmit(struct sk_buff *skb, struct device *dev); +static void depca_interrupt(int irq, void *dev_id, struct pt_regs * regs); +static int depca_close(struct device *dev); +static int depca_ioctl(struct device *dev, struct ifreq *rq, int cmd); +static struct enet_statistics *depca_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev); + +/* +** Private functions +*/ +static int depca_hw_init(struct device *dev, u_long ioaddr); +static void depca_init_ring(struct device *dev); +static int depca_rx(struct device *dev); +static int depca_tx(struct device *dev); + +static void LoadCSRs(struct device *dev); +static int InitRestartDepca(struct device *dev); +static void DepcaSignature(char *name, u_long paddr); +static int DevicePresent(u_long ioaddr); +static int get_hw_addr(struct device *dev); +static int EISA_signature(char *name, s32 eisa_id); +static void SetMulticastFilter(struct device *dev); +static void isa_probe(struct device *dev, u_long iobase); +static void eisa_probe(struct device *dev, u_long iobase); +static struct device *alloc_device(struct device *dev, u_long iobase); +static int depca_dev_index(char *s); +static struct device *insert_device(struct device *dev, u_long iobase, int (*init)(struct device *)); +static int load_packet(struct device *dev, struct sk_buff *skb); +static void depca_dbg_open(struct device *dev); + +#ifdef MODULE +int init_module(void); +void cleanup_module(void); +static int autoprobed = 1, loading_module = 1; +# else +static u_char de1xx_irq[] = {2,3,4,5,7,9,0}; +static u_char de2xx_irq[] = {5,9,10,11,15,0}; +static u_char de422_irq[] = {5,9,10,11,0}; +static u_char *depca_irq; +static int autoprobed = 0, loading_module = 0; +#endif /* MODULE */ + +static char name[DEPCA_STRLEN]; +static int num_depcas = 0, num_eth = 0; +static int mem=0; /* For loadable module assignment + use insmod mem=0x????? .... */ +static char *adapter_name = '\0'; /* If no PROM when loadable module + use insmod adapter_name=DE??? ... + */ +/* +** Miscellaneous defines... +*/ +#define STOP_DEPCA \ + outw(CSR0, DEPCA_ADDR);\ + outw(STOP, DEPCA_DATA) + + + +int depca_probe(struct device *dev) +{ + int tmp = num_depcas, status = -ENODEV; + u_long iobase = dev->base_addr; + + if ((iobase == 0) && loading_module){ + printk("Autoprobing is not supported when loading a module based driver.\n"); + status = -EIO; + } else { + isa_probe(dev, iobase); + eisa_probe(dev, iobase); + + if ((tmp == num_depcas) && (iobase != 0) && loading_module) { + printk("%s: depca_probe() cannot find device at 0x%04lx.\n", dev->name, + iobase); + } + + /* + ** Walk the device list to check that at least one device + ** initialised OK + */ + for (; (dev->priv == NULL) && (dev->next != NULL); dev = dev->next); + + if (dev->priv) status = 0; + if (iobase == 0) autoprobed = 1; + } + + return status; +} + +static int +depca_hw_init(struct device *dev, u_long ioaddr) +{ + struct depca_private *lp; + int i, j, offset, netRAM, mem_len, status=0; + s16 nicsr; + u_long mem_start=0, mem_base[] = DEPCA_RAM_BASE_ADDRESSES; + + STOP_DEPCA; + + nicsr = inb(DEPCA_NICSR); + nicsr = ((nicsr & ~SHE & ~RBE & ~IEN) | IM); + outb(nicsr, DEPCA_NICSR); + + if (inw(DEPCA_DATA) == STOP) { + do { + strcpy(name, (adapter_name ? adapter_name : "")); + mem_start = (mem ? mem & 0xf0000 : mem_base[mem_chkd++]); + DepcaSignature(name, mem_start); + } while (!mem && mem_base[mem_chkd] && (adapter == unknown)); + + if ((adapter != unknown) && mem_start) { /* found a DEPCA device */ + dev->base_addr = ioaddr; + + if ((ioaddr&0x0fff)==DEPCA_EISA_IO_PORTS) {/* EISA slot address */ + printk("%s: %s at 0x%04lx (EISA slot %d)", + dev->name, name, ioaddr, (int)((ioaddr>>12)&0x0f)); + } else { /* ISA port address */ + printk("%s: %s at 0x%04lx", dev->name, name, ioaddr); + } + + printk(", h/w address "); + status = get_hw_addr(dev); + for (i=0; i<ETH_ALEN - 1; i++) { /* get the ethernet address */ + printk("%2.2x:", dev->dev_addr[i]); + } + printk("%2.2x", dev->dev_addr[i]); + + if (status == 0) { + /* Set up the maximum amount of network RAM(kB) */ + netRAM = ((adapter != DEPCA) ? 64 : 48); + if ((nicsr & _128KB) && (adapter == de422)) netRAM = 128; + offset = 0x0000; + + /* Shared Memory Base Address */ + if (nicsr & BUF) { + offset = 0x8000; /* 32kbyte RAM offset*/ + nicsr &= ~BS; /* DEPCA RAM in top 32k */ + netRAM -= 32; + } + mem_start += offset; /* (E)ISA start address */ + if ((mem_len = (NUM_RX_DESC*(sizeof(struct depca_rx_desc)+RX_BUFF_SZ) + + NUM_TX_DESC*(sizeof(struct depca_tx_desc)+TX_BUFF_SZ) + + sizeof(struct depca_init))) <= + (netRAM<<10)) { + printk(",\n has %dkB RAM at 0x%.5lx", netRAM, mem_start); + + /* Enable the shadow RAM. */ + if (adapter != DEPCA) { + nicsr |= SHE; + outb(nicsr, DEPCA_NICSR); + } + + /* Define the device private memory */ + dev->priv = (void *) kmalloc(sizeof(struct depca_private), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + lp = (struct depca_private *)dev->priv; + memset((char *)dev->priv, 0, sizeof(struct depca_private)); + lp->adapter = adapter; + sprintf(lp->adapter_name,"%s (%s)", name, dev->name); + request_region(ioaddr, DEPCA_TOTAL_SIZE, lp->adapter_name); + + /* Initialisation Block */ + lp->sh_mem = mem_start; + mem_start += sizeof(struct depca_init); + + /* Tx & Rx descriptors (aligned to a quadword boundary) */ + mem_start = (mem_start + ALIGN) & ~ALIGN; + lp->rx_ring = (struct depca_rx_desc *)mem_start; + + mem_start += (sizeof(struct depca_rx_desc) * NUM_RX_DESC); + lp->tx_ring = (struct depca_tx_desc *)mem_start; + + mem_start += (sizeof(struct depca_tx_desc) * NUM_TX_DESC); + lp->bus_offset = mem_start & 0x00ff0000; + mem_start &= LA_MASK; /* LANCE re-mapped start address */ + + lp->dma_buffs = mem_start; + + /* Finish initialising the ring information. */ + lp->rxRingMask = NUM_RX_DESC - 1; + lp->txRingMask = NUM_TX_DESC - 1; + + /* Calculate Tx/Rx RLEN size for the descriptors. */ + for (i=0, j = lp->rxRingMask; j>0; i++) { + j >>= 1; + } + lp->rx_rlen = (s32)(i << 29); + for (i=0, j = lp->txRingMask; j>0; i++) { + j >>= 1; + } + lp->tx_rlen = (s32)(i << 29); + + /* Load the initialisation block */ + depca_init_ring(dev); + + /* Initialise the control and status registers */ + LoadCSRs(dev); + + /* Enable DEPCA board interrupts for autoprobing */ + nicsr = ((nicsr & ~IM)|IEN); + outb(nicsr, DEPCA_NICSR); + + /* To auto-IRQ we enable the initialization-done and DMA err, + interrupts. For now we will always get a DMA error. */ + if (dev->irq < 2) { +#ifndef MODULE + unsigned char irqnum; + autoirq_setup(0); + + /* Assign the correct irq list */ + switch (lp->adapter) { + case DEPCA: + case de100: + case de101: + depca_irq = de1xx_irq; + break; + case de200: + case de201: + case de202: + case de210: + depca_irq = de2xx_irq; + break; + case de422: + depca_irq = de422_irq; + break; + } + + /* Trigger an initialization just for the interrupt. */ + outw(INEA | INIT, DEPCA_DATA); + + irqnum = autoirq_report(1); + if (!irqnum) { + printk(" and failed to detect IRQ line.\n"); + status = -ENXIO; + } else { + for (dev->irq=0,i=0; (depca_irq[i]) && (!dev->irq); i++) { + if (irqnum == depca_irq[i]) { + dev->irq = irqnum; + printk(" and uses IRQ%d.\n", dev->irq); + } + } + + if (!dev->irq) { + printk(" but incorrect IRQ line detected.\n"); + status = -ENXIO; + } + } +#endif /* MODULE */ + } else { + printk(" and assigned IRQ%d.\n", dev->irq); + } + if (status) release_region(ioaddr, DEPCA_TOTAL_SIZE); + } else { + printk(",\n requests %dkB RAM: only %dkB is available!\n", + (mem_len>>10), netRAM); + status = -ENXIO; + } + } else { + printk(" which has an Ethernet PROM CRC error.\n"); + status = -ENXIO; + } + } else { + status = -ENXIO; + } + if (!status) { + if (depca_debug > 1) { + printk(version); + } + + /* The DEPCA-specific entries in the device structure. */ + dev->open = &depca_open; + dev->hard_start_xmit = &depca_start_xmit; + dev->stop = &depca_close; + dev->get_stats = &depca_get_stats; + dev->set_multicast_list = &set_multicast_list; + dev->do_ioctl = &depca_ioctl; + + dev->mem_start = 0; + + /* Fill in the generic field of the device structure. */ + ether_setup(dev); + } else { /* Incorrectly initialised hardware */ + if (dev->priv) { + kfree_s(dev->priv, sizeof(struct depca_private)); + dev->priv = NULL; + } + } + } else { + status = -ENXIO; + } + + return status; +} + + +static int +depca_open(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + u_long ioaddr = dev->base_addr; + s16 nicsr; + int status = 0; + + irq2dev_map[dev->irq] = dev; + STOP_DEPCA; + nicsr = inb(DEPCA_NICSR); + + /* Make sure the shadow RAM is enabled */ + if (adapter != DEPCA) { + nicsr |= SHE; + outb(nicsr, DEPCA_NICSR); + } + + /* Re-initialize the DEPCA... */ + depca_init_ring(dev); + LoadCSRs(dev); + + depca_dbg_open(dev); + + if (request_irq(dev->irq, &depca_interrupt, 0, lp->adapter_name, NULL)) { + printk("depca_open(): Requested IRQ%d is busy\n",dev->irq); + status = -EAGAIN; + } else { + + /* Enable DEPCA board interrupts and turn off LED */ + nicsr = ((nicsr & ~IM & ~LED)|IEN); + outb(nicsr, DEPCA_NICSR); + outw(CSR0,DEPCA_ADDR); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + status = InitRestartDepca(dev); + + if (depca_debug > 1){ + printk("CSR0: 0x%4.4x\n",inw(DEPCA_DATA)); + printk("nicsr: 0x%02x\n",inb(DEPCA_NICSR)); + } + } + + MOD_INC_USE_COUNT; + + return status; +} + +/* Initialize the lance Rx and Tx descriptor rings. */ +static void +depca_init_ring(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + u_int i; + u_long p; + + /* Lock out other processes whilst setting up the hardware */ + set_bit(0, (void *)&dev->tbusy); + + lp->rx_new = lp->tx_new = 0; + lp->rx_old = lp->tx_old = 0; + + /* Initialize the base addresses and length of each buffer in the ring */ + for (i = 0; i <= lp->rxRingMask; i++) { + writel((p=lp->dma_buffs+i*RX_BUFF_SZ) | R_OWN, &lp->rx_ring[i].base); + writew(-RX_BUFF_SZ, &lp->rx_ring[i].buf_length); + lp->rx_memcpy[i]=(char *)(p+lp->bus_offset); + } + for (i = 0; i <= lp->txRingMask; i++) { + writel((p=lp->dma_buffs+(i+lp->txRingMask+1)*TX_BUFF_SZ) & 0x00ffffff, + &lp->tx_ring[i].base); + lp->tx_memcpy[i]=(char *)(p+lp->bus_offset); + } + + /* Set up the initialization block */ + lp->init_block.rx_ring = ((u32)((u_long)lp->rx_ring)&LA_MASK) | lp->rx_rlen; + lp->init_block.tx_ring = ((u32)((u_long)lp->tx_ring)&LA_MASK) | lp->tx_rlen; + + SetMulticastFilter(dev); + + for (i = 0; i < ETH_ALEN; i++) { + lp->init_block.phys_addr[i] = dev->dev_addr[i]; + } + + lp->init_block.mode = 0x0000; /* Enable the Tx and Rx */ + + return; +} + +/* +** Writes a socket buffer to TX descriptor ring and starts transmission +*/ +static int +depca_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + u_long ioaddr = dev->base_addr; + int status = 0; + + /* Transmitter timeout, serious problems. */ + if (dev->tbusy) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 1*HZ) { + status = -1; + } else { + printk("%s: transmit timed out, status %04x, resetting.\n", + dev->name, inw(DEPCA_DATA)); + + STOP_DEPCA; + depca_init_ring(dev); + LoadCSRs(dev); + dev->interrupt = UNMASK_INTERRUPTS; + dev->start = 1; + dev->tbusy=0; + dev->trans_start = jiffies; + InitRestartDepca(dev); + dev_kfree_skb(skb, FREE_WRITE); + } + return status; + } else if (skb == NULL) { + dev_tint(dev); + } else if (skb->len > 0) { + /* Enforce 1 process per h/w access */ + if (set_bit(0, (void*)&dev->tbusy) != 0) { + printk("%s: Transmitter access conflict.\n", dev->name); + status = -1; + } else { + if (TX_BUFFS_AVAIL) { /* Fill in a Tx ring entry */ + status = load_packet(dev, skb); + + if (!status) { + /* Trigger an immediate send demand. */ + outw(CSR0, DEPCA_ADDR); + outw(INEA | TDMD, DEPCA_DATA); + + dev->trans_start = jiffies; + dev_kfree_skb(skb, FREE_WRITE); + } + if (TX_BUFFS_AVAIL) { + dev->tbusy=0; + } + } else { + status = -1; + } + } + } + + return status; +} + +/* +** The DEPCA interrupt handler. +*/ +static void +depca_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct depca_private *lp; + s16 csr0, nicsr; + u_long ioaddr; + + if (dev == NULL) { + printk ("depca_interrupt(): irq %d for unknown device.\n", irq); + } else { + lp = (struct depca_private *)dev->priv; + ioaddr = dev->base_addr; + + if (dev->interrupt) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + + dev->interrupt = MASK_INTERRUPTS; + + /* mask the DEPCA board interrupts and turn on the LED */ + nicsr = inb(DEPCA_NICSR); + nicsr |= (IM|LED); + outb(nicsr, DEPCA_NICSR); + + outw(CSR0, DEPCA_ADDR); + csr0 = inw(DEPCA_DATA); + + /* Acknowledge all of the current interrupt sources ASAP. */ + outw(csr0 & INTE, DEPCA_DATA); + + if (csr0 & RINT) /* Rx interrupt (packet arrived) */ + depca_rx(dev); + + if (csr0 & TINT) /* Tx interrupt (packet sent) */ + depca_tx(dev); + + if ((TX_BUFFS_AVAIL >= 0) && dev->tbusy) { /* any resources available? */ + dev->tbusy = 0; /* clear TX busy flag */ + mark_bh(NET_BH); + } + + /* Unmask the DEPCA board interrupts and turn off the LED */ + nicsr = (nicsr & ~IM & ~LED); + outb(nicsr, DEPCA_NICSR); + + dev->interrupt = UNMASK_INTERRUPTS; + } + + return; +} + +static int +depca_rx(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + int i, entry; + s32 status; + + for (entry=lp->rx_new; + !(readl(&lp->rx_ring[entry].base) & R_OWN); + entry=lp->rx_new){ + status = readl(&lp->rx_ring[entry].base) >> 16 ; + if (status & R_STP) { /* Remember start of frame */ + lp->rx_old = entry; + } + if (status & R_ENP) { /* Valid frame status */ + if (status & R_ERR) { /* There was an error. */ + lp->stats.rx_errors++; /* Update the error stats. */ + if (status & R_FRAM) lp->stats.rx_frame_errors++; + if (status & R_OFLO) lp->stats.rx_over_errors++; + if (status & R_CRC) lp->stats.rx_crc_errors++; + if (status & R_BUFF) lp->stats.rx_fifo_errors++; + } else { + short len, pkt_len = readw(&lp->rx_ring[entry].msg_length); + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+2); + if (skb != NULL) { + unsigned char *buf; + skb_reserve(skb,2); /* 16 byte align the IP header */ + buf = skb_put(skb,pkt_len); + skb->dev = dev; + if (entry < lp->rx_old) { /* Wrapped buffer */ + len = (lp->rxRingMask - lp->rx_old + 1) * RX_BUFF_SZ; + memcpy_fromio(buf, lp->rx_memcpy[lp->rx_old], len); + memcpy_fromio(buf + len, lp->rx_memcpy[0], pkt_len-len); + } else { /* Linear buffer */ + memcpy_fromio(buf, lp->rx_memcpy[lp->rx_old], pkt_len); + } + + /* + ** Notify the upper protocol layers that there is another + ** packet to handle + */ + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + + /* + ** Update stats + */ + lp->stats.rx_packets++; + for (i=1; i<DEPCA_PKT_STAT_SZ-1; i++) { + if (pkt_len < (i*DEPCA_PKT_BIN_SZ)) { + lp->pktStats.bins[i]++; + i = DEPCA_PKT_STAT_SZ; + } + } + if (buf[0] & 0x01) { /* Multicast/Broadcast */ + if ((*(s16 *)&buf[0] == -1) && + (*(s16 *)&buf[2] == -1) && + (*(s16 *)&buf[4] == -1)) { + lp->pktStats.broadcast++; + } else { + lp->pktStats.multicast++; + } + } else if ((*(s16 *)&buf[0] == *(s16 *)&dev->dev_addr[0]) && + (*(s16 *)&buf[2] == *(s16 *)&dev->dev_addr[2]) && + (*(s16 *)&buf[4] == *(s16 *)&dev->dev_addr[4])) { + lp->pktStats.unicast++; + } + + lp->pktStats.bins[0]++; /* Duplicates stats.rx_packets */ + if (lp->pktStats.bins[0] == 0) { /* Reset counters */ + memset((char *)&lp->pktStats, 0, sizeof(lp->pktStats)); + } + } else { + printk("%s: Memory squeeze, deferring packet.\n", dev->name); + lp->stats.rx_dropped++; /* Really, deferred. */ + break; + } + } + /* Change buffer ownership for this last frame, back to the adapter */ + for (; lp->rx_old!=entry; lp->rx_old=(++lp->rx_old)&lp->rxRingMask) { + writel(readl(&lp->rx_ring[lp->rx_old].base) | R_OWN, + &lp->rx_ring[lp->rx_old].base); + } + writel(readl(&lp->rx_ring[entry].base) | R_OWN, &lp->rx_ring[entry].base); + } + + /* + ** Update entry information + */ + lp->rx_new = (++lp->rx_new) & lp->rxRingMask; + } + + return 0; +} + +/* +** Buffer sent - check for buffer errors. +*/ +static int +depca_tx(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + int entry; + s32 status; + u_long ioaddr = dev->base_addr; + + for (entry = lp->tx_old; entry != lp->tx_new; entry = lp->tx_old) { + status = readl(&lp->tx_ring[entry].base) >> 16 ; + + if (status < 0) { /* Packet not yet sent! */ + break; + } else if (status & T_ERR) { /* An error occurred. */ + status = readl(&lp->tx_ring[entry].misc); + lp->stats.tx_errors++; + if (status & TMD3_RTRY) lp->stats.tx_aborted_errors++; + if (status & TMD3_LCAR) lp->stats.tx_carrier_errors++; + if (status & TMD3_LCOL) lp->stats.tx_window_errors++; + if (status & TMD3_UFLO) lp->stats.tx_fifo_errors++; + if (status & (TMD3_BUFF | TMD3_UFLO)) { + /* Trigger an immediate send demand. */ + outw(CSR0, DEPCA_ADDR); + outw(INEA | TDMD, DEPCA_DATA); + } + } else if (status & (T_MORE | T_ONE)) { + lp->stats.collisions++; + } else { + lp->stats.tx_packets++; + } + + /* Update all the pointers */ + lp->tx_old = (++lp->tx_old) & lp->txRingMask; + } + + return 0; +} + +static int +depca_close(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + s16 nicsr; + u_long ioaddr = dev->base_addr; + + dev->start = 0; + dev->tbusy = 1; + + outw(CSR0, DEPCA_ADDR); + + if (depca_debug > 1) { + printk("%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, inw(DEPCA_DATA)); + } + + /* + ** We stop the DEPCA here -- it occasionally polls + ** memory if we don't. + */ + outw(STOP, DEPCA_DATA); + + /* + ** Give back the ROM in case the user wants to go to DOS + */ + if (lp->adapter != DEPCA) { + nicsr = inb(DEPCA_NICSR); + nicsr &= ~SHE; + outb(nicsr, DEPCA_NICSR); + } + + /* + ** Free the associated irq + */ + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = NULL; + + MOD_DEC_USE_COUNT; + + return 0; +} + +static void LoadCSRs(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + u_long ioaddr = dev->base_addr; + + outw(CSR1, DEPCA_ADDR); /* initialisation block address LSW */ + outw((u16)(lp->sh_mem & LA_MASK), DEPCA_DATA); + outw(CSR2, DEPCA_ADDR); /* initialisation block address MSW */ + outw((u16)((lp->sh_mem & LA_MASK) >> 16), DEPCA_DATA); + outw(CSR3, DEPCA_ADDR); /* ALE control */ + outw(ACON, DEPCA_DATA); + + outw(CSR0, DEPCA_ADDR); /* Point back to CSR0 */ + + return; +} + +static int InitRestartDepca(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + u_long ioaddr = dev->base_addr; + int i, status=0; + + /* Copy the shadow init_block to shared memory */ + memcpy_toio((char *)lp->sh_mem, &lp->init_block, sizeof(struct depca_init)); + + outw(CSR0, DEPCA_ADDR); /* point back to CSR0 */ + outw(INIT, DEPCA_DATA); /* initialize DEPCA */ + + /* wait for lance to complete initialisation */ + for (i=0;(i<100) && !(inw(DEPCA_DATA) & IDON); i++); + + if (i!=100) { + /* clear IDON by writing a "1", enable interrupts and start lance */ + outw(IDON | INEA | STRT, DEPCA_DATA); + if (depca_debug > 2) { + printk("%s: DEPCA open after %d ticks, init block 0x%08lx csr0 %4.4x.\n", + dev->name, i, lp->sh_mem, inw(DEPCA_DATA)); + } + } else { + printk("%s: DEPCA unopen after %d ticks, init block 0x%08lx csr0 %4.4x.\n", + dev->name, i, lp->sh_mem, inw(DEPCA_DATA)); + status = -1; + } + + return status; +} + +static struct enet_statistics * +depca_get_stats(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + + /* Null body since there is no framing error counter */ + + return &lp->stats; +} + +/* +** Set or clear the multicast filter for this adaptor. +*/ +static void +set_multicast_list(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + u_long ioaddr = dev->base_addr; + + if (irq2dev_map[dev->irq] != NULL) { + while(dev->tbusy); /* Stop ring access */ + set_bit(0, (void*)&dev->tbusy); + while(lp->tx_old != lp->tx_new); /* Wait for the ring to empty */ + + STOP_DEPCA; /* Temporarily stop the depca. */ + depca_init_ring(dev); /* Initialize the descriptor rings */ + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous mode */ + lp->init_block.mode |= PROM; + } else { + SetMulticastFilter(dev); + lp->init_block.mode &= ~PROM; /* Unset promiscuous mode */ + } + + LoadCSRs(dev); /* Reload CSR3 */ + InitRestartDepca(dev); /* Resume normal operation. */ + dev->tbusy = 0; /* Unlock the TX ring */ + } +} + +/* +** Calculate the hash code and update the logical address filter +** from a list of ethernet multicast addresses. +** Big endian crc one liner is mine, all mine, ha ha ha ha! +** LANCE calculates its hash codes big endian. +*/ +static void SetMulticastFilter(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + struct dev_mc_list *dmi=dev->mc_list; + char *addrs; + int i, j, bit, byte; + u16 hashcode; + s32 crc, poly = CRC_POLYNOMIAL_BE; + + if (dev->flags & IFF_ALLMULTI) { /* Set all multicast bits */ + for (i=0; i<(HASH_TABLE_LEN>>3); i++) { + lp->init_block.mcast_table[i] = (char)0xff; + } + } else { + for (i=0; i<(HASH_TABLE_LEN>>3); i++){ /* Clear the multicast table */ + lp->init_block.mcast_table[i]=0; + } + /* Add multicast addresses */ + for (i=0;i<dev->mc_count;i++) { /* for each address in the list */ + addrs=dmi->dmi_addr; + dmi=dmi->next; + if ((*addrs & 0x01) == 1) { /* multicast address? */ + crc = 0xffffffff; /* init CRC for each address */ + for (byte=0;byte<ETH_ALEN;byte++) {/* for each address byte */ + /* process each address bit */ + for (bit = *addrs++,j=0;j<8;j++, bit>>=1) { + crc = (crc << 1) ^ ((((crc<0?1:0) ^ bit) & 0x01) ? poly : 0); + } + } + hashcode = (crc & 1); /* hashcode is 6 LSb of CRC ... */ + for (j=0;j<5;j++) { /* ... in reverse order. */ + hashcode = (hashcode << 1) | ((crc>>=1) & 1); + } + + + byte = hashcode >> 3; /* bit[3-5] -> byte in filter */ + bit = 1 << (hashcode & 0x07); /* bit[0-2] -> bit in byte */ + lp->init_block.mcast_table[byte] |= bit; + } + } + } + + return; +} + +/* +** ISA bus I/O device probe +*/ +static void isa_probe(struct device *dev, u_long ioaddr) +{ + int i = num_depcas, maxSlots; + s32 ports[] = DEPCA_IO_PORTS; + + if (!ioaddr && autoprobed) return ; /* Been here before ! */ + if (ioaddr > 0x400) return; /* EISA Address */ + if (i >= MAX_NUM_DEPCAS) return; /* Too many ISA adapters */ + + if (ioaddr == 0) { /* Autoprobing */ + maxSlots = MAX_NUM_DEPCAS; + } else { /* Probe a specific location */ + ports[i] = ioaddr; + maxSlots = i + 1; + } + + for (; (i<maxSlots) && (dev!=NULL) && ports[i]; i++) { + if (DevicePresent(ports[i]) == 0) { + if (check_region(ports[i], DEPCA_TOTAL_SIZE) == 0) { + if ((dev = alloc_device(dev, ports[i])) != NULL) { + if (depca_hw_init(dev, ports[i]) == 0) { + num_depcas++; + } + num_eth++; + } + } else if (autoprobed) { + printk("%s: region already allocated at 0x%04x.\n", dev->name,ports[i]); + } + } + } + + return; +} + +/* +** EISA bus I/O device probe. Probe from slot 1 since slot 0 is usually +** the motherboard. Upto 15 EISA devices are supported. +*/ +static void eisa_probe(struct device *dev, u_long ioaddr) +{ + int i, maxSlots; + u_long iobase; + char name[DEPCA_STRLEN]; + + if (!ioaddr && autoprobed) return ; /* Been here before ! */ + if ((ioaddr < 0x400) && (ioaddr > 0)) return; /* ISA Address */ + + if (ioaddr == 0) { /* Autoprobing */ + iobase = EISA_SLOT_INC; /* Get the first slot address */ + i = 1; + maxSlots = MAX_EISA_SLOTS; + } else { /* Probe a specific location */ + iobase = ioaddr; + i = (ioaddr >> 12); + maxSlots = i + 1; + } + if ((iobase & 0x0fff) == 0) iobase += DEPCA_EISA_IO_PORTS; + + for (; (i<maxSlots) && (dev!=NULL); i++, iobase+=EISA_SLOT_INC) { + if (EISA_signature(name, EISA_ID)) { + if (DevicePresent(iobase) == 0) { + if (check_region(iobase, DEPCA_TOTAL_SIZE) == 0) { + if ((dev = alloc_device(dev, iobase)) != NULL) { + if (depca_hw_init(dev, iobase) == 0) { + num_depcas++; + } + num_eth++; + } + } else if (autoprobed) { + printk("%s: region already allocated at 0x%04lx.\n",dev->name,iobase); + } + } + } + } + + return; +} + +/* +** Search the entire 'eth' device list for a fixed probe. If a match isn't +** found then check for an autoprobe or unused device location. If they +** are not available then insert a new device structure at the end of +** the current list. +*/ +static struct device * +alloc_device(struct device *dev, u_long iobase) +{ + struct device *adev = NULL; + int fixed = 0, new_dev = 0; + + num_eth = depca_dev_index(dev->name); + if (loading_module) return dev; + + while (1) { + if (((dev->base_addr == DEPCA_NDA) || (dev->base_addr==0)) && !adev) { + adev=dev; + } else if ((dev->priv == NULL) && (dev->base_addr==iobase)) { + fixed = 1; + } else { + if (dev->next == NULL) { + new_dev = 1; + } else if (strncmp(dev->next->name, "eth", 3) != 0) { + new_dev = 1; + } + } + if ((dev->next == NULL) || new_dev || fixed) break; + dev = dev->next; + num_eth++; + } + if (adev && !fixed) { + dev = adev; + num_eth = depca_dev_index(dev->name); + new_dev = 0; + } + + if (((dev->next == NULL) && + ((dev->base_addr != DEPCA_NDA) && (dev->base_addr != 0)) && !fixed) || + new_dev) { + num_eth++; /* New device */ + dev = insert_device(dev, iobase, depca_probe); + } + + return dev; +} + +/* +** If at end of eth device list and can't use current entry, malloc +** one up. If memory could not be allocated, print an error message. +*/ +static struct device * +insert_device(struct device *dev, u_long iobase, int (*init)(struct device *)) +{ + struct device *new; + + new = (struct device *)kmalloc(sizeof(struct device)+8, GFP_KERNEL); + if (new == NULL) { + printk("eth%d: Device not initialised, insufficient memory\n",num_eth); + return NULL; + } else { + new->next = dev->next; + dev->next = new; + dev = dev->next; /* point to the new device */ + dev->name = (char *)(dev + 1); + if (num_eth > 9999) { + sprintf(dev->name,"eth????");/* New device name */ + } else { + sprintf(dev->name,"eth%d", num_eth);/* New device name */ + } + dev->base_addr = iobase; /* assign the io address */ + dev->init = init; /* initialisation routine */ + } + + return dev; +} + +static int +depca_dev_index(char *s) +{ + int i=0, j=0; + + for (;*s; s++) { + if (isdigit(*s)) { + j=1; + i = (i * 10) + (*s - '0'); + } else if (j) break; + } + + return i; +} + +/* +** Look for a particular board name in the on-board Remote Diagnostics +** and Boot (readb) ROM. This will also give us a clue to the network RAM +** base address. +*/ +static void DepcaSignature(char *name, u_long paddr) +{ + u_int i,j,k; + const char *signatures[] = DEPCA_SIGNATURE; + char tmpstr[16]; + + /* Copy the first 16 bytes of ROM */ + for (i=0;i<16;i++) { + tmpstr[i] = readb(paddr+0xc000+i); + } + + /* Check if PROM contains a valid string */ + for (i=0;*signatures[i]!='\0';i++) { + for (j=0,k=0;j<16 && k<strlen(signatures[i]);j++) { + if (signatures[i][k] == tmpstr[j]) { /* track signature */ + k++; + } else { /* lost signature; begin search again */ + k=0; + } + } + if (k == strlen(signatures[i])) break; + } + + /* Check if name string is valid, provided there's no PROM */ + if (*name && (i == unknown)) { + for (i=0;*signatures[i]!='\0';i++) { + if (strcmp(name,signatures[i]) == 0) break; + } + } + + /* Update search results */ + strcpy(name,signatures[i]); + adapter = i; + + return; +} + +/* +** Look for a special sequence in the Ethernet station address PROM that +** is common across all DEPCA products. Note that the original DEPCA needs +** its ROM address counter to be initialized and enabled. Only enable +** if the first address octet is a 0x08 - this minimises the chances of +** messing around with some other hardware, but it assumes that this DEPCA +** card initialized itself correctly. +** +** Search the Ethernet address ROM for the signature. Since the ROM address +** counter can start at an arbitrary point, the search must include the entire +** probe sequence length plus the (length_of_the_signature - 1). +** Stop the search IMMEDIATELY after the signature is found so that the +** PROM address counter is correctly positioned at the start of the +** ethernet address for later read out. +*/ +static int DevicePresent(u_long ioaddr) +{ + union { + struct { + u32 a; + u32 b; + } llsig; + char Sig[sizeof(u32) << 1]; + } dev; + short sigLength=0; + s8 data; + s16 nicsr; + int i, j, status = 0; + + data = inb(DEPCA_PROM); /* clear counter on DEPCA */ + data = inb(DEPCA_PROM); /* read data */ + + if (data == 0x08) { /* Enable counter on DEPCA */ + nicsr = inb(DEPCA_NICSR); + nicsr |= AAC; + outb(nicsr, DEPCA_NICSR); + } + + dev.llsig.a = ETH_PROM_SIG; + dev.llsig.b = ETH_PROM_SIG; + sigLength = sizeof(u32) << 1; + + for (i=0,j=0;j<sigLength && i<PROBE_LENGTH+sigLength-1;i++) { + data = inb(DEPCA_PROM); + if (dev.Sig[j] == data) { /* track signature */ + j++; + } else { /* lost signature; begin search again */ + if (data == dev.Sig[0]) { /* rare case.... */ + j=1; + } else { + j=0; + } + } + } + + if (j!=sigLength) { + status = -ENODEV; /* search failed */ + } + + return status; +} + +/* +** The DE100 and DE101 PROM accesses were made non-standard for some bizarre +** reason: access the upper half of the PROM with x=0; access the lower half +** with x=1. +*/ +static int get_hw_addr(struct device *dev) +{ + u_long ioaddr = dev->base_addr; + int i, k, tmp, status = 0; + u_short j, x, chksum; + + x = (((adapter == de100) || (adapter == de101)) ? 1 : 0); + + for (i=0,k=0,j=0;j<3;j++) { + k <<= 1 ; + if (k > 0xffff) k-=0xffff; + + k += (u_char) (tmp = inb(DEPCA_PROM + x)); + dev->dev_addr[i++] = (u_char) tmp; + k += (u_short) ((tmp = inb(DEPCA_PROM + x)) << 8); + dev->dev_addr[i++] = (u_char) tmp; + + if (k > 0xffff) k-=0xffff; + } + if (k == 0xffff) k=0; + + chksum = (u_char) inb(DEPCA_PROM + x); + chksum |= (u_short) (inb(DEPCA_PROM + x) << 8); + if (k != chksum) status = -1; + + return status; +} + +/* +** Load a packet into the shared memory +*/ +static int load_packet(struct device *dev, struct sk_buff *skb) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + int i, entry, end, len, status = 0; + + entry = lp->tx_new; /* Ring around buffer number. */ + end = (entry + (skb->len - 1) / TX_BUFF_SZ) & lp->txRingMask; + if (!(readl(&lp->tx_ring[end].base) & T_OWN)) {/* Enough room? */ + /* + ** Caution: the write order is important here... don't set up the + ** ownership rights until all the other information is in place. + */ + if (end < entry) { /* wrapped buffer */ + len = (lp->txRingMask - entry + 1) * TX_BUFF_SZ; + memcpy_toio(lp->tx_memcpy[entry], skb->data, len); + memcpy_toio(lp->tx_memcpy[0], skb->data + len, skb->len - len); + } else { /* linear buffer */ + memcpy_toio(lp->tx_memcpy[entry], skb->data, skb->len); + } + + /* set up the buffer descriptors */ + len = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len; + for (i = entry; i != end; i = (++i) & lp->txRingMask) { + /* clean out flags */ + writel(readl(&lp->tx_ring[i].base) & ~T_FLAGS, &lp->tx_ring[i].base); + writew(0x0000, &lp->tx_ring[i].misc); /* clears other error flags */ + writew(-TX_BUFF_SZ, &lp->tx_ring[i].length);/* packet length in buffer */ + len -= TX_BUFF_SZ; + } + /* clean out flags */ + writel(readl(&lp->tx_ring[end].base) & ~T_FLAGS, &lp->tx_ring[end].base); + writew(0x0000, &lp->tx_ring[end].misc); /* clears other error flags */ + writew(-len, &lp->tx_ring[end].length); /* packet length in last buff */ + + /* start of packet */ + writel(readl(&lp->tx_ring[entry].base) | T_STP, &lp->tx_ring[entry].base); + /* end of packet */ + writel(readl(&lp->tx_ring[end].base) | T_ENP, &lp->tx_ring[end].base); + + for (i=end; i!=entry; --i) { + /* ownership of packet */ + writel(readl(&lp->tx_ring[i].base) | T_OWN, &lp->tx_ring[i].base); + if (i == 0) i=lp->txRingMask+1; + } + writel(readl(&lp->tx_ring[entry].base) | T_OWN, &lp->tx_ring[entry].base); + + lp->tx_new = (++end) & lp->txRingMask; /* update current pointers */ + } else { + status = -1; + } + + return status; +} + +/* +** Look for a particular board name in the EISA configuration space +*/ +static int EISA_signature(char *name, s32 eisa_id) +{ + u_int i; + const char *signatures[] = DEPCA_SIGNATURE; + char ManCode[DEPCA_STRLEN]; + union { + s32 ID; + char Id[4]; + } Eisa; + int status = 0; + + *name = '\0'; + Eisa.ID = inl(eisa_id); + + ManCode[0]=(((Eisa.Id[0]>>2)&0x1f)+0x40); + ManCode[1]=(((Eisa.Id[1]&0xe0)>>5)+((Eisa.Id[0]&0x03)<<3)+0x40); + ManCode[2]=(((Eisa.Id[2]>>4)&0x0f)+0x30); + ManCode[3]=(( Eisa.Id[2]&0x0f)+0x30); + ManCode[4]=(((Eisa.Id[3]>>4)&0x0f)+0x30); + ManCode[5]='\0'; + + for (i=0;(*signatures[i] != '\0') && (*name == '\0');i++) { + if (strstr(ManCode, signatures[i]) != NULL) { + strcpy(name,ManCode); + status = 1; + } + } + + return status; +} + +static void depca_dbg_open(struct device *dev) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + u_long ioaddr = dev->base_addr; + struct depca_init *p = (struct depca_init *)lp->sh_mem; + int i; + + if (depca_debug > 1){ + /* Copy the shadow init_block to shared memory */ + memcpy_toio((char *)lp->sh_mem,&lp->init_block,sizeof(struct depca_init)); + + printk("%s: depca open with irq %d\n",dev->name,dev->irq); + printk("Descriptor head addresses:\n"); + printk("\t0x%lx 0x%lx\n",(u_long)lp->rx_ring, (u_long)lp->tx_ring); + printk("Descriptor addresses:\nRX: "); + for (i=0;i<lp->rxRingMask;i++){ + if (i < 3) { + printk("0x%8.8lx ", (long) &lp->rx_ring[i].base); + } + } + printk("...0x%8.8lx\n", (long) &lp->rx_ring[i].base); + printk("TX: "); + for (i=0;i<lp->txRingMask;i++){ + if (i < 3) { + printk("0x%8.8lx ", (long) &lp->tx_ring[i].base); + } + } + printk("...0x%8.8lx\n", (long) &lp->tx_ring[i].base); + printk("\nDescriptor buffers:\nRX: "); + for (i=0;i<lp->rxRingMask;i++){ + if (i < 3) { + printk("0x%8.8x ", readl(&lp->rx_ring[i].base)); + } + } + printk("...0x%8.8x\n", readl(&lp->rx_ring[i].base)); + printk("TX: "); + for (i=0;i<lp->txRingMask;i++){ + if (i < 3) { + printk("0x%8.8x ", readl(&lp->tx_ring[i].base)); + } + } + printk("...0x%8.8x\n", readl(&lp->tx_ring[i].base)); + printk("Initialisation block at 0x%8.8lx\n",lp->sh_mem); + printk("\tmode: 0x%4.4x\n",readw(&p->mode)); + printk("\tphysical address: "); + for (i=0;i<ETH_ALEN-1;i++){ + printk("%2.2x:",(u_char)readb(&p->phys_addr[i])); + } + printk("%2.2x\n",(u_char)readb(&p->phys_addr[i])); + printk("\tmulticast hash table: "); + for (i=0;i<(HASH_TABLE_LEN >> 3)-1;i++){ + printk("%2.2x:",(u_char)readb(&p->mcast_table[i])); + } + printk("%2.2x\n",(u_char)readb(&p->mcast_table[i])); + printk("\trx_ring at: 0x%8.8x\n",readl(&p->rx_ring)); + printk("\ttx_ring at: 0x%8.8x\n",readl(&p->tx_ring)); + printk("dma_buffs: 0x%8.8lx\n",lp->dma_buffs); + printk("Ring size:\nRX: %d Log2(rxRingMask): 0x%8.8x\n", + (int)lp->rxRingMask + 1, + lp->rx_rlen); + printk("TX: %d Log2(txRingMask): 0x%8.8x\n", + (int)lp->txRingMask + 1, + lp->tx_rlen); + outw(CSR2,DEPCA_ADDR); + printk("CSR2&1: 0x%4.4x",inw(DEPCA_DATA)); + outw(CSR1,DEPCA_ADDR); + printk("%4.4x\n",inw(DEPCA_DATA)); + outw(CSR3,DEPCA_ADDR); + printk("CSR3: 0x%4.4x\n",inw(DEPCA_DATA)); + } + + return; +} + +/* +** Perform IOCTL call functions here. Some are privileged operations and the +** effective uid is checked in those cases. +** All MCA IOCTLs will not work here and are for testing purposes only. +*/ +static int depca_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct depca_private *lp = (struct depca_private *)dev->priv; + struct depca_ioctl *ioc = (struct depca_ioctl *) &rq->ifr_data; + int i, status = 0; + u_long ioaddr = dev->base_addr; + union { + u8 addr[(HASH_TABLE_LEN * ETH_ALEN)]; + u16 sval[(HASH_TABLE_LEN * ETH_ALEN) >> 1]; + u32 lval[(HASH_TABLE_LEN * ETH_ALEN) >> 2]; + } tmp; + + switch(ioc->cmd) { + case DEPCA_GET_HWADDR: /* Get the hardware address */ + for (i=0; i<ETH_ALEN; i++) { + tmp.addr[i] = dev->dev_addr[i]; + } + ioc->len = ETH_ALEN; + if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + } + + break; + case DEPCA_SET_HWADDR: /* Set the hardware address */ + if (suser()) { + if (!(status = verify_area(VERIFY_READ, (void *)ioc->data, ETH_ALEN))) { + memcpy_fromfs(tmp.addr,ioc->data,ETH_ALEN); + for (i=0; i<ETH_ALEN; i++) { + dev->dev_addr[i] = tmp.addr[i]; + } + while(dev->tbusy); /* Stop ring access */ + set_bit(0, (void*)&dev->tbusy); + while(lp->tx_old != lp->tx_new);/* Wait for the ring to empty */ + + STOP_DEPCA; /* Temporarily stop the depca. */ + depca_init_ring(dev); /* Initialize the descriptor rings */ + LoadCSRs(dev); /* Reload CSR3 */ + InitRestartDepca(dev); /* Resume normal operation. */ + dev->tbusy = 0; /* Unlock the TX ring */ + } + } else { + status = -EPERM; + } + + break; + case DEPCA_SET_PROM: /* Set Promiscuous Mode */ + if (suser()) { + while(dev->tbusy); /* Stop ring access */ + set_bit(0, (void*)&dev->tbusy); + while(lp->tx_old != lp->tx_new); /* Wait for the ring to empty */ + + STOP_DEPCA; /* Temporarily stop the depca. */ + depca_init_ring(dev); /* Initialize the descriptor rings */ + lp->init_block.mode |= PROM; /* Set promiscuous mode */ + + LoadCSRs(dev); /* Reload CSR3 */ + InitRestartDepca(dev); /* Resume normal operation. */ + dev->tbusy = 0; /* Unlock the TX ring */ + } else { + status = -EPERM; + } + + break; + case DEPCA_CLR_PROM: /* Clear Promiscuous Mode */ + if (suser()) { + while(dev->tbusy); /* Stop ring access */ + set_bit(0, (void*)&dev->tbusy); + while(lp->tx_old != lp->tx_new); /* Wait for the ring to empty */ + + STOP_DEPCA; /* Temporarily stop the depca. */ + depca_init_ring(dev); /* Initialize the descriptor rings */ + lp->init_block.mode &= ~PROM; /* Clear promiscuous mode */ + + LoadCSRs(dev); /* Reload CSR3 */ + InitRestartDepca(dev); /* Resume normal operation. */ + dev->tbusy = 0; /* Unlock the TX ring */ + } else { + status = -EPERM; + } + + break; + case DEPCA_SAY_BOO: /* Say "Boo!" to the kernel log file */ + printk("%s: Boo!\n", dev->name); + + break; + case DEPCA_GET_MCA: /* Get the multicast address table */ + ioc->len = (HASH_TABLE_LEN >> 3); + if (!(status = verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, lp->init_block.mcast_table, ioc->len); + } + + break; + case DEPCA_SET_MCA: /* Set a multicast address */ + if (suser()) { + if (!(status=verify_area(VERIFY_READ, ioc->data, ETH_ALEN*ioc->len))) { + memcpy_fromfs(tmp.addr, ioc->data, ETH_ALEN * ioc->len); + set_multicast_list(dev); + } + } else { + status = -EPERM; + } + + break; + case DEPCA_CLR_MCA: /* Clear all multicast addresses */ + if (suser()) { + set_multicast_list(dev); + } else { + status = -EPERM; + } + + break; + case DEPCA_MCA_EN: /* Enable pass all multicast addressing */ + if (suser()) { + set_multicast_list(dev); + } else { + status = -EPERM; + } + + break; + case DEPCA_GET_STATS: /* Get the driver statistics */ + cli(); + ioc->len = sizeof(lp->pktStats); + if (!(status=verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, &lp->pktStats, ioc->len); + } + sti(); + + break; + case DEPCA_CLR_STATS: /* Zero out the driver statistics */ + if (suser()) { + cli(); + memset(&lp->pktStats, 0, sizeof(lp->pktStats)); + sti(); + } else { + status = -EPERM; + } + + break; + case DEPCA_GET_REG: /* Get the DEPCA Registers */ + i=0; + tmp.sval[i++] = inw(DEPCA_NICSR); + outw(CSR0, DEPCA_ADDR); /* status register */ + tmp.sval[i++] = inw(DEPCA_DATA); + memcpy(&tmp.sval[i], &lp->init_block, sizeof(struct depca_init)); + ioc->len = i+sizeof(struct depca_init); + if (!(status=verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + } + + break; + default: + status = -EOPNOTSUPP; + } + + return status; +} + +#ifdef MODULE +static char devicename[9] = { 0, }; +static struct device thisDepca = { + devicename, /* device name is inserted by /linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0x200, 7, /* I/O address, IRQ */ + 0, 0, 0, NULL, depca_probe }; + +static int irq=7; /* EDIT THESE LINE FOR YOUR CONFIGURATION */ +static int io=0x200; /* Or use the irq= io= options to insmod */ + +/* See depca_probe() for autoprobe messages when a module */ +int +init_module(void) +{ + thisDepca.irq=irq; + thisDepca.base_addr=io; + + if (register_netdev(&thisDepca) != 0) + return -EIO; + + return 0; +} + +void +cleanup_module(void) +{ + if (thisDepca.priv) { + kfree(thisDepca.priv); + thisDepca.priv = NULL; + } + thisDepca.irq=0; + + unregister_netdev(&thisDepca); + release_region(thisDepca.base_addr, DEPCA_TOTAL_SIZE); +} +#endif /* MODULE */ + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/linux/include -Wall -Wstrict-prototypes -fomit-frame-pointer -fno-strength-reduce -malign-loops=2 -malign-jumps=2 -malign-functions=2 -O2 -m486 -c depca.c" + * + * compile-command: "gcc -D__KERNEL__ -DMODULE -I/linux/include -Wall -Wstrict-prototypes -fomit-frame-pointer -fno-strength-reduce -malign-loops=2 -malign-jumps=2 -malign-functions=2 -O2 -m486 -c depca.c" + * End: + */ diff --git a/linux/src/drivers/net/depca.h b/linux/src/drivers/net/depca.h new file mode 100644 index 00000000..012f7399 --- /dev/null +++ b/linux/src/drivers/net/depca.h @@ -0,0 +1,185 @@ +/* + Written 1994 by David C. Davies. + + Copyright 1994 David C. Davies. This software may be used and distributed + according to the terms of the GNU Public License, incorporated herein by + reference. +*/ + +/* +** I/O addresses. Note that the 2k buffer option is not supported in +** this driver. +*/ +#define DEPCA_NICSR ioaddr+0x00 /* Network interface CSR */ +#define DEPCA_RBI ioaddr+0x02 /* RAM buffer index (2k buffer mode) */ +#define DEPCA_DATA ioaddr+0x04 /* LANCE registers' data port */ +#define DEPCA_ADDR ioaddr+0x06 /* LANCE registers' address port */ +#define DEPCA_HBASE ioaddr+0x08 /* EISA high memory base address reg. */ +#define DEPCA_PROM ioaddr+0x0c /* Ethernet address ROM data port */ +#define DEPCA_CNFG ioaddr+0x0c /* EISA Configuration port */ +#define DEPCA_RBSA ioaddr+0x0e /* RAM buffer starting address (2k buff.) */ + +/* +** These are LANCE registers addressable through DEPCA_ADDR +*/ +#define CSR0 0 +#define CSR1 1 +#define CSR2 2 +#define CSR3 3 + +/* +** NETWORK INTERFACE CSR (NI_CSR) bit definitions +*/ + +#define TO 0x0100 /* Time Out for remote boot */ +#define SHE 0x0080 /* SHadow memory Enable */ +#define BS 0x0040 /* Bank Select */ +#define BUF 0x0020 /* BUFfer size (1->32k, 0->64k) */ +#define RBE 0x0010 /* Remote Boot Enable (1->net boot) */ +#define AAC 0x0008 /* Address ROM Address Counter (1->enable) */ +#define _128KB 0x0008 /* 128kB Network RAM (1->enable) */ +#define IM 0x0004 /* Interrupt Mask (1->mask) */ +#define IEN 0x0002 /* Interrupt tristate ENable (1->enable) */ +#define LED 0x0001 /* LED control */ + +/* +** Control and Status Register 0 (CSR0) bit definitions +*/ + +#define ERR 0x8000 /* Error summary */ +#define BABL 0x4000 /* Babble transmitter timeout error */ +#define CERR 0x2000 /* Collision Error */ +#define MISS 0x1000 /* Missed packet */ +#define MERR 0x0800 /* Memory Error */ +#define RINT 0x0400 /* Receiver Interrupt */ +#define TINT 0x0200 /* Transmit Interrupt */ +#define IDON 0x0100 /* Initialization Done */ +#define INTR 0x0080 /* Interrupt Flag */ +#define INEA 0x0040 /* Interrupt Enable */ +#define RXON 0x0020 /* Receiver on */ +#define TXON 0x0010 /* Transmitter on */ +#define TDMD 0x0008 /* Transmit Demand */ +#define STOP 0x0004 /* Stop */ +#define STRT 0x0002 /* Start */ +#define INIT 0x0001 /* Initialize */ +#define INTM 0xff00 /* Interrupt Mask */ +#define INTE 0xfff0 /* Interrupt Enable */ + +/* +** CONTROL AND STATUS REGISTER 3 (CSR3) +*/ + +#define BSWP 0x0004 /* Byte SWaP */ +#define ACON 0x0002 /* ALE control */ +#define BCON 0x0001 /* Byte CONtrol */ + +/* +** Initialization Block Mode Register +*/ + +#define PROM 0x8000 /* Promiscuous Mode */ +#define EMBA 0x0080 /* Enable Modified Back-off Algorithm */ +#define INTL 0x0040 /* Internal Loopback */ +#define DRTY 0x0020 /* Disable Retry */ +#define COLL 0x0010 /* Force Collision */ +#define DTCR 0x0008 /* Disable Transmit CRC */ +#define LOOP 0x0004 /* Loopback */ +#define DTX 0x0002 /* Disable the Transmitter */ +#define DRX 0x0001 /* Disable the Receiver */ + +/* +** Receive Message Descriptor 1 (RMD1) bit definitions. +*/ + +#define R_OWN 0x80000000 /* Owner bit 0 = host, 1 = lance */ +#define R_ERR 0x4000 /* Error Summary */ +#define R_FRAM 0x2000 /* Framing Error */ +#define R_OFLO 0x1000 /* Overflow Error */ +#define R_CRC 0x0800 /* CRC Error */ +#define R_BUFF 0x0400 /* Buffer Error */ +#define R_STP 0x0200 /* Start of Packet */ +#define R_ENP 0x0100 /* End of Packet */ + +/* +** Transmit Message Descriptor 1 (TMD1) bit definitions. +*/ + +#define T_OWN 0x80000000 /* Owner bit 0 = host, 1 = lance */ +#define T_ERR 0x4000 /* Error Summary */ +#define T_ADD_FCS 0x2000 /* More the 1 retry needed to Xmit */ +#define T_MORE 0x1000 /* >1 retry to transmit packet */ +#define T_ONE 0x0800 /* 1 try needed to transmit the packet */ +#define T_DEF 0x0400 /* Deferred */ +#define T_STP 0x02000000 /* Start of Packet */ +#define T_ENP 0x01000000 /* End of Packet */ +#define T_FLAGS 0xff000000 /* TX Flags Field */ + +/* +** Transmit Message Descriptor 3 (TMD3) bit definitions. +*/ + +#define TMD3_BUFF 0x8000 /* BUFFer error */ +#define TMD3_UFLO 0x4000 /* UnderFLOw error */ +#define TMD3_RES 0x2000 /* REServed */ +#define TMD3_LCOL 0x1000 /* Late COLlision */ +#define TMD3_LCAR 0x0800 /* Loss of CARrier */ +#define TMD3_RTRY 0x0400 /* ReTRY error */ + +/* +** EISA configuration Register (CNFG) bit definitions +*/ + +#define TIMEOUT 0x0100 /* 0:2.5 mins, 1: 30 secs */ +#define REMOTE 0x0080 /* Remote Boot Enable -> 1 */ +#define IRQ11 0x0040 /* Enable -> 1 */ +#define IRQ10 0x0020 /* Enable -> 1 */ +#define IRQ9 0x0010 /* Enable -> 1 */ +#define IRQ5 0x0008 /* Enable -> 1 */ +#define BUFF 0x0004 /* 0: 64kB or 128kB, 1: 32kB */ +#define PADR16 0x0002 /* RAM on 64kB boundary */ +#define PADR17 0x0001 /* RAM on 128kB boundary */ + +/* +** Miscellaneous +*/ +#define HASH_TABLE_LEN 64 /* Bits */ +#define HASH_BITS 0x003f /* 6 LS bits */ + +#define MASK_INTERRUPTS 1 +#define UNMASK_INTERRUPTS 0 + +#define EISA_EN 0x0001 /* Enable EISA bus buffers */ +#define EISA_ID iobase+0x0080 /* ID long word for EISA card */ +#define EISA_CTRL iobase+0x0084 /* Control word for EISA card */ + +/* +** Include the IOCTL stuff +*/ +#include <linux/sockios.h> + +#define DEPCAIOCTL SIOCDEVPRIVATE + +struct depca_ioctl { + unsigned short cmd; /* Command to run */ + unsigned short len; /* Length of the data buffer */ + unsigned char *data; /* Pointer to the data buffer */ +}; + +/* +** Recognised commands for the driver +*/ +#define DEPCA_GET_HWADDR 0x01 /* Get the hardware address */ +#define DEPCA_SET_HWADDR 0x02 /* Get the hardware address */ +#define DEPCA_SET_PROM 0x03 /* Set Promiscuous Mode */ +#define DEPCA_CLR_PROM 0x04 /* Clear Promiscuous Mode */ +#define DEPCA_SAY_BOO 0x05 /* Say "Boo!" to the kernel log file */ +#define DEPCA_GET_MCA 0x06 /* Get a multicast address */ +#define DEPCA_SET_MCA 0x07 /* Set a multicast address */ +#define DEPCA_CLR_MCA 0x08 /* Clear a multicast address */ +#define DEPCA_MCA_EN 0x09 /* Enable a multicast address group */ +#define DEPCA_GET_STATS 0x0a /* Get the driver statistics */ +#define DEPCA_CLR_STATS 0x0b /* Zero out the driver statistics */ +#define DEPCA_GET_REG 0x0c /* Get the Register contents */ +#define DEPCA_SET_REG 0x0d /* Set the Register contents */ +#define DEPCA_DUMP 0x0f /* Dump the DEPCA Status */ + diff --git a/linux/src/drivers/net/e2100.c b/linux/src/drivers/net/e2100.c new file mode 100644 index 00000000..7ba12d31 --- /dev/null +++ b/linux/src/drivers/net/e2100.c @@ -0,0 +1,456 @@ +/* e2100.c: A Cabletron E2100 series ethernet driver for linux. */ +/* + Written 1993-1994 by Donald Becker. + + Copyright 1994 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU Public License, + incorporated herein by reference. + + This is a driver for the Cabletron E2100 series ethercards. + + The Author may be reached as becker@cesdis.gsfc.nasa.gov, or + C/O Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + The E2100 series ethercard is a fairly generic shared memory 8390 + implementation. The only unusual aspect is the way the shared memory + registers are set: first you do an inb() in what is normally the + station address region, and the low three bits of next outb() *address* + is used as the write value for that register. Either someone wasn't + too used to dem bit en bites, or they were trying to obfuscate the + programming interface. + + There is an additional complication when setting the window on the packet + buffer. You must first do a read into the packet buffer region with the + low 8 address bits the address setting the page for the start of the packet + buffer window, and then do the above operation. See mem_on() for details. + + One bug on the chip is that even a hard reset won't disable the memory + window, usually resulting in a hung machine if mem_off() isn't called. + If this happens, you must power down the machine for about 30 seconds. +*/ + +static const char *version = + "e2100.c:v1.01 7/21/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include <asm/io.h> +#include <asm/system.h> + +#include "8390.h" + +static int e21_probe_list[] = {0x300, 0x280, 0x380, 0x220, 0}; + +/* Offsets from the base_addr. + Read from the ASIC register, and the low three bits of the next outb() + address is used to set the corresponding register. */ +#define E21_NIC_OFFSET 0 /* Offset to the 8390 NIC. */ +#define E21_ASIC 0x10 +#define E21_MEM_ENABLE 0x10 +#define E21_MEM_ON 0x05 /* Enable memory in 16 bit mode. */ +#define E21_MEM_ON_8 0x07 /* Enable memory in 8 bit mode. */ +#define E21_MEM_BASE 0x11 +#define E21_IRQ_LOW 0x12 /* The low three bits of the IRQ number. */ +#define E21_IRQ_HIGH 0x14 /* The high IRQ bit and media select ... */ +#define E21_MEDIA 0x14 /* (alias). */ +#define E21_ALT_IFPORT 0x02 /* Set to use the other (BNC,AUI) port. */ +#define E21_BIG_MEM 0x04 /* Use a bigger (64K) buffer (we don't) */ +#define E21_SAPROM 0x10 /* Offset to station address data. */ +#define E21_IO_EXTENT 0x20 + +extern inline void mem_on(short port, volatile char *mem_base, + unsigned char start_page ) +{ + /* This is a little weird: set the shared memory window by doing a + read. The low address bits specify the starting page. */ + mem_base[start_page]; + inb(port + E21_MEM_ENABLE); + outb(E21_MEM_ON, port + E21_MEM_ENABLE + E21_MEM_ON); +} + +extern inline void mem_off(short port) +{ + inb(port + E21_MEM_ENABLE); + outb(0x00, port + E21_MEM_ENABLE); +} + +/* In other drivers I put the TX pages first, but the E2100 window circuitry + is designed to have a 4K Tx region last. The windowing circuitry wraps the + window at 0x2fff->0x0000 so that the packets at e.g. 0x2f00 in the RX ring + appear contiguously in the window. */ +#define E21_RX_START_PG 0x00 /* First page of RX buffer */ +#define E21_RX_STOP_PG 0x30 /* Last page +1 of RX ring */ +#define E21_BIG_RX_STOP_PG 0xF0 /* Last page +1 of RX ring */ +#define E21_TX_START_PG E21_RX_STOP_PG /* First page of TX buffer */ + +int e2100_probe(struct device *dev); +int e21_probe1(struct device *dev, int ioaddr); + +static int e21_open(struct device *dev); +static void e21_reset_8390(struct device *dev); +static void e21_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void e21_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static void e21_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); + +static int e21_close(struct device *dev); + + +/* Probe for the E2100 series ethercards. These cards have an 8390 at the + base address and the station address at both offset 0x10 and 0x18. I read + the station address from offset 0x18 to avoid the dataport of NE2000 + ethercards, and look for Ctron's unique ID (first three octets of the + station address). + */ + +int e2100_probe(struct device *dev) +{ + int *port; + int base_addr = dev->base_addr; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return e21_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (port = e21_probe_list; *port; port++) { + if (check_region(*port, E21_IO_EXTENT)) + continue; + if (e21_probe1(dev, *port) == 0) + return 0; + } + + return ENODEV; +} + +int e21_probe1(struct device *dev, int ioaddr) +{ + int i, status; + unsigned char *station_addr = dev->dev_addr; + static unsigned version_printed = 0; + + /* First check the station address for the Ctron prefix. */ + if (inb(ioaddr + E21_SAPROM + 0) != 0x00 + || inb(ioaddr + E21_SAPROM + 1) != 0x00 + || inb(ioaddr + E21_SAPROM + 2) != 0x1d) + return ENODEV; + + /* Verify by making certain that there is a 8390 at there. */ + outb(E8390_NODMA + E8390_STOP, ioaddr); + SLOW_DOWN_IO; + status = inb(ioaddr); + if (status != 0x21 && status != 0x23) + return ENODEV; + + /* Read the station address PROM. */ + for (i = 0; i < 6; i++) + station_addr[i] = inb(ioaddr + E21_SAPROM + i); + + inb(ioaddr + E21_MEDIA); /* Point to media selection. */ + outb(0, ioaddr + E21_ASIC); /* and disable the secondary interface. */ + + if (ei_debug && version_printed++ == 0) + printk(version); + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk("e2100.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + printk("%s: E21** at %#3x,", dev->name, ioaddr); + for (i = 0; i < 6; i++) + printk(" %02X", station_addr[i]); + + if (dev->irq < 2) { + int irqlist[] = {15,11,10,12,5,9,3,4}, i; + for (i = 0; i < 8; i++) + if (request_irq (irqlist[i], NULL, 0, "bogus", NULL) != -EBUSY) { + dev->irq = irqlist[i]; + break; + } + if (i >= 8) { + printk(" unable to get IRQ %d.\n", dev->irq); + return EAGAIN; + } + } else if (dev->irq == 2) /* Fixup luser bogosity: IRQ2 is really IRQ9 */ + dev->irq = 9; + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk (" unable to get memory for dev->priv.\n"); + return -ENOMEM; + } + + /* Grab the region so we can find a different board if IRQ select fails. */ + request_region(ioaddr, E21_IO_EXTENT, "e2100"); + + /* The 8390 is at the base address. */ + dev->base_addr = ioaddr; + + ei_status.name = "E2100"; + ei_status.word16 = 1; + ei_status.tx_start_page = E21_TX_START_PG; + ei_status.rx_start_page = E21_RX_START_PG; + ei_status.stop_page = E21_RX_STOP_PG; + ei_status.saved_irq = dev->irq; + + /* Check the media port used. The port can be passed in on the + low mem_end bits. */ + if (dev->mem_end & 15) + dev->if_port = dev->mem_end & 7; + else { + dev->if_port = 0; + inb(ioaddr + E21_MEDIA); /* Turn automatic media detection on. */ + for(i = 0; i < 6; i++) + if (station_addr[i] != inb(ioaddr + E21_SAPROM + 8 + i)) { + dev->if_port = 1; + break; + } + } + + /* Never map in the E21 shared memory unless you are actively using it. + Also, the shared memory has effective only one setting -- spread all + over the 128K region! */ + if (dev->mem_start == 0) + dev->mem_start = 0xd0000; + +#ifdef notdef + /* These values are unused. The E2100 has a 2K window into the packet + buffer. The window can be set to start on any page boundary. */ + dev->rmem_start = dev->mem_start + TX_PAGES*256; + dev->mem_end = dev->rmem_end = dev->mem_start + 2*1024; +#endif + + printk(", IRQ %d, %s media, memory @ %#lx.\n", dev->irq, + dev->if_port ? "secondary" : "primary", dev->mem_start); + + ei_status.reset_8390 = &e21_reset_8390; + ei_status.block_input = &e21_block_input; + ei_status.block_output = &e21_block_output; + ei_status.get_8390_hdr = &e21_get_8390_hdr; + dev->open = &e21_open; + dev->stop = &e21_close; + NS8390_init(dev, 0); + + return 0; +} + +static int +e21_open(struct device *dev) +{ + short ioaddr = dev->base_addr; + + if (request_irq(dev->irq, ei_interrupt, 0, "e2100", NULL)) { + return EBUSY; + } + irq2dev_map[dev->irq] = dev; + + /* Set the interrupt line and memory base on the hardware. */ + inb(ioaddr + E21_IRQ_LOW); + outb(0, ioaddr + E21_ASIC + (dev->irq & 7)); + inb(ioaddr + E21_IRQ_HIGH); /* High IRQ bit, and if_port. */ + outb(0, ioaddr + E21_ASIC + (dev->irq > 7 ? 1:0) + + (dev->if_port ? E21_ALT_IFPORT : 0)); + inb(ioaddr + E21_MEM_BASE); + outb(0, ioaddr + E21_ASIC + ((dev->mem_start >> 17) & 7)); + + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static void +e21_reset_8390(struct device *dev) +{ + short ioaddr = dev->base_addr; + + outb(0x01, ioaddr); + if (ei_debug > 1) printk("resetting the E2180x3 t=%ld...", jiffies); + ei_status.txing = 0; + + /* Set up the ASIC registers, just in case something changed them. */ + + if (ei_debug > 1) printk("reset done\n"); + return; +} + +/* Grab the 8390 specific header. We put the 2k window so the header page + appears at the start of the shared memory. */ + +static void +e21_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + + short ioaddr = dev->base_addr; + char *shared_mem = (char *)dev->mem_start; + + mem_on(ioaddr, shared_mem, ring_page); + +#ifdef notdef + /* Officially this is what we are doing, but the readl() is faster */ + memcpy_fromio(hdr, shared_mem, sizeof(struct e8390_pkt_hdr)); +#else + ((unsigned int*)hdr)[0] = readl(shared_mem); +#endif + + /* Turn off memory access: we would need to reprogram the window anyway. */ + mem_off(ioaddr); + +} + +/* Block input and output are easy on shared memory ethercards. + The E21xx makes block_input() especially easy by wrapping the top + ring buffer to the bottom automatically. */ +static void +e21_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + short ioaddr = dev->base_addr; + char *shared_mem = (char *)dev->mem_start; + + mem_on(ioaddr, shared_mem, (ring_offset>>8)); + + /* Packet is always in one chunk -- we can copy + cksum. */ + eth_io_copy_and_sum(skb, dev->mem_start + (ring_offset & 0xff), count, 0); + + mem_off(ioaddr); +} + +static void +e21_block_output(struct device *dev, int count, const unsigned char *buf, + const int start_page) +{ + short ioaddr = dev->base_addr; + volatile char *shared_mem = (char *)dev->mem_start; + + /* Set the shared memory window start by doing a read, with the low address + bits specifying the starting page. */ + readb(shared_mem + start_page); + mem_on(ioaddr, shared_mem, start_page); + + memcpy_toio(shared_mem, buf, count); + mem_off(ioaddr); +} + +static int +e21_close(struct device *dev) +{ + short ioaddr = dev->base_addr; + + if (ei_debug > 1) + printk("%s: Shutting down ethercard.\n", dev->name); + + free_irq(dev->irq, NULL); + dev->irq = ei_status.saved_irq; + + /* Shut off the interrupt line and secondary interface. */ + inb(ioaddr + E21_IRQ_LOW); + outb(0, ioaddr + E21_ASIC); + inb(ioaddr + E21_IRQ_HIGH); /* High IRQ bit, and if_port. */ + outb(0, ioaddr + E21_ASIC); + + irq2dev_map[dev->irq] = NULL; + + ei_close(dev); + + /* Double-check that the memory has been turned off, because really + really bad things happen if it isn't. */ + mem_off(ioaddr); + + MOD_DEC_USE_COUNT; + + return 0; +} + +#ifdef HAVE_DEVLIST +struct netdev_entry e21_drv = +{"e21", e21_probe1, E21_IO_EXTENT, e21_probe_list}; +#endif + + +#ifdef MODULE +#define MAX_E21_CARDS 4 /* Max number of E21 cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_E21_CARDS] = { 0, }; +static struct device dev_e21[MAX_E21_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_E21_CARDS] = { 0, }; +static int irq[MAX_E21_CARDS] = { 0, }; +static int mem[MAX_E21_CARDS] = { 0, }; +static int xcvr[MAX_E21_CARDS] = { 0, }; /* choose int. or ext. xcvr */ + +/* This is set up so that only a single autoprobe takes place per call. +ISA device autoprobes on a running machine are not recommended. */ +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_E21_CARDS; this_dev++) { + struct device *dev = &dev_e21[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->mem_start = mem[this_dev]; + dev->mem_end = xcvr[this_dev]; /* low 4bits = xcvr sel. */ + dev->init = e2100_probe; + if (io[this_dev] == 0) { + if (this_dev != 0) break; /* only autoprobe 1st one */ + printk(KERN_NOTICE "e2100.c: Presently autoprobing (not recommended) for a single card.\n"); + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "e2100.c: No E2100 card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_E21_CARDS; this_dev++) { + struct device *dev = &dev_e21[this_dev]; + if (dev->priv != NULL) { + /* NB: e21_close() handles free_irq + irq2dev map */ + kfree(dev->priv); + dev->priv = NULL; + release_region(dev->base_addr, E21_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c e2100.c" + * version-control: t + * tab-width: 4 + * kept-new-versions: 5 + * End: + */ diff --git a/linux/src/drivers/net/eepro.c b/linux/src/drivers/net/eepro.c new file mode 100644 index 00000000..2c3a6b26 --- /dev/null +++ b/linux/src/drivers/net/eepro.c @@ -0,0 +1,1407 @@ +/* eepro.c: Intel EtherExpress Pro/10 device driver for Linux. */ +/* + Written 1994-1998 by Bao C. Ha. + + Copyright (C) 1994-1998 by Bao C. Ha. + + This software may be used and distributed + according to the terms of the GNU Public License, + incorporated herein by reference. + + The author may be reached at bao@hacom.net + or Hacom, 2477 Wrightsboro Rd., Augusta, GA 30904. + + Things remaining to do: + Better record keeping of errors. + Eliminate transmit interrupt to reduce overhead. + Implement "concurrent processing". I won't be doing it! + + Bugs: + + If you have a problem of not detecting the 82595 during a + reboot (warm reset), disable the FLASH memory should fix it. + This is a compatibility hardware problem. + + Versions: + + 0.10c Some cosmetic changes. (9/28/98, BCH) + + 0.10b Should work now with (some) Pro/10+. At least for + me (and my two cards) it does. _No_ guarantee for + function with non-Pro/10+ cards! (don't have any) + (RMC, 9/11/96) + + 0.10 Added support for the Etherexpress Pro/10+. The + IRQ map was changed significantly from the old + pro/10. The new interrupt map was provided by + Rainer M. Canavan (Canavan@Zeus.cs.bonn.edu). + (BCH, 9/3/96) + + 0.09 Fixed a race condition in the transmit algorithm, + which causes crashes under heavy load with fast + pentium computers. The performance should also + improve a bit. The size of RX buffer, and hence + TX buffer, can also be changed via lilo or insmod. + (BCH, 7/31/96) + + 0.08 Implement 32-bit I/O for the 82595TX and 82595FX + based lan cards. Disable full-duplex mode if TPE + is not used. (BCH, 4/8/96) + + 0.07a Fix a stat report which counts every packet as a + heart-beat failure. (BCH, 6/3/95) + + 0.07 Modified to support all other 82595-based lan cards. + The IRQ vector of the EtherExpress Pro will be set + according to the value saved in the EEPROM. For other + cards, I will do autoirq_request() to grab the next + available interrupt vector. (BCH, 3/17/95) + + 0.06a,b Interim released. Minor changes in the comments and + print out format. (BCH, 3/9/95 and 3/14/95) + + 0.06 First stable release that I am comfortable with. (BCH, + 3/2/95) + + 0.05 Complete testing of multicast. (BCH, 2/23/95) + + 0.04 Adding multicast support. (BCH, 2/14/95) + + 0.03 First widely alpha release for public testing. + (BCH, 2/14/95) + +*/ + +static const char *version = + "eepro.c: v0.10c 9/28/98 Bao C. Ha (bao@hacom.net)\n"; + +#include <linux/module.h> + +/* + Sources: + + This driver wouldn't have been written without the availability + of the Crynwr's Lan595 driver source code. It helps me to + familiarize with the 82595 chipset while waiting for the Intel + documentation. I also learned how to detect the 82595 using + the packet driver's technique. + + This driver is written by cutting and pasting the skeleton.c driver + provided by Donald Becker. I also borrowed the EEPROM routine from + Donald Becker's 82586 driver. + + Datasheet for the Intel 82595 (including the TX and FX version). It + provides just enough info that the casual reader might think that it + documents the i82595. + + The User Manual for the 82595. It provides a lot of the missing + information. + +*/ + +#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/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* First, a few definitions that the brave might change. */ + +/* A zero-terminated list of I/O addresses to be probed. */ +static unsigned int eepro_portlist[] = + { 0x300, 0x240, 0x280, 0x2C0, 0x200, 0x320, 0x340, 0x360, 0}; + +/* use 0 for production, 1 for verification, >2 for debug */ + +#ifndef NET_DEBUG +#define NET_DEBUG 1 +#endif + +static unsigned int net_debug = NET_DEBUG; + +/* The number of low I/O ports used by the ethercard. */ + +#define EEPRO_IO_EXTENT 16 + +/* Different 82595 chips */ + +#define LAN595 0 +#define LAN595TX 1 +#define LAN595FX 2 + +/* Information that need to be kept for each board. */ +struct eepro_local { + struct enet_statistics stats; + unsigned rx_start; + unsigned tx_start; /* start of the transmit chain */ + int tx_last; /* pointer to last packet in the transmit chain */ + unsigned tx_end; /* end of the transmit chain (plus 1) */ + int eepro; /* 1 for the EtherExpress Pro/10, + 2 for the EtherExpress Pro/10+, + 0 for other 82595-based lan cards. */ + int version; /* a flag to indicate if this is a TX or FX + version of the 82595 chip. */ + int stepping; +}; + +/* The station (ethernet) address prefix, used for IDing the board. */ + +#define SA_ADDR0 0x00 /* Etherexpress Pro/10 */ +#define SA_ADDR1 0xaa +#define SA_ADDR2 0x00 + +#define SA2_ADDR0 0x00 /* Etherexpress Pro/10+ */ +#define SA2_ADDR1 0xa0 +#define SA2_ADDR2 0xc9 + +#define SA3_ADDR0 0x00 /* more Etherexpress Pro/10+ */ +#define SA3_ADDR1 0xaa +#define SA3_ADDR2 0x00 +#define SA3_ADDR3 0xc9 + +/* Index to functions, as function prototypes. */ + +extern int eepro_probe(struct device *dev); + +static int eepro_probe1(struct device *dev, short ioaddr); +static int eepro_open(struct device *dev); +static int eepro_send_packet(struct sk_buff *skb, struct device *dev); +static void eepro_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void eepro_rx(struct device *dev); +static void eepro_transmit_interrupt(struct device *dev); +static int eepro_close(struct device *dev); +static struct enet_statistics *eepro_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 int eepro_grab_irq(struct device *dev); + +/* + Details of the i82595. + +You will need either the datasheet or the user manual to understand what +is going on here. The 82595 is very different from the 82586, 82593. + +The receive algorithm in eepro_rx() is just an implementation of the +RCV ring structure that the Intel 82595 imposes at the hardware level. +The receive buffer is set at 24K, and the transmit buffer is 8K. I +am assuming that the total buffer memory is 32K, which is true for the +Intel EtherExpress Pro/10. If it is less than that on a generic card, +the driver will be broken. + +The transmit algorithm in the hardware_send_packet() is similar to the +one in the eepro_rx(). The transmit buffer is a ring linked list. +I just queue the next available packet to the end of the list. In my +system, the 82595 is so fast that the list seems to always contain a +single packet. In other systems with faster computers and more congested +network traffics, the ring linked list should improve performance by +allowing up to 8K worth of packets to be queued. + +The sizes of the receive and transmit buffers can now be changed via lilo +or insmod. Lilo uses the appended line "ether=io,irq,debug,rx-buffer,eth0" +where rx-buffer is in KB unit. Modules uses the parameter mem which is +also in KB unit, for example "insmod io=io-address irq=0 mem=rx-buffer." +The receive buffer has to be more than 3K or less than 29K. Otherwise, +it is reset to the default of 24K, and, hence, 8K for the trasnmit +buffer (transmit-buffer = 32K - receive-buffer). + +*/ + +#define RAM_SIZE 0x8000 +#define RCV_HEADER 8 +#define RCV_RAM 0x6000 /* 24KB default for RCV buffer */ +#define RCV_LOWER_LIMIT 0x00 /* 0x0000 */ + +/* #define RCV_UPPER_LIMIT ((RCV_RAM - 2) >> 8) */ /* 0x5ffe */ +#define RCV_UPPER_LIMIT (((rcv_ram) - 2) >> 8) + +/* #define XMT_RAM (RAM_SIZE - RCV_RAM) */ /* 8KB for XMT buffer */ +#define XMT_RAM (RAM_SIZE - (rcv_ram)) /* 8KB for XMT buffer */ + +/* #define XMT_LOWER_LIMIT (RCV_RAM >> 8) */ /* 0x6000 */ +#define XMT_LOWER_LIMIT ((rcv_ram) >> 8) +#define XMT_UPPER_LIMIT ((RAM_SIZE - 2) >> 8) /* 0x7ffe */ +#define XMT_HEADER 8 + +#define RCV_DONE 0x0008 +#define RX_OK 0x2000 +#define RX_ERROR 0x0d81 + +#define TX_DONE_BIT 0x0080 +#define CHAIN_BIT 0x8000 +#define XMT_STATUS 0x02 +#define XMT_CHAIN 0x04 +#define XMT_COUNT 0x06 + +#define BANK0_SELECT 0x00 +#define BANK1_SELECT 0x40 +#define BANK2_SELECT 0x80 + +/* Bank 0 registers */ + +#define COMMAND_REG 0x00 /* Register 0 */ +#define MC_SETUP 0x03 +#define XMT_CMD 0x04 +#define DIAGNOSE_CMD 0x07 +#define RCV_ENABLE_CMD 0x08 +#define RCV_DISABLE_CMD 0x0a +#define STOP_RCV_CMD 0x0b +#define RESET_CMD 0x0e +#define POWER_DOWN_CMD 0x18 +#define RESUME_XMT_CMD 0x1c +#define SEL_RESET_CMD 0x1e +#define STATUS_REG 0x01 /* Register 1 */ +#define RX_INT 0x02 +#define TX_INT 0x04 +#define EXEC_STATUS 0x30 +#define ID_REG 0x02 /* Register 2 */ +#define R_ROBIN_BITS 0xc0 /* round robin counter */ +#define ID_REG_MASK 0x2c +#define ID_REG_SIG 0x24 +#define AUTO_ENABLE 0x10 +#define INT_MASK_REG 0x03 /* Register 3 */ +#define RX_STOP_MASK 0x01 +#define RX_MASK 0x02 +#define TX_MASK 0x04 +#define EXEC_MASK 0x08 +#define ALL_MASK 0x0f +#define IO_32_BIT 0x10 +#define RCV_BAR 0x04 /* The following are word (16-bit) registers */ +#define RCV_STOP 0x06 +#define XMT_BAR 0x0a +#define HOST_ADDRESS_REG 0x0c +#define IO_PORT 0x0e +#define IO_PORT_32_BIT 0x0c + +/* Bank 1 registers */ + +#define REG1 0x01 +#define WORD_WIDTH 0x02 +#define INT_ENABLE 0x80 +#define INT_NO_REG 0x02 +#define RCV_LOWER_LIMIT_REG 0x08 +#define RCV_UPPER_LIMIT_REG 0x09 +#define XMT_LOWER_LIMIT_REG 0x0a +#define XMT_UPPER_LIMIT_REG 0x0b + +/* Bank 2 registers */ + +#define XMT_Chain_Int 0x20 /* Interrupt at the end of the transmit chain */ +#define XMT_Chain_ErrStop 0x40 /* Interrupt at the end of the chain even if there are errors */ +#define RCV_Discard_BadFrame 0x80 /* Throw bad frames away, and continue to receive others */ +#define REG2 0x02 +#define PRMSC_Mode 0x01 +#define Multi_IA 0x20 +#define REG3 0x03 +#define TPE_BIT 0x04 +#define BNC_BIT 0x20 +#define REG13 0x0d +#define FDX 0x00 +#define A_N_ENABLE 0x02 + +#define I_ADD_REG0 0x04 +#define I_ADD_REG1 0x05 +#define I_ADD_REG2 0x06 +#define I_ADD_REG3 0x07 +#define I_ADD_REG4 0x08 +#define I_ADD_REG5 0x09 + +#define EEPROM_REG 0x0a +#define EESK 0x01 +#define EECS 0x02 +#define EEDI 0x04 +#define EEDO 0x08 + +/* Check for a network adaptor of this type, and return '0' if one exists. + + If dev->base_addr == 0, probe all likely locations. + If dev->base_addr == 1, always return failure. + If dev->base_addr == 2, allocate space for the device and return success + (detachable devices only). + + */ + +#ifdef HAVE_DEVLIST + +/* Support for a alternate probe manager, which will eliminate the + boilerplate below. */ + +struct netdev_entry netcard_drv = +{"eepro", eepro_probe1, EEPRO_IO_EXTENT, eepro_portlist}; + +#else + +int +eepro_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return eepro_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; eepro_portlist[i]; i++) { + int ioaddr = eepro_portlist[i]; + if (check_region(ioaddr, EEPRO_IO_EXTENT)) + continue; + + if (eepro_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +/* This is the real probe routine. Linux has a history of friendly device + probes on the ISA bus. A good device probes avoids doing writes, and + verifies that the correct device exists and functions. */ + +int +eepro_probe1(struct device *dev, short ioaddr) +{ + unsigned short station_addr[6], id, counter; + int i; + int eepro; + const char *ifmap[] = {"AUI", "10Base2", "10BaseT"}; + enum iftype { AUI=0, BNC=1, TPE=2 }; + + /* Now, we are going to check for the signature of the + ID_REG (register 2 of bank 0) */ + if (((id=inb(ioaddr + ID_REG)) & ID_REG_MASK) == ID_REG_SIG) { + + /* We seem to have the 82595 signature, let's + play with its counter (last 2 bits of + register 2 of bank 0) to be sure. */ + + counter = (id & R_ROBIN_BITS); + if (((id=inb(ioaddr+ID_REG)) & R_ROBIN_BITS) == + (counter + 0x40)) { + + /* Yes, the 82595 has been found */ + + /* Now, get the ethernet hardware address from + the EEPROM */ + + station_addr[0] = read_eeprom(ioaddr, 2); + station_addr[1] = read_eeprom(ioaddr, 3); + station_addr[2] = read_eeprom(ioaddr, 4); + + /* Check the station address for the manufacturer's code */ + + if ((station_addr[2] == 0x00aa) && (station_addr[1]!= 0x00c9)) { + eepro = 1; + printk("%s: Intel EtherExpress Pro/10 ISA at %#x,", + dev->name, ioaddr); + } else + if ( (station_addr[2] == 0x00a0) + || ((station_addr[2] == 0x00aa) && (station_addr[1] == 0x00c9) )) { + eepro = 2; + printk("%s: Intel EtherExpress Pro/10+ ISA\n at %#x,", + dev->name, ioaddr); + } + else { + eepro = 0; + printk("%s: Intel 82595-based lan card at %#x,", + dev->name, ioaddr); + } + + /* Fill in the 'dev' fields. */ + dev->base_addr = ioaddr; + + for (i=0; i < 6; i++) { + dev->dev_addr[i] = ((unsigned char *) station_addr)[5-i]; + printk("%c%02x", i ? ':' : ' ', dev->dev_addr[i]); + } + + if ((dev->mem_end & 0x3f) < 3 || /* RX buffer must be more than 3K */ + (dev->mem_end & 0x3f) > 29) /* and less than 29K */ + dev->mem_end = RCV_RAM; /* or it will be set to 24K */ + else dev->mem_end = 1024*dev->mem_end; /* Maybe I should shift << 10 */ + + /* From now on, dev->mem_end contains the actual size of rx buffer */ + + if (net_debug > 3) + printk(", %dK RCV buffer", (int)(dev->mem_end)/1024); + + outb(BANK2_SELECT, ioaddr); /* be CAREFUL, BANK 2 now */ + id = inb(ioaddr + REG3); + if (id & TPE_BIT) + dev->if_port = TPE; + else dev->if_port = BNC; + + if (net_debug>3) + printk("id: %x\n", id); + + if (dev->irq < 2 && eepro) { + i = read_eeprom(ioaddr, 1); + if (eepro == 1) + switch (i & 0x07) { + case 0: dev->irq = 9; break; + case 1: dev->irq = 3; break; + case 2: dev->irq = 5; break; + case 3: dev->irq = 10; break; + case 4: dev->irq = 11; break; + default: /* should never get here !!!!! */ + printk(" illegal interrupt vector stored in EEPROM.\n"); + return ENODEV; + } + else switch (i & 0x07) { + case 0: dev->irq = 3; break; + case 1: dev->irq = 4; break; + case 2: dev->irq = 5; break; + case 3: dev->irq = 7; break; + case 4: dev->irq = 9; break; + case 5: dev->irq = 10; break; + case 6: dev->irq = 11; break; + case 7: dev->irq = 12; break; + } + } + else if (dev->irq == 2) + dev->irq = 9; + + if (dev->irq > 2) { + printk(", IRQ %d, %s.\n", dev->irq, + ifmap[dev->if_port]); + if (request_irq(dev->irq, &eepro_interrupt, 0, "eepro", NULL)) { + printk("%s: unable to get IRQ %d.\n", dev->name, dev->irq); + return -EAGAIN; + } + } + else printk(", %s.\n", ifmap[dev->if_port]); + + if ((dev->mem_start & 0xf) > 0) /* I don't know if this is */ + net_debug = dev->mem_start & 7; /* still useful or not */ + + if (net_debug > 3) { + i = read_eeprom(ioaddr, 5); + if (i & 0x2000) /* bit 13 of EEPROM word 5 */ + printk("%s: Concurrent Processing is enabled but not used!\n", + dev->name); + } + + if (net_debug) + printk(version); + + /* Grab the region so we can find another board if autoIRQ fails. */ + request_region(ioaddr, EEPRO_IO_EXTENT, "eepro"); + + /* Initialize the device structure */ + dev->priv = kmalloc(sizeof(struct eepro_local), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + memset(dev->priv, 0, sizeof(struct eepro_local)); + + dev->open = eepro_open; + dev->stop = eepro_close; + dev->hard_start_xmit = eepro_send_packet; + dev->get_stats = eepro_get_stats; + dev->set_multicast_list = &set_multicast_list; + + /* Fill in the fields of the device structure with + ethernet generic values */ + + ether_setup(dev); + + outb(RESET_CMD, ioaddr); /* RESET the 82595 */ + + return 0; + } + else return ENODEV; + } + else if (net_debug > 3) + printk ("EtherExpress Pro probed failed!\n"); + return ENODEV; +} + +/* Open/initialize the board. This is called (in the current kernel) + sometime after booting when the 'ifconfig' program is run. + + This routine should set everything up anew at each open, even + registers that "should" only need to be set once at boot, so that + there is non-reboot way to recover if something goes wrong. + */ + +static char irqrmap[] = {-1,-1,0,1,-1,2,-1,-1,-1,0,3,4,-1,-1,-1,-1}; +static char irqrmap2[] = {-1,-1,4,0,1,2,-1,3,-1,4,5,6,7,-1,-1,-1}; + +static int +eepro_grab_irq(struct device *dev) +{ + int irqlist[] = { 3, 4, 5, 7, 9, 10, 11, 12 }; + int *irqp = irqlist, temp_reg, ioaddr = dev->base_addr; + + outb(BANK1_SELECT, ioaddr); /* be CAREFUL, BANK 1 now */ + + /* Enable the interrupt line. */ + temp_reg = inb(ioaddr + REG1); + outb(temp_reg | INT_ENABLE, ioaddr + REG1); + + outb(BANK0_SELECT, ioaddr); /* be CAREFUL, BANK 0 now */ + + /* clear all interrupts */ + outb(ALL_MASK, ioaddr + STATUS_REG); + + /* Let EXEC event to interrupt */ + outb(ALL_MASK & ~(EXEC_MASK), ioaddr + INT_MASK_REG); + + do { + outb(BANK1_SELECT, ioaddr); /* be CAREFUL, BANK 1 now */ + temp_reg = inb(ioaddr + INT_NO_REG); + outb((temp_reg & 0xf8) | irqrmap[*irqp], ioaddr + INT_NO_REG); + outb(BANK0_SELECT, ioaddr); /* Switch back to Bank 0 */ + if (request_irq (*irqp, NULL, 0, "bogus", NULL) != EBUSY) { + /* Twinkle the interrupt, and check if it's seen */ + autoirq_setup(0); + outb(DIAGNOSE_CMD, ioaddr); /* RESET the 82595 */ + + if (*irqp == autoirq_report(2) && /* It's a good IRQ line */ + (request_irq(dev->irq = *irqp, &eepro_interrupt, 0, "eepro", NULL) == 0)) + break; + /* clear all interrupts */ + outb(ALL_MASK, ioaddr + STATUS_REG); + } + } while (*++irqp); + + outb(BANK1_SELECT, ioaddr); /* Switch back to Bank 1 */ + + /* Disable the physical interrupt line. */ + temp_reg = inb(ioaddr + REG1); + outb(temp_reg & 0x7f, ioaddr + REG1); + outb(BANK0_SELECT, ioaddr); /* Switch back to Bank 0 */ + + /* Mask all the interrupts. */ + outb(ALL_MASK, ioaddr + INT_MASK_REG); + + /* clear all interrupts */ + outb(ALL_MASK, ioaddr + STATUS_REG); + + return dev->irq; +} + +static int +eepro_open(struct device *dev) +{ + unsigned short temp_reg, old8, old9; + int i, ioaddr = dev->base_addr, rcv_ram = dev->mem_end; + struct eepro_local *lp = (struct eepro_local *)dev->priv; + + if (net_debug > 3) + printk("eepro: entering eepro_open routine.\n"); + + if ((dev->dev_addr[0] == SA_ADDR0 && + dev->dev_addr[1] == SA_ADDR1 && + dev->dev_addr[2] == SA_ADDR2)&& + (dev->dev_addr[3] != SA3_ADDR3)) + { + lp->eepro = 1; + if (net_debug > 3) printk("p->eepro = 1;\n"); + } /* Yes, an Intel EtherExpress Pro/10 */ + + else if ((dev->dev_addr[0] == SA2_ADDR0 && + dev->dev_addr[1] == SA2_ADDR1 && + dev->dev_addr[2] == SA2_ADDR2)|| + (dev->dev_addr[0] == SA3_ADDR0 && + dev->dev_addr[1] == SA3_ADDR1 && + dev->dev_addr[2] == SA3_ADDR2 && + dev->dev_addr[3] == SA3_ADDR3)) + { + lp->eepro = 2; /* Yes, an Intel EtherExpress Pro/10+ */ + if (net_debug > 3) printk("p->eepro = 2;\n"); + } + + else lp->eepro = 0; /* No, it is a generic 82585 lan card */ + + /* Get the interrupt vector for the 82595 */ + if (dev->irq < 2 && eepro_grab_irq(dev) == 0) { + printk("%s: unable to get IRQ %d.\n", dev->name, dev->irq); + return -EAGAIN; + } + + if (irq2dev_map[dev->irq] != 0 + || (irq2dev_map[dev->irq] = dev) == 0) + return -EAGAIN; + + /* Initialize the 82595. */ + outb(BANK2_SELECT, ioaddr); /* be CAREFUL, BANK 2 now */ + temp_reg = inb(ioaddr + EEPROM_REG); + lp->stepping = temp_reg >> 5; /* Get the stepping number of the 595 */ + + if (net_debug > 3) + printk("The stepping of the 82595 is %d\n", lp->stepping); + if (temp_reg & 0x10) /* Check the TurnOff Enable bit */ + outb(temp_reg & 0xef, ioaddr + EEPROM_REG); + for (i=0; i < 6; i++) + outb(dev->dev_addr[i] , ioaddr + I_ADD_REG0 + i); + + temp_reg = inb(ioaddr + REG1); /* Setup Transmit Chaining */ + outb(temp_reg | XMT_Chain_Int | XMT_Chain_ErrStop /* and discard bad RCV frames */ + | RCV_Discard_BadFrame, ioaddr + REG1); + temp_reg = inb(ioaddr + REG2); /* Match broadcast */ + outb(temp_reg | 0x14, ioaddr + REG2); + temp_reg = inb(ioaddr + REG3); + outb(temp_reg & 0x3f, ioaddr + REG3); /* clear test mode */ + + /* Set the receiving mode */ + outb(BANK1_SELECT, ioaddr); /* be CAREFUL, BANK 1 now */ + + /* Set the interrupt vector */ + temp_reg = inb(ioaddr + INT_NO_REG); + + if (lp->eepro == 2) + outb((temp_reg & 0xf8) | irqrmap2[dev->irq], ioaddr + INT_NO_REG); + else outb((temp_reg & 0xf8) | irqrmap[dev->irq], ioaddr + INT_NO_REG); + + temp_reg = inb(ioaddr + INT_NO_REG); + + if (lp->eepro == 2) + outb((temp_reg & 0xf0) | irqrmap2[dev->irq] | 0x08,ioaddr+INT_NO_REG); + else outb((temp_reg & 0xf8) | irqrmap[dev->irq], ioaddr + INT_NO_REG); + + if (net_debug > 3) + printk("eepro_open: content of INT Reg is %x\n", temp_reg); + + + /* Initialize the RCV and XMT upper and lower limits */ + outb(RCV_LOWER_LIMIT, ioaddr + RCV_LOWER_LIMIT_REG); + outb(RCV_UPPER_LIMIT, ioaddr + RCV_UPPER_LIMIT_REG); + outb(XMT_LOWER_LIMIT, ioaddr + XMT_LOWER_LIMIT_REG); + outb(XMT_UPPER_LIMIT, ioaddr + XMT_UPPER_LIMIT_REG); + + /* Enable the interrupt line. */ + temp_reg = inb(ioaddr + REG1); + outb(temp_reg | INT_ENABLE, ioaddr + REG1); + outb(BANK0_SELECT, ioaddr); /* Switch back to Bank 0 */ + + /* Let RX and TX events to interrupt */ + outb(ALL_MASK & ~(RX_MASK | TX_MASK), ioaddr + INT_MASK_REG); + + /* clear all interrupts */ + outb(ALL_MASK, ioaddr + STATUS_REG); + + /* Initialize RCV */ + outw(RCV_LOWER_LIMIT << 8, ioaddr + RCV_BAR); + lp->rx_start = (RCV_LOWER_LIMIT << 8) ; + outw((RCV_UPPER_LIMIT << 8) | 0xfe, ioaddr + RCV_STOP); + + /* Initialize XMT */ + outw(XMT_LOWER_LIMIT << 8, ioaddr + XMT_BAR); + + /* Check for the i82595TX and i82595FX */ + old8 = inb(ioaddr + 8); + outb(~old8, ioaddr + 8); + + if ((temp_reg = inb(ioaddr + 8)) == old8) { + if (net_debug > 3) + printk("i82595 detected!\n"); + lp->version = LAN595; + } + else { + lp->version = LAN595TX; + outb(old8, ioaddr + 8); + old9 = inb(ioaddr + 9); + outb(~old9, ioaddr + 9); + + if (((temp_reg = inb(ioaddr + 9)) == ( (~old9)&0xff) )) { + enum iftype { AUI=0, BNC=1, TPE=2 }; + + if (net_debug > 3) { + printk("temp_reg: %#x ~old9: %#x\n",temp_reg, ~old9); + printk("i82595FX detected!\n"); + } + + lp->version = LAN595FX; + outb(old9, ioaddr + 9); + + if (dev->if_port != TPE) { /* Hopefully, this will fix the + problem of using Pentiums and + pro/10 w/ BNC. */ + outb(BANK2_SELECT, ioaddr); /* be CAREFUL, BANK 2 now */ + temp_reg = inb(ioaddr + REG13); + + /* disable the full duplex mode since it is not + applicable with the 10Base2 cable. */ + outb(temp_reg & ~(FDX | A_N_ENABLE), REG13); + outb(BANK0_SELECT, ioaddr); /* be CAREFUL, BANK 0 now */ + } + } + else if (net_debug > 3) { + printk("temp_reg: %#x ~old9: %#x\n",temp_reg,((~old9)&0xff)); + printk("i82595TX detected!\n"); + } + } + + outb(SEL_RESET_CMD, ioaddr); + + /* We are supposed to wait for 2 us after a SEL_RESET */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + + lp->tx_start = lp->tx_end = XMT_LOWER_LIMIT << 8; /* or = RCV_RAM */ + lp->tx_last = 0; + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + if (net_debug > 3) + printk("eepro: exiting eepro_open routine.\n"); + + outb(RCV_ENABLE_CMD, ioaddr); + MOD_INC_USE_COUNT; + + return 0; +} + +static int +eepro_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct eepro_local *lp = (struct eepro_local *)dev->priv; + int ioaddr = dev->base_addr; + int rcv_ram = dev->mem_end; + + if (net_debug > 5) + printk("eepro: entering eepro_send_packet routine.\n"); + + 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 < 40) + return 1; + + if (net_debug > 1) + printk("%s: transmit timed out, %s?\n", dev->name, + "network cable problem"); + + lp->stats.tx_errors++; + + /* Try to restart the adaptor. */ + outb(SEL_RESET_CMD, ioaddr); + + /* We are supposed to wait for 2 us after a SEL_RESET */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + + /* Do I also need to flush the transmit buffers here? YES? */ + lp->tx_start = lp->tx_end = rcv_ram; + lp->tx_last = 0; + + dev->tbusy=0; + dev->trans_start = jiffies; + outb(RCV_ENABLE_CMD, ioaddr); + } + + /* 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) { + dev_tint(dev); + 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; + hardware_send_packet(dev, buf, length); + dev->trans_start = jiffies; + } + + dev_kfree_skb (skb, FREE_WRITE); + + /* You might need to clean up and record Tx statistics here. */ + /* lp->stats.tx_aborted_errors++; */ + + if (net_debug > 5) + printk("eepro: exiting eepro_send_packet routine.\n"); + + return 0; +} + +/* The typical workload of the driver: + Handle the network interface interrupts. */ + +static void +eepro_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + int ioaddr, status, boguscount = 20; + + if (net_debug > 5) + printk("eepro: entering eepro_interrupt routine.\n"); + + if (dev == NULL) { + printk ("eepro_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + dev->interrupt = 1; + + ioaddr = dev->base_addr; + + do { + status = inb(ioaddr + STATUS_REG); + + if (status & RX_INT) { + if (net_debug > 4) + printk("eepro: packet received interrupt.\n"); + /* Acknowledge the RX_INT */ + outb(RX_INT, ioaddr + STATUS_REG); + /* Get the received packets */ + eepro_rx(dev); + } + else if (status & TX_INT) { + if (net_debug > 4) + printk("eepro: packet transmit interrupt.\n"); + /* Acknowledge the TX_INT */ + outb(TX_INT, ioaddr + STATUS_REG); + /* Process the status of transmitted packets */ + eepro_transmit_interrupt(dev); + } + + } while ((boguscount-- > 0) && (status & 0x06)); + + dev->interrupt = 0; + + if (net_debug > 5) + printk("eepro: exiting eepro_interrupt routine.\n"); + + return; +} + +static int +eepro_close(struct device *dev) +{ + struct eepro_local *lp = (struct eepro_local *)dev->priv; + int ioaddr = dev->base_addr; + int rcv_ram = dev->mem_end; + short temp_reg; + + dev->tbusy = 1; + dev->start = 0; + + outb(BANK1_SELECT, ioaddr); /* Switch back to Bank 1 */ + + /* Disable the physical interrupt line. */ + temp_reg = inb(ioaddr + REG1); + outb(temp_reg & 0x7f, ioaddr + REG1); + outb(BANK0_SELECT, ioaddr); /* Switch back to Bank 0 */ + + /* Flush the Tx and disable Rx. */ + outb(STOP_RCV_CMD, ioaddr); + + lp->tx_start = lp->tx_end = rcv_ram ; + lp->tx_last = 0; + + /* Mask all the interrupts. */ + outb(ALL_MASK, ioaddr + INT_MASK_REG); + + /* clear all interrupts */ + outb(ALL_MASK, ioaddr + STATUS_REG); + + /* Reset the 82595 */ + outb(RESET_CMD, ioaddr); + + /* release the interrupt */ + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = 0; + + /* Update the statistics here. What statistics? */ + /* We are supposed to wait for 200 us after a RESET */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; /* May not be enough? */ + MOD_DEC_USE_COUNT; + + return 0; +} + +/* Get the current statistics. This may be called with the card open or + closed. */ +static struct enet_statistics * +eepro_get_stats(struct device *dev) +{ + struct eepro_local *lp = (struct eepro_local *)dev->priv; + return &lp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + */ + +static void +set_multicast_list(struct device *dev) +{ + struct eepro_local *lp = (struct eepro_local *)dev->priv; + short ioaddr = dev->base_addr; + unsigned short mode; + struct dev_mc_list *dmi=dev->mc_list; + + if (dev->flags&(IFF_ALLMULTI|IFF_PROMISC) || dev->mc_count > 63) + { + /* + * We must make the kernel realise we had to move + * into promisc mode or we start all out war on + * the cable. If it was a promisc request the + * flag is already set. If not we assert it. + */ + dev->flags|=IFF_PROMISC; + outb(BANK2_SELECT, ioaddr); /* be CAREFUL, BANK 2 now */ + mode = inb(ioaddr + REG2); + outb(mode | PRMSC_Mode, ioaddr + REG2); + mode = inb(ioaddr + REG3); + outb(mode, ioaddr + REG3); /* writing reg. 3 to complete the update */ + outb(BANK0_SELECT, ioaddr); /* Return to BANK 0 now */ + printk("%s: promiscuous mode enabled.\n", dev->name); + } + + else if (dev->mc_count==0 ) + { + outb(BANK2_SELECT, ioaddr); /* be CAREFUL, BANK 2 now */ + mode = inb(ioaddr + REG2); + outb(mode & 0xd6, ioaddr + REG2); /* Turn off Multi-IA and PRMSC_Mode bits */ + mode = inb(ioaddr + REG3); + outb(mode, ioaddr + REG3); /* writing reg. 3 to complete the update */ + outb(BANK0_SELECT, ioaddr); /* Return to BANK 0 now */ + } + + else + { + unsigned short status, *eaddrs; + int i, boguscount = 0; + + /* Disable RX and TX interrupts. Necessary to avoid + corruption of the HOST_ADDRESS_REG by interrupt + service routines. */ + outb(ALL_MASK, ioaddr + INT_MASK_REG); + outb(BANK2_SELECT, ioaddr); /* be CAREFUL, BANK 2 now */ + mode = inb(ioaddr + REG2); + outb(mode | Multi_IA, ioaddr + REG2); + mode = inb(ioaddr + REG3); + outb(mode, ioaddr + REG3); /* writing reg. 3 to complete the update */ + outb(BANK0_SELECT, ioaddr); /* Return to BANK 0 now */ + outw(lp->tx_end, ioaddr + HOST_ADDRESS_REG); + outw(MC_SETUP, ioaddr + IO_PORT); + outw(0, ioaddr + IO_PORT); + outw(0, ioaddr + IO_PORT); + outw(6*(dev->mc_count + 1), ioaddr + IO_PORT); + + for (i = 0; i < dev->mc_count; i++) + { + eaddrs=(unsigned short *)dmi->dmi_addr; + dmi=dmi->next; + outw(*eaddrs++, ioaddr + IO_PORT); + outw(*eaddrs++, ioaddr + IO_PORT); + outw(*eaddrs++, ioaddr + IO_PORT); + } + + eaddrs = (unsigned short *) dev->dev_addr; + outw(eaddrs[0], ioaddr + IO_PORT); + outw(eaddrs[1], ioaddr + IO_PORT); + outw(eaddrs[2], ioaddr + IO_PORT); + outw(lp->tx_end, ioaddr + XMT_BAR); + outb(MC_SETUP, ioaddr); + + /* Update the transmit queue */ + i = lp->tx_end + XMT_HEADER + 6*(dev->mc_count + 1); + + if (lp->tx_start != lp->tx_end) + { + /* update the next address and the chain bit in the + last packet */ + outw(lp->tx_last + XMT_CHAIN, ioaddr + HOST_ADDRESS_REG); + outw(i, ioaddr + IO_PORT); + outw(lp->tx_last + XMT_COUNT, ioaddr + HOST_ADDRESS_REG); + status = inw(ioaddr + IO_PORT); + outw(status | CHAIN_BIT, ioaddr + IO_PORT); + lp->tx_end = i ; + } + else { + lp->tx_start = lp->tx_end = i ; + } + + /* Acknowledge that the MC setup is done */ + do { /* We should be doing this in the eepro_interrupt()! */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + + if (inb(ioaddr + STATUS_REG) & 0x08) + { + i = inb(ioaddr); + outb(0x08, ioaddr + STATUS_REG); + + if (i & 0x20) { /* command ABORTed */ + printk("%s: multicast setup failed.\n", + dev->name); + break; + } else if ((i & 0x0f) == 0x03) { /* MC-Done */ + printk("%s: set Rx mode to %d addresses.\n", + dev->name, dev->mc_count); + break; + } + } + } while (++boguscount < 100); + + /* Re-enable RX and TX interrupts */ + outb(ALL_MASK & ~(RX_MASK | TX_MASK), ioaddr + INT_MASK_REG); + + } + outb(RCV_ENABLE_CMD, ioaddr); +} + +/* The horrible routine to read a word from the serial EEPROM. */ +/* IMPORTANT - the 82595 will be set to Bank 0 after the eeprom is read */ +/* The delay between EEPROM clock transitions. */ + +#define eeprom_delay() { int _i = 40; while (--_i > 0) { __SLOW_DOWN_IO; }} +#define EE_READ_CMD (6 << 6) + +int +read_eeprom(int ioaddr, int location) +{ + int i; + unsigned short retval = 0; + short ee_addr = ioaddr + EEPROM_REG; + int read_cmd = location | EE_READ_CMD; + short ctrl_val = EECS ; + + outb(BANK2_SELECT, ioaddr); + 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 | EEDI + : ctrl_val; + outb(outval, ee_addr); + outb(outval | EESK, ee_addr); /* EEPROM clock tick. */ + eeprom_delay(); + outb(outval, ee_addr); /* Finish EEPROM a clock tick. */ + eeprom_delay(); + } + outb(ctrl_val, ee_addr); + + for (i = 16; i > 0; i--) { + outb(ctrl_val | EESK, ee_addr); eeprom_delay(); + retval = (retval << 1) | ((inb(ee_addr) & EEDO) ? 1 : 0); + outb(ctrl_val, ee_addr); eeprom_delay(); + } + /* Terminate the EEPROM access. */ + ctrl_val &= ~EECS; + outb(ctrl_val | EESK, ee_addr); + eeprom_delay(); + outb(ctrl_val, ee_addr); + eeprom_delay(); + outb(BANK0_SELECT, ioaddr); + return retval; +} + +static void +hardware_send_packet(struct device *dev, void *buf, short length) +{ + struct eepro_local *lp = (struct eepro_local *)dev->priv; + short ioaddr = dev->base_addr; + int rcv_ram = dev->mem_end; + unsigned status, tx_available, last, end, boguscount = 100; + + if (net_debug > 5) + printk("eepro: entering hardware_send_packet routine.\n"); + + while (boguscount-- > 0) { + + /* Disable RX and TX interrupts. Necessary to avoid + corruption of the HOST_ADDRESS_REG by interrupt + service routines. */ + outb(ALL_MASK, ioaddr + INT_MASK_REG); + + if (dev->interrupt == 1) { + /* Enable RX and TX interrupts */ + outb(ALL_MASK & ~(RX_MASK | TX_MASK), ioaddr + INT_MASK_REG); + continue; + } + + /* determine how much of the transmit buffer space is available */ + if (lp->tx_end > lp->tx_start) + tx_available = XMT_RAM - (lp->tx_end - lp->tx_start); + else if (lp->tx_end < lp->tx_start) + tx_available = lp->tx_start - lp->tx_end; + else tx_available = XMT_RAM; + + if (((((length + 3) >> 1) << 1) + 2*XMT_HEADER) + >= tx_available) /* No space available ??? */ + { + eepro_transmit_interrupt(dev); /* Clean up the transmiting queue */ + /* Enable RX and TX interrupts */ + outb(ALL_MASK & ~(RX_MASK | TX_MASK), ioaddr + INT_MASK_REG); + continue; + } + + last = lp->tx_end; + end = last + (((length + 3) >> 1) << 1) + XMT_HEADER; + if (end >= RAM_SIZE) { /* the transmit buffer is wrapped around */ + + if ((RAM_SIZE - last) <= XMT_HEADER) { + /* Arrrr!!!, must keep the xmt header together, + several days were lost to chase this one down. */ + last = rcv_ram; + end = last + (((length + 3) >> 1) << 1) + XMT_HEADER; + } + + else end = rcv_ram + (end - RAM_SIZE); + } + + outw(last, ioaddr + HOST_ADDRESS_REG); + outw(XMT_CMD, ioaddr + IO_PORT); + outw(0, ioaddr + IO_PORT); + outw(end, ioaddr + IO_PORT); + outw(length, ioaddr + IO_PORT); + + if (lp->version == LAN595) + outsw(ioaddr + IO_PORT, buf, (length + 3) >> 1); + + else { /* LAN595TX or LAN595FX, capable of 32-bit I/O processing */ + unsigned short temp = inb(ioaddr + INT_MASK_REG); + outb(temp | IO_32_BIT, ioaddr + INT_MASK_REG); + outsl(ioaddr + IO_PORT_32_BIT, buf, (length + 3) >> 2); + outb(temp & ~(IO_32_BIT), ioaddr + INT_MASK_REG); + } + + /* A dummy read to flush the DRAM write pipeline */ + status = inw(ioaddr + IO_PORT); + + if (lp->tx_start == lp->tx_end) { + outw(last, ioaddr + XMT_BAR); + outb(XMT_CMD, ioaddr); + lp->tx_start = last; /* I don't like to change tx_start here */ + } + else { + /* update the next address and the chain bit in the + last packet */ + + if (lp->tx_end != last) { + outw(lp->tx_last + XMT_CHAIN, ioaddr + HOST_ADDRESS_REG); + outw(last, ioaddr + IO_PORT); + } + + outw(lp->tx_last + XMT_COUNT, ioaddr + HOST_ADDRESS_REG); + status = inw(ioaddr + IO_PORT); + outw(status | CHAIN_BIT, ioaddr + IO_PORT); + + /* Continue the transmit command */ + outb(RESUME_XMT_CMD, ioaddr); + } + lp->tx_last = last; + lp->tx_end = end; + + /* Enable RX and TX interrupts */ + outb(ALL_MASK & ~(RX_MASK | TX_MASK), ioaddr + INT_MASK_REG); + + if (dev->tbusy) { + dev->tbusy = 0; + } + + if (net_debug > 5) + printk("eepro: exiting hardware_send_packet routine.\n"); + + return; + } + dev->tbusy = 1; + + if (net_debug > 5) + printk("eepro: exiting hardware_send_packet routine.\n"); +} + +static void +eepro_rx(struct device *dev) +{ + struct eepro_local *lp = (struct eepro_local *)dev->priv; + short ioaddr = dev->base_addr, rcv_ram = dev->mem_end; + short boguscount = 20; + short rcv_car = lp->rx_start; + unsigned rcv_event, rcv_status, rcv_next_frame, rcv_size; + + if (net_debug > 5) + printk("eepro: entering eepro_rx routine.\n"); + + /* Set the read pointer to the start of the RCV */ + outw(rcv_car, ioaddr + HOST_ADDRESS_REG); + + rcv_event = inw(ioaddr + IO_PORT); + while (rcv_event == RCV_DONE) { + + rcv_status = inw(ioaddr + IO_PORT); + rcv_next_frame = inw(ioaddr + IO_PORT); + rcv_size = inw(ioaddr + IO_PORT); + + if ((rcv_status & (RX_OK | RX_ERROR)) == RX_OK) { + + /* Malloc up new buffer. */ + struct sk_buff *skb; + rcv_size &= 0x3fff; + skb = dev_alloc_skb(rcv_size+5); + + if (skb == NULL) { + printk("%s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + break; + } + + skb->dev = dev; + skb_reserve(skb,2); + + if (lp->version == LAN595) + insw(ioaddr+IO_PORT, skb_put(skb,rcv_size), (rcv_size + 3) >> 1); + + else { /* LAN595TX or LAN595FX, capable of 32-bit I/O processing */ + unsigned short temp = inb(ioaddr + INT_MASK_REG); + outb(temp | IO_32_BIT, ioaddr + INT_MASK_REG); + insl(ioaddr+IO_PORT_32_BIT, skb_put(skb,rcv_size), (rcv_size + 3) >> 2); + outb(temp & ~(IO_32_BIT), ioaddr + INT_MASK_REG); + } + + skb->protocol = eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + + else { /* Not sure will ever reach here, + I set the 595 to discard bad received frames */ + lp->stats.rx_errors++; + + if (rcv_status & 0x0100) + lp->stats.rx_over_errors++; + + else if (rcv_status & 0x0400) + lp->stats.rx_frame_errors++; + + else if (rcv_status & 0x0800) + lp->stats.rx_crc_errors++; + + printk("%s: event = %#x, status = %#x, next = %#x, size = %#x\n", + dev->name, rcv_event, rcv_status, rcv_next_frame, rcv_size); + } + + if (rcv_status & 0x1000) + lp->stats.rx_length_errors++; + + if (--boguscount == 0) + break; + + rcv_car = lp->rx_start + RCV_HEADER + rcv_size; + lp->rx_start = rcv_next_frame; + outw(rcv_next_frame, ioaddr + HOST_ADDRESS_REG); + rcv_event = inw(ioaddr + IO_PORT); + } + if (rcv_car == 0) + rcv_car = (RCV_UPPER_LIMIT << 8) | 0xff; + + outw(rcv_car - 1, ioaddr + RCV_STOP); + + if (net_debug > 5) + printk("eepro: exiting eepro_rx routine.\n"); +} + +static void +eepro_transmit_interrupt(struct device *dev) +{ + struct eepro_local *lp = (struct eepro_local *)dev->priv; + short ioaddr = dev->base_addr; + short boguscount = 20; + short xmt_status; + + while (lp->tx_start != lp->tx_end) { + + outw(lp->tx_start, ioaddr + HOST_ADDRESS_REG); + xmt_status = inw(ioaddr+IO_PORT); + + if ((xmt_status & TX_DONE_BIT) == 0) break; + + xmt_status = inw(ioaddr+IO_PORT); + lp->tx_start = inw(ioaddr+IO_PORT); + dev->tbusy = 0; + mark_bh(NET_BH); + + if (xmt_status & 0x2000) + lp->stats.tx_packets++; + else { + lp->stats.tx_errors++; + if (xmt_status & 0x0400) + lp->stats.tx_carrier_errors++; + printk("%s: XMT status = %#x\n", + dev->name, xmt_status); + } + + if (xmt_status & 0x000f) { + lp->stats.collisions += (xmt_status & 0x000f); + } + + if ((xmt_status & 0x0040) == 0x0) { + lp->stats.tx_heartbeat_errors++; + } + + if (--boguscount == 0) + break; + } +} + +#ifdef MODULE + +static char devicename[9] = { 0, }; +static struct device dev_eepro = { + devicename, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, eepro_probe }; +static int io = 0x200; +static int irq = 0; +static int mem = (RCV_RAM/1024); /* Size of the rx buffer in KB */ + +int +init_module(void) +{ + if (io == 0) + printk("eepro: You should not use auto-probing with insmod!\n"); + + dev_eepro.base_addr = io; + dev_eepro.irq = irq; + dev_eepro.mem_end = mem; + + if (register_netdev(&dev_eepro) != 0) + return -EIO; + + return 0; +} + +void +cleanup_module(void) +{ + unregister_netdev(&dev_eepro); + + kfree_s(dev_eepro.priv,sizeof(struct eepro_local)); + dev_eepro.priv=NULL; + + /* If we don't do this, we can't re-insmod it later. */ + release_region(dev_eepro.base_addr, EEPRO_IO_EXTENT); +} +#endif /* MODULE */ diff --git a/linux/src/drivers/net/eepro100.c b/linux/src/drivers/net/eepro100.c new file mode 100644 index 00000000..4e6db459 --- /dev/null +++ b/linux/src/drivers/net/eepro100.c @@ -0,0 +1,1582 @@ +/* drivers/net/eepro100.c: An Intel i82557 Ethernet driver for Linux. */ +/* + NOTICE: this version tested with kernels 1.3.72 and later only! + Written 1996-1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + This driver is for the Intel EtherExpress Pro 100B boards. + It should work with other i82557 boards (if any others exist). + To use a built-in driver, install as drivers/net/eepro100.c. + To use as a module, use the compile-command at the end of the file. + + The author may be reached as becker@CESDIS.usra.edu, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, NASA Goddard Space Flight Center, Greenbelt MD 20771 + For updates see + <base href="http://cesdis.gsfc.nasa.gov/linux/drivers/eepro100.html"> +*/ + +static const char *version = +"eepro100.c:v0.99B 4/7/98 Donald Becker linux-eepro100@cesdis.gsfc.nasa.gov\n"; + +/* A few user-configurable values that apply to all boards. + First set are undocumented and spelled per Intel recommendations. */ + +static int congenb = 0; /* Enable congestion control in the DP83840. */ +static int txfifo = 8; /* Tx FIFO threshold in 4 byte units, 0-15 */ +static int rxfifo = 8; /* Rx FIFO threshold, default 32 bytes. */ +/* Tx/Rx DMA burst length, 0-127, 0 == no preemption, tx==128 -> disabled. */ +static int txdmacount = 128; +static int rxdmacount = 0; + +/* Set the copy breakpoint for the copy-only-tiny-buffer Rx method. + Lower values use more memory, but are faster. */ +static int rx_copybreak = 200; + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 20; + +/* Maximum number of multicast addresses to filter (vs. rx-all-multicast) */ +static int multicast_filter_limit = 64; + +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/delay.h> + +/* Unused in the 2.0.* version, but retained for documentation. */ +#if LINUX_VERSION_CODE > 0x20118 +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("Intel i82557/i82558 EtherExpressPro driver"); +MODULE_PARM(debug, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(congenb, "i"); +MODULE_PARM(txfifo, "i"); +MODULE_PARM(rxfifo, "i"); +MODULE_PARM(txdmacount, "i"); +MODULE_PARM(rxdmacount, "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(max_interrupt_work, "i"); +MODULE_PARM(multicast_filter_limit, "i"); +#endif + +#define RUN_AT(x) (jiffies + (x)) + +#if (LINUX_VERSION_CODE < 0x20123) +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif + +/* The total I/O port extent of the board. + The registers beyond 0x18 only exist on the i82558. */ +#define SPEEDO3_TOTAL_SIZE 0x20 + +int speedo_debug = 1; + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the Intel i82557 "Speedo3" chip, Intel's +single-chip fast Ethernet controller for PCI, as used on the Intel +EtherExpress Pro 100 adapter. + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board. The system BIOS should be set to assign the +PCI INTA signal to an otherwise unused system IRQ line. While it's +possible to share PCI interrupt lines, it negatively impacts performance and +only recent kernels support it. + +III. Driver operation + +IIIA. General +The Speedo3 is very similar to other Intel network chips, that is to say +"apparently designed on a different planet". This chips retains the complex +Rx and Tx descriptors and multiple buffers pointers as previous chips, but +also has simplified Tx and Rx buffer modes. This driver uses the "flexible" +Tx mode, but in a simplified lower-overhead manner: it associates only a +single buffer descriptor with each frame descriptor. + +Despite the extra space overhead in each receive skbuff, the driver must use +the simplified Rx buffer mode to assure that only a single data buffer is +associated with each RxFD. The driver implements this by reserving space +for the Rx descriptor at the head of each Rx skbuff + +The Speedo-3 has receive and command unit base addresses that are added to +almost all descriptor pointers. The driver sets these to zero, so that all +pointer fields are absolute addresses. + +The System Control Block (SCB) of some previous Intel chips exists on the +chip in both PCI I/O and memory space. This driver uses the I/O space +registers, but might switch to memory mapped mode to better support non-x86 +processors. + +IIIB. Transmit structure + +The driver must use the complex Tx command+descriptor mode in order to +have a indirect pointer to the skbuff data section. Each Tx command block +(TxCB) is associated with a single, immediately appended Tx buffer descriptor +(TxBD). A fixed ring of these TxCB+TxBD pairs are kept as part of the +speedo_private data structure for each adapter instance. + +This ring structure is used for all normal transmit packets, but the +transmit packet descriptors aren't long enough for most non-Tx commands such +as CmdConfigure. This is complicated by the possibility that the chip has +already loaded the link address in the previous descriptor. So for these +commands we convert the next free descriptor on the ring to a NoOp, and point +that descriptor's link to the complex command. + +An additional complexity of these non-transmit commands are that they may be +added asynchronous to the normal transmit queue, so we disable interrupts +whenever the Tx descriptor ring is manipulated. + +A notable aspect of these special configure commands is that they do +work with the normal Tx ring entry scavenge method. The Tx ring scavenge +is done at interrupt time using the 'dirty_tx' index, and checking for the +command-complete bit. While the setup frames may have the NoOp command on the +Tx ring marked as complete, but not have completed the setup command, this +is not a problem. The tx_ring entry can be still safely reused, as the +tx_skbuff[] entry is always empty for config_cmd and mc_setup frames. + +Commands may have bits set e.g. CmdSuspend in the command word to either +suspend or stop the transmit/command unit. This driver always flags the last +command with CmdSuspend, erases the CmdSuspend in the previous command, and +then issues a CU_RESUME. +Note: Watch out for the potential race condition here: imagine + erasing the previous suspend + the chip processes the previous command + the chip processes the final command, and suspends + doing the CU_RESUME + the chip processes the next-yet-valid post-final-command. +So blindly sending a CU_RESUME is only safe if we do it immediately after +erasing the previous CmdSuspend, without the possibility of an intervening +delay. Thus the resume command is always within the interrupts-disabled +region. This is a timing dependence, but handling this condition in a +timing-independent way would considerably complicate the code. + +Note: In previous generation Intel chips, restarting the command unit was a +notoriously slow process. This is presumably no longer true. + +IIIC. Receive structure + +Because of the bus-master support on the Speedo3 this driver uses the new +SKBUFF_RX_COPYBREAK scheme, rather than a fixed intermediate receive buffer. +This scheme allocates full-sized skbuffs as receive buffers. The value +SKBUFF_RX_COPYBREAK is used as the copying breakpoint: it is chosen to +trade-off the memory wasted by passing the full-sized skbuff to the queue +layer for all frames vs. the copying cost of copying a frame to a +correctly-sized skbuff. + +For small frames the copying cost is negligible (esp. considering that we +are pre-loading the cache with immediately useful header information), so we +allocate a new, minimally-sized skbuff. For large frames the copying cost +is non-trivial, and the larger copy might flush the cache of useful data, so +we pass up the skbuff the packet was received into. + +IIID. Synchronization +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +The send packet thread has partial control over the Tx ring and 'dev->tbusy' +flag. It sets the tbusy flag whenever it's queuing a Tx packet. If the next +queue slot is empty, it clears the tbusy flag when finished otherwise it sets +the 'sp->tx_full' flag. + +The interrupt handler has exclusive control over the Rx ring and records stats +from the Tx ring. (The Tx-done interrupt can't be selectively turned off, so +we can't avoid the interrupt overhead by having the Tx routine reap the Tx +stats.) After reaping the stats, it marks the queue entry as empty by setting +the 'base' to zero. Iff the 'sp->tx_full' flag is set, it clears both the +tx_full and tbusy flags. + +IV. Notes + +Thanks to Steve Williams of Intel for arranging the non-disclosure agreement +that stated that I could disclose the information. But I still resent +having to sign an Intel NDA when I'm helping Intel sell their own product! + +*/ + +/* A few values that may be tweaked. */ +/* The ring sizes should be a power of two for efficiency. */ +#define TX_RING_SIZE 16 /* Effectively 2 entries fewer. */ +#define RX_RING_SIZE 16 +/* Size of an pre-allocated Rx buffer: <Ethernet MTU> + slack.*/ +#define PKT_BUF_SZ 1536 + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((800*HZ)/1000) + +/* How to wait for the command unit to accept a command. + Typically this takes 0 ticks. */ +static inline void wait_for_cmd_done(int cmd_ioaddr) +{ + short wait = 100; + do ; + while(inb(cmd_ioaddr) && --wait >= 0); +} + +/* Operational parameter that usually are not changed. */ + +/* The rest of these values should never change. */ + +/* Offsets to the various registers. + All accesses need not be longword aligned. */ +enum speedo_offsets { + SCBStatus = 0, SCBCmd = 2, /* Rx/Command Unit command and status. */ + SCBPointer = 4, /* General purpose pointer. */ + SCBPort = 8, /* Misc. commands and operands. */ + SCBflash = 12, SCBeeprom = 14, /* EEPROM and flash memory control. */ + SCBCtrlMDI = 16, /* MDI interface control. */ + SCBEarlyRx = 20, /* Early receive byte count. */ +}; +/* Commands that can be put in a command list entry. */ +enum commands { + CmdNOp = 0, CmdIASetup = 1, CmdConfigure = 2, CmdMulticastList = 3, + CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7, + CmdSuspend = 0x4000, /* Suspend after completion. */ + CmdIntr = 0x2000, /* Interrupt after completion. */ + CmdTxFlex = 0x0008, /* Use "Flexible mode" for CmdTx command. */ +}; + +/* The SCB accepts the following controls for the Tx and Rx units: */ +#define CU_START 0x0010 +#define CU_RESUME 0x0020 +#define CU_STATSADDR 0x0040 +#define CU_SHOWSTATS 0x0050 /* Dump statistics counters. */ +#define CU_CMD_BASE 0x0060 /* Base address to add to add CU commands. */ +#define CU_DUMPSTATS 0x0070 /* Dump then reset stats counters. */ + +#define RX_START 0x0001 +#define RX_RESUME 0x0002 +#define RX_ABORT 0x0004 +#define RX_ADDR_LOAD 0x0006 +#define RX_RESUMENR 0x0007 +#define INT_MASK 0x0100 +#define DRVR_INT 0x0200 /* Driver generated interrupt. */ + +/* The Speedo3 Rx and Tx frame/buffer descriptors. */ +struct descriptor { /* A generic descriptor. */ + s16 status; /* Offset 0. */ + s16 command; /* Offset 2. */ + u32 link; /* struct descriptor * */ + unsigned char params[0]; +}; + +/* The Speedo3 Rx and Tx buffer descriptors. */ +struct RxFD { /* Receive frame descriptor. */ + s32 status; + u32 link; /* struct RxFD * */ + u32 rx_buf_addr; /* void * */ + u16 count; + u16 size; +}; + +/* Elements of the RxFD.status word. */ +#define RX_COMPLETE 0x8000 + +struct TxFD { /* Transmit frame descriptor set. */ + s32 status; + u32 link; /* void * */ + u32 tx_desc_addr; /* Always points to the tx_buf_addr element. */ + s32 count; /* # of TBD (=1), Tx start thresh., etc. */ + /* This constitutes a single "TBD" entry -- we only use one. */ + u32 tx_buf_addr; /* void *, frame to be transmitted. */ + s32 tx_buf_size; /* Length of Tx frame. */ +}; + +/* Elements of the dump_statistics block. This block must be lword aligned. */ +struct speedo_stats { + u32 tx_good_frames; + u32 tx_coll16_errs; + u32 tx_late_colls; + u32 tx_underruns; + u32 tx_lost_carrier; + u32 tx_deferred; + u32 tx_one_colls; + u32 tx_multi_colls; + u32 tx_total_colls; + u32 rx_good_frames; + u32 rx_crc_errs; + u32 rx_align_errs; + u32 rx_resource_errs; + u32 rx_overrun_errs; + u32 rx_colls_errs; + u32 rx_runt_errs; + u32 done_marker; +}; + +struct speedo_private { + char devname[8]; /* Used only for kernel debugging. */ + const char *product_name; + struct device *next_module; + struct TxFD tx_ring[TX_RING_SIZE]; /* Commands (usually CmdTxPacket). */ + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + struct descriptor *last_cmd; /* Last command sent. */ + /* Rx descriptor ring & addresses of receive-in-place skbuffs. */ + struct RxFD *rx_ringp[RX_RING_SIZE]; + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + struct RxFD *last_rxf; /* Last command sent. */ + struct enet_statistics stats; + struct speedo_stats lstats; + struct timer_list timer; /* Media selection timer. */ + long last_rx_time; /* Last Rx, in jiffies, to handle Rx hang. */ + unsigned int cur_rx, cur_tx; /* The next free ring entry */ + unsigned int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + struct descriptor config_cmd; /* A configure command, with header... */ + u8 config_cmd_data[22]; /* .. and setup parameters. */ + int mc_setup_frm_len; /* The length of an allocated.. */ + struct descriptor *mc_setup_frm; /* ..multicast setup frame. */ + int in_interrupt; /* Word-aligned dev->interrupt */ + char rx_mode; /* Current PROMISC/ALLMULTI setting. */ + unsigned int tx_full:1; /* The Tx queue is full. */ + unsigned int full_duplex:1; /* Full-duplex operation requested. */ + unsigned int default_port:1; /* Last dev->if_port value. */ + unsigned int rx_bug:1; /* Work around receiver hang errata. */ + unsigned int rx_bug10:1; /* Receiver might hang at 10mbps. */ + unsigned int rx_bug100:1; /* Receiver might hang at 100mbps. */ + unsigned short phy[2]; /* PHY media interfaces available. */ +}; + +/* The parameters for a CmdConfigure operation. + There are so many options that it would be difficult to document each bit. + We mostly use the default or recommended settings. */ +const char basic_config_cmd[22] = { + 22, 0x08, 0, 0, 0, 0x80, 0x32, 0x03, 1, /* 1=Use MII 0=Use AUI */ + 0, 0x2E, 0, 0x60, 0, + 0xf2, 0x48, 0, 0x40, 0xf2, 0x80, /* 0x40=Force full-duplex */ + 0x3f, 0x05, }; + +/* PHY media interface chips. */ +static const char *phys[] = { + "None", "i82553-A/B", "i82553-C", "i82503", + "DP83840", "80c240", "80c24", "i82555", + "unknown-8", "unknown-9", "DP83840A", "unknown-11", + "unknown-12", "unknown-13", "unknown-14", "unknown-15", }; +enum phy_chips { NonSuchPhy=0, I82553AB, I82553C, I82503, DP83840, S80C240, + S80C24, I82555, DP83840A=10, }; +static const char is_mii[] = { 0, 1, 1, 0, 1, 1, 0, 1 }; + +static void speedo_found1(struct device *dev, int ioaddr, int irq, + int card_idx); + +static int read_eeprom(int ioaddr, int location); +static int mdio_read(int ioaddr, int phy_id, int location); +static int mdio_write(int ioaddr, int phy_id, int location, int value); +static int speedo_open(struct device *dev); +static void speedo_timer(unsigned long data); +static void speedo_init_rx_ring(struct device *dev); +static int speedo_start_xmit(struct sk_buff *skb, struct device *dev); +static int speedo_rx(struct device *dev); +static void speedo_interrupt(int irq, void *dev_instance, struct pt_regs *regs); +static int speedo_close(struct device *dev); +static struct enet_statistics *speedo_get_stats(struct device *dev); +static int speedo_ioctl(struct device *dev, struct ifreq *rq, int cmd); +static void set_rx_mode(struct device *dev); + + + +/* The parameters that may be passed in... */ +/* 'options' is used to pass a transceiver override or full-duplex flag + e.g. "options=16" for FD, "options=32" for 100mbps-only. */ +static int full_duplex[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +static int options[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +#ifdef MODULE +static int debug = -1; /* The debug level */ +#endif + +/* A list of all installed Speedo devices, for removing the driver module. */ +static struct device *root_speedo_dev = NULL; + +int eepro100_init(struct device *dev) +{ + int cards_found = 0; + + if (pcibios_present()) { + static int pci_index = 0; + for (; pci_index < 8; pci_index++) { + unsigned char pci_bus, pci_device_fn, pci_irq_line, pci_latency; + int pci_ioaddr; + + unsigned short pci_command, new_command; + + if (pcibios_find_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82557, + pci_index, &pci_bus, + &pci_device_fn)) + break; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + /* Note: BASE_ADDRESS_0 is for memory-mapping the registers. */ + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_1, &pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + if (speedo_debug > 2) + printk("Found Intel i82557 PCI Speedo at I/O %#x, IRQ %d.\n", + (int)pci_ioaddr, pci_irq_line); + + /* Get and check the bus-master and latency values. */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled this" + " device! Updating PCI command %4.4x->%4.4x.\n", + pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < 32) { + printk(" PCI latency timer (CFLT) is unreasonably low at %d." + " Setting to 32 clocks.\n", pci_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, 32); + } else if (speedo_debug > 1) + printk(" PCI latency timer (CFLT) is %#x.\n", pci_latency); + + speedo_found1(dev, pci_ioaddr, pci_irq_line, cards_found); + dev = NULL; + cards_found++; + } + } + + return cards_found; +} + +static void speedo_found1(struct device *dev, int ioaddr, int irq, + int card_idx) +{ + static int did_version = 0; /* Already printed version info. */ + struct speedo_private *sp; + char *product; + int i, option; + u16 eeprom[0x40]; + + if (speedo_debug > 0 && did_version++ == 0) + printk(version); + + dev = init_etherdev(dev, sizeof(struct speedo_private)); + + if (dev->mem_start > 0) + option = dev->mem_start; + else if (card_idx >= 0 && options[card_idx] >= 0) + option = options[card_idx]; + else + option = 0; + + /* Read the station address EEPROM before doing the reset. + Perhaps this should even be done before accepting the device, + then we wouldn't have a device name with which to report the error. */ + { + u16 sum = 0; + int j; + for (j = 0, i = 0; i < 0x40; i++) { + u16 value = read_eeprom(ioaddr, i); + eeprom[i] = value; + sum += value; + if (i < 3) { + dev->dev_addr[j++] = value; + dev->dev_addr[j++] = value >> 8; + } + } + if (sum != 0xBABA) + printk(KERN_WARNING "%s: Invalid EEPROM checksum %#4.4x, " + "check settings before activating this device!\n", + dev->name, sum); + /* Don't unregister_netdev(dev); as the EEPro may actually be + usable, especially if the MAC address is set later. */ + } + + /* Reset the chip: stop Tx and Rx processes and clear counters. + This takes less than 10usec and will easily finish before the next + action. */ + outl(0, ioaddr + SCBPort); + + if (eeprom[3] & 0x0100) + product = "OEM i82557/i82558 10/100 Ethernet"; + else + product = "Intel EtherExpress Pro 10/100"; + + printk(KERN_INFO "%s: %s at %#3x, ", dev->name, product, ioaddr); + + for (i = 0; i < 5; i++) + printk("%2.2X:", dev->dev_addr[i]); + printk("%2.2X, IRQ %d.\n", dev->dev_addr[i], irq); + +#ifndef kernel_bloat + /* OK, this is pure kernel bloat. I don't like it when other drivers + waste non-pageable kernel space to emit similar messages, but I need + them for bug reports. */ + { + const char *connectors[] = {" RJ45", " BNC", " AUI", " MII"}; + /* The self-test results must be paragraph aligned. */ + s32 str[6], *volatile self_test_results; + int boguscnt = 16000; /* Timeout for set-test. */ + if (eeprom[3] & 0x03) + printk(KERN_INFO " Receiver lock-up bug exists -- enabling" + " work-around.\n"); + printk(KERN_INFO " Board assembly %4.4x%2.2x-%3.3d, Physical" + " connectors present:", + eeprom[8], eeprom[9]>>8, eeprom[9] & 0xff); + for (i = 0; i < 4; i++) + if (eeprom[5] & (1<<i)) + printk(connectors[i]); + printk("\n"KERN_INFO" Primary interface chip %s PHY #%d.\n", + phys[(eeprom[6]>>8)&15], eeprom[6] & 0x1f); + if (eeprom[7] & 0x0700) + printk(KERN_INFO " Secondary interface chip %s.\n", + phys[(eeprom[7]>>8)&7]); + if (((eeprom[6]>>8) & 0x3f) == DP83840 + || ((eeprom[6]>>8) & 0x3f) == DP83840A) { + int mdi_reg23 = mdio_read(ioaddr, eeprom[6] & 0x1f, 23) | 0x0422; + if (congenb) + mdi_reg23 |= 0x0100; + printk(KERN_INFO" DP83840 specific setup, setting register 23 to %4.4x.\n", + mdi_reg23); + mdio_write(ioaddr, eeprom[6] & 0x1f, 23, mdi_reg23); + } + if ((option >= 0) && (option & 0x70)) { + printk(KERN_INFO " Forcing %dMbs %s-duplex operation.\n", + (option & 0x20 ? 100 : 10), + (option & 0x10 ? "full" : "half")); + mdio_write(ioaddr, eeprom[6] & 0x1f, 0, + ((option & 0x20) ? 0x2000 : 0) | /* 100mbps? */ + ((option & 0x10) ? 0x0100 : 0)); /* Full duplex? */ + } + + /* Perform a system self-test. */ + self_test_results = (s32*) ((((long) str) + 15) & ~0xf); + self_test_results[0] = 0; + self_test_results[1] = -1; + outl(virt_to_bus(self_test_results) | 1, ioaddr + SCBPort); + do { + udelay(10); + } while (self_test_results[1] == -1 && --boguscnt >= 0); + + if (boguscnt < 0) { /* Test optimized out. */ + printk(KERN_ERR "Self test failed, status %8.8x:\n" + KERN_ERR " Failure to initialize the i82557.\n" + KERN_ERR " Verify that the card is a bus-master" + " capable slot.\n", + self_test_results[1]); + } else + printk(KERN_INFO " General self-test: %s.\n" + KERN_INFO " Serial sub-system self-test: %s.\n" + KERN_INFO " Internal registers self-test: %s.\n" + KERN_INFO " ROM checksum self-test: %s (%#8.8x).\n", + self_test_results[1] & 0x1000 ? "failed" : "passed", + self_test_results[1] & 0x0020 ? "failed" : "passed", + self_test_results[1] & 0x0008 ? "failed" : "passed", + self_test_results[1] & 0x0004 ? "failed" : "passed", + self_test_results[0]); + } +#endif /* kernel_bloat */ + + outl(0, ioaddr + SCBPort); + + /* We do a request_region() only to register /proc/ioports info. */ + request_region(ioaddr, SPEEDO3_TOTAL_SIZE, "Intel Speedo3 Ethernet"); + + dev->base_addr = ioaddr; + dev->irq = irq; + + if (dev->priv == NULL) + dev->priv = kmalloc(sizeof(*sp), GFP_KERNEL); + sp = dev->priv; + memset(sp, 0, sizeof(*sp)); + sp->next_module = root_speedo_dev; + root_speedo_dev = dev; + + sp->full_duplex = option >= 0 && (option & 0x10) ? 1 : 0; + if (card_idx >= 0) { + if (full_duplex[card_idx] >= 0) + sp->full_duplex = full_duplex[card_idx]; + } + sp->default_port = option >= 0 ? (option & 0x0f) : 0; + + sp->phy[0] = eeprom[6]; + sp->phy[1] = eeprom[7]; + sp->rx_bug = (eeprom[3] & 0x03) == 3 ? 0 : 1; + + if (sp->rx_bug) + printk(KERN_INFO " Receiver lock-up workaround activated.\n"); + + /* The Speedo-specific entries in the device structure. */ + dev->open = &speedo_open; + dev->hard_start_xmit = &speedo_start_xmit; + dev->stop = &speedo_close; + dev->get_stats = &speedo_get_stats; + dev->set_multicast_list = &set_rx_mode; + dev->do_ioctl = &speedo_ioctl; + + return; +} + +/* Serial EEPROM section. + A "bit" grungy, but we work our way through bit-by-bit :->. */ +/* 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_WRITE_0 0x01 +#define EE_WRITE_1 0x05 +#define EE_DATA_READ 0x08 /* EEPROM chip data out. */ +#define EE_ENB (0x4800 | EE_CS) + +/* Delay between EEPROM clock transitions. + This will actually work with no delay on 33Mhz PCI. */ +#define eeprom_delay(nanosec) udelay(1); + +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD (5 << 6) +#define EE_READ_CMD (6 << 6) +#define EE_ERASE_CMD (7 << 6) + +static int read_eeprom(int ioaddr, int location) +{ + int i; + unsigned short retval = 0; + int ee_addr = ioaddr + SCBeeprom; + int read_cmd = location | EE_READ_CMD; + + outw(EE_ENB & ~EE_CS, ee_addr); + outw(EE_ENB, ee_addr); + + /* Shift the read command bits out. */ + for (i = 10; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0; + outw(EE_ENB | dataval, ee_addr); + eeprom_delay(100); + outw(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr); + eeprom_delay(150); + } + outw(EE_ENB, ee_addr); + + for (i = 15; i >= 0; i--) { + outw(EE_ENB | EE_SHIFT_CLK, ee_addr); + eeprom_delay(100); + retval = (retval << 1) | ((inw(ee_addr) & EE_DATA_READ) ? 1 : 0); + outw(EE_ENB, ee_addr); + eeprom_delay(100); + } + + /* Terminate the EEPROM access. */ + outw(EE_ENB & ~EE_CS, ee_addr); + return retval; +} + +static int mdio_read(int ioaddr, int phy_id, int location) +{ + int val, boguscnt = 64*10; /* <64 usec. to complete, typ 27 ticks */ + outl(0x08000000 | (location<<16) | (phy_id<<21), ioaddr + SCBCtrlMDI); + do { + val = inl(ioaddr + SCBCtrlMDI); + if (--boguscnt < 0) { + printk(KERN_ERR " mdio_read() timed out with val = %8.8x.\n", val); + } + } while (! (val & 0x10000000)); + return val & 0xffff; +} + +static int mdio_write(int ioaddr, int phy_id, int location, int value) +{ + int val, boguscnt = 64*10; /* <64 usec. to complete, typ 27 ticks */ + outl(0x04000000 | (location<<16) | (phy_id<<21) | value, + ioaddr + SCBCtrlMDI); + do { + val = inl(ioaddr + SCBCtrlMDI); + if (--boguscnt < 0) { + printk(KERN_ERR" mdio_write() timed out with val = %8.8x.\n", val); + } + } while (! (val & 0x10000000)); + return val & 0xffff; +} + + +static int +speedo_open(struct device *dev) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int ioaddr = dev->base_addr; + +#ifdef notdef + /* We could reset the chip, but should not need to. */ + outl(0, ioaddr + SCBPort); + udelay(10); +#endif + + if (request_irq(dev->irq, &speedo_interrupt, SA_SHIRQ, + "Intel EtherExpress Pro 10/100 Ethernet", dev)) { + return -EAGAIN; + } + if (speedo_debug > 1) + printk(KERN_DEBUG "%s: speedo_open() irq %d.\n", dev->name, dev->irq); + + MOD_INC_USE_COUNT; + + /* Load the statistics block address. */ + wait_for_cmd_done(ioaddr + SCBCmd); + outl(virt_to_bus(&sp->lstats), ioaddr + SCBPointer); + outw(INT_MASK | CU_STATSADDR, ioaddr + SCBCmd); + sp->lstats.done_marker = 0; + + speedo_init_rx_ring(dev); + wait_for_cmd_done(ioaddr + SCBCmd); + outl(0, ioaddr + SCBPointer); + outw(INT_MASK | RX_ADDR_LOAD, ioaddr + SCBCmd); + + /* Todo: verify that we must wait for previous command completion. */ + wait_for_cmd_done(ioaddr + SCBCmd); + outl(virt_to_bus(sp->rx_ringp[0]), ioaddr + SCBPointer); + outw(INT_MASK | RX_START, ioaddr + SCBCmd); + + /* Fill the first command with our physical address. */ + { + u16 *eaddrs = (u16 *)dev->dev_addr; + u16 *setup_frm = (u16 *)&(sp->tx_ring[0].tx_desc_addr); + + /* Avoid a bug(?!) here by marking the command already completed. */ + sp->tx_ring[0].status = ((CmdSuspend | CmdIASetup) << 16) | 0xa000; + sp->tx_ring[0].link = virt_to_bus(&(sp->tx_ring[1])); + *setup_frm++ = eaddrs[0]; + *setup_frm++ = eaddrs[1]; + *setup_frm++ = eaddrs[2]; + } + sp->last_cmd = (struct descriptor *)&sp->tx_ring[0]; + sp->cur_tx = 1; + sp->dirty_tx = 0; + sp->tx_full = 0; + + wait_for_cmd_done(ioaddr + SCBCmd); + outl(0, ioaddr + SCBPointer); + outw(INT_MASK | CU_CMD_BASE, ioaddr + SCBCmd); + + dev->if_port = sp->default_port; + + sp->in_interrupt = 0; + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* Start the chip's Tx process and unmask interrupts. */ + /* Todo: verify that we must wait for previous command completion. */ + wait_for_cmd_done(ioaddr + SCBCmd); + outl(virt_to_bus(&sp->tx_ring[0]), ioaddr + SCBPointer); + outw(CU_START, ioaddr + SCBCmd); + + /* Setup the chip and configure the multicast list. */ + sp->mc_setup_frm = NULL; + sp->mc_setup_frm_len = 0; + sp->rx_mode = -1; /* Invalid -> always reset the mode. */ + set_rx_mode(dev); + + if (speedo_debug > 2) { + printk(KERN_DEBUG "%s: Done speedo_open(), status %8.8x.\n", + dev->name, inw(ioaddr + SCBStatus)); + } + /* Set the timer. The timer serves a dual purpose: + 1) to monitor the media interface (e.g. link beat) and perhaps switch + to an alternate media type + 2) to monitor Rx activity, and restart the Rx process if the receiver + hangs. */ + init_timer(&sp->timer); + sp->timer.expires = RUN_AT((24*HZ)/10); /* 2.4 sec. */ + sp->timer.data = (unsigned long)dev; + sp->timer.function = &speedo_timer; /* timer handler */ + add_timer(&sp->timer); + + wait_for_cmd_done(ioaddr + SCBCmd); + outw(CU_DUMPSTATS, ioaddr + SCBCmd); + return 0; +} + +/* Media monitoring and control. */ +static void speedo_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int tickssofar = jiffies - sp->last_rx_time; + + if (speedo_debug > 3) { + int ioaddr = dev->base_addr; + printk(KERN_DEBUG "%s: Media selection tick, status %4.4x.\n", + dev->name, inw(ioaddr + SCBStatus)); + } + if (sp->rx_bug) { + if (tickssofar > 2*HZ || sp->rx_mode < 0) { + /* We haven't received a packet in a Long Time. We might have been + bitten by the receiver hang bug. This can be cleared by sending + a set multicast list command. */ + set_rx_mode(dev); + } + /* We must continue to monitor the media. */ + sp->timer.expires = RUN_AT(2*HZ); /* 2.0 sec. */ + add_timer(&sp->timer); + } +} + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void +speedo_init_rx_ring(struct device *dev) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + struct RxFD *rxf, *last_rxf = NULL; + int i; + + sp->cur_rx = 0; + sp->dirty_rx = RX_RING_SIZE - 1; + + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb; + skb = alloc_skb(PKT_BUF_SZ, GFP_ATOMIC); + sp->rx_skbuff[i] = skb; + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ + + rxf = (struct RxFD *)skb->tail; + skb_reserve(skb, sizeof(struct RxFD)); + sp->rx_ringp[i] = rxf; + if (last_rxf) + last_rxf->link = virt_to_bus(rxf); + last_rxf = rxf; + rxf->status = 0x00000001; /* '1' is flag value only. */ + rxf->link = 0; /* None yet. */ + /* This field unused by i82557, we use it as a consistency check. */ + rxf->rx_buf_addr = virt_to_bus(skb->tail); + + rxf->count = 0; + rxf->size = PKT_BUF_SZ; + } + /* Mark the last entry as end-of-list. */ + last_rxf->status = 0xC0000002; /* '2' is flag value only. */ + sp->last_rxf = last_rxf; +} + +static void speedo_tx_timeout(struct device *dev) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int ioaddr = dev->base_addr; + + printk(KERN_WARNING "%s: Transmit timed out: status %4.4x " + "command %4.4x.\n", + dev->name, inw(ioaddr + SCBStatus), inw(ioaddr + SCBCmd)); + + if ((inw(ioaddr + SCBStatus) & 0x00C0) != 0x0080) { + printk(KERN_WARNING "%s: Trying to restart the transmitter...\n", + dev->name); + outl(virt_to_bus(&sp->tx_ring[sp->dirty_tx % TX_RING_SIZE]), + ioaddr + SCBPointer); + outw(CU_START, ioaddr + SCBCmd); + } else { + outw(DRVR_INT, ioaddr + SCBCmd); + } + /* Reset the MII transceiver, suggested by Fred Young @ scalable.com. */ + if ((sp->phy[0] & 0x8000) == 0) { + int phy_addr = sp->phy[0] & 0x1f; + mdio_write(ioaddr, phy_addr, 0, 0x0400); + mdio_write(ioaddr, phy_addr, 1, 0x0000); + mdio_write(ioaddr, phy_addr, 4, 0x0000); + mdio_write(ioaddr, phy_addr, 0, 0x8000); + } + sp->stats.tx_errors++; + dev->trans_start = jiffies; + return; +} + +static int +speedo_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int ioaddr = dev->base_addr; + int entry; + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. + If this ever occurs the queue layer is doing something evil! */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < TX_TIMEOUT - 2) + return 1; + if (tickssofar < TX_TIMEOUT) { + /* Reap sent packets from the full Tx queue. */ + outw(DRVR_INT, ioaddr + SCBCmd); + return 1; + } + speedo_tx_timeout(dev); + return 1; + } + + /* Caution: the write order is important here, set the base address + with the "ownership" bits last. */ + + { /* Prevent interrupts from changing the Tx ring from underneath us. */ + unsigned long flags; + + save_flags(flags); + cli(); + /* Calculate the Tx descriptor entry. */ + entry = sp->cur_tx++ % TX_RING_SIZE; + + sp->tx_skbuff[entry] = skb; + /* Todo: be a little more clever about setting the interrupt bit. */ + sp->tx_ring[entry].status = + (CmdSuspend | CmdTx | CmdTxFlex) << 16; + sp->tx_ring[entry].link = + virt_to_bus(&sp->tx_ring[sp->cur_tx % TX_RING_SIZE]); + sp->tx_ring[entry].tx_desc_addr = + virt_to_bus(&sp->tx_ring[entry].tx_buf_addr); + /* The data region is always in one buffer descriptor, Tx FIFO + threshold of 256. */ + sp->tx_ring[entry].count = 0x01208000; + sp->tx_ring[entry].tx_buf_addr = virt_to_bus(skb->data); + sp->tx_ring[entry].tx_buf_size = skb->len; + /* Todo: perhaps leave the interrupt bit set if the Tx queue is more + than half full. Argument against: we should be receiving packets + and scavenging the queue. Argument for: if so, it shouldn't + matter. */ + sp->last_cmd->command &= ~(CmdSuspend | CmdIntr); + sp->last_cmd = (struct descriptor *)&sp->tx_ring[entry]; + /* Trigger the command unit resume. */ + wait_for_cmd_done(ioaddr + SCBCmd); + outw(CU_RESUME, ioaddr + SCBCmd); + restore_flags(flags); + } + + /* Leave room for set_rx_mode() to fill two entries. */ + if (sp->cur_tx - sp->dirty_tx > TX_RING_SIZE - 3) + sp->tx_full = 1; + else + clear_bit(0, (void*)&dev->tbusy); + + dev->trans_start = jiffies; + + return 0; +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void speedo_interrupt(int irq, void *dev_instance, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_instance; + struct speedo_private *sp; + int ioaddr, boguscnt = max_interrupt_work; + unsigned short status; + +#ifndef final_version + if (dev == NULL) { + printk(KERN_ERR "speedo_interrupt(): irq %d for unknown device.\n", irq); + return; + } +#endif + + ioaddr = dev->base_addr; + sp = (struct speedo_private *)dev->priv; +#ifndef final_version + /* A lock to prevent simultaneous entry on SMP machines. */ + if (test_and_set_bit(0, (void*)&sp->in_interrupt)) { + printk(KERN_ERR"%s: SMP simultaneous entry of an interrupt handler.\n", + dev->name); + return; + } + dev->interrupt = 1; +#endif + + do { + status = inw(ioaddr + SCBStatus); + /* Acknowledge all of the current interrupt sources ASAP. */ + outw(status & 0xfc00, ioaddr + SCBStatus); + + if (speedo_debug > 4) + printk(KERN_DEBUG "%s: interrupt status=%#4.4x.\n", + dev->name, status); + + if ((status & 0xfc00) == 0) + break; + + if (status & 0x4000) /* Packet received. */ + speedo_rx(dev); + + if (status & 0x1000) { + if ((status & 0x003c) == 0x0028) /* No more Rx buffers. */ + outw(RX_RESUMENR, ioaddr + SCBCmd); + else if ((status & 0x003c) == 0x0008) { /* No resources (why?!) */ + /* No idea of what went wrong. Restart the receiver. */ + outl(virt_to_bus(sp->rx_ringp[sp->cur_rx % RX_RING_SIZE]), + ioaddr + SCBPointer); + outw(RX_START, ioaddr + SCBCmd); + } + sp->stats.rx_errors++; + } + + /* User interrupt, Command/Tx unit interrupt or CU not active. */ + if (status & 0xA400) { + unsigned int dirty_tx = sp->dirty_tx; + + while (sp->cur_tx - dirty_tx > 0) { + int entry = dirty_tx % TX_RING_SIZE; + int status = sp->tx_ring[entry].status; + + if (speedo_debug > 5) + printk(KERN_DEBUG " scavenge candidate %d status %4.4x.\n", + entry, status); + if ((status & 0x8000) == 0) + break; /* It still hasn't been processed. */ + /* Free the original skb. */ + if (sp->tx_skbuff[entry]) { + sp->stats.tx_packets++; /* Count only user packets. */ + dev_kfree_skb(sp->tx_skbuff[entry], FREE_WRITE); + sp->tx_skbuff[entry] = 0; + } + dirty_tx++; + } + +#ifndef final_version + if (sp->cur_tx - dirty_tx > TX_RING_SIZE) { + printk(KERN_ERR "out-of-sync dirty pointer, %d vs. %d," + " full=%d.\n", + dirty_tx, sp->cur_tx, sp->tx_full); + dirty_tx += TX_RING_SIZE; + } +#endif + + if (sp->tx_full && dev->tbusy + && dirty_tx > sp->cur_tx - TX_RING_SIZE + 2) { + /* The ring is no longer full, clear tbusy. */ + sp->tx_full = 0; + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + + sp->dirty_tx = dirty_tx; + } + + if (--boguscnt < 0) { + printk(KERN_ERR "%s: Too much work at interrupt, status=0x%4.4x.\n", + dev->name, status); + /* Clear all interrupt sources. */ + outl(0xfc00, ioaddr + SCBStatus); + break; + } + } while (1); + + if (speedo_debug > 3) + printk(KERN_DEBUG "%s: exiting interrupt, status=%#4.4x.\n", + dev->name, inw(ioaddr + SCBStatus)); + + dev->interrupt = 0; + clear_bit(0, (void*)&sp->in_interrupt); + return; +} + +static int +speedo_rx(struct device *dev) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int entry = sp->cur_rx % RX_RING_SIZE; + int status; + + if (speedo_debug > 4) + printk(KERN_DEBUG " In speedo_rx().\n"); + /* If we own the next entry, it's a new packet. Send it up. */ + while ((status = sp->rx_ringp[entry]->status) & RX_COMPLETE) { + + if (speedo_debug > 4) + printk(KERN_DEBUG " speedo_rx() status %8.8x len %d.\n", status, + sp->rx_ringp[entry]->count & 0x3fff); + if (status & 0x0200) { + printk(KERN_ERR "%s: Ethernet frame overran the Rx buffer, " + "status %8.8x!\n", dev->name, status); + } else if ( ! (status & 0x2000)) { + /* There was a fatal error. This *should* be impossible. */ + sp->stats.rx_errors++; + printk(KERN_ERR "%s: Anomalous event in speedo_rx(), status %8.8x.\n", + dev->name, status); + } else { + /* Malloc up new buffer, compatible with net-2e. */ + int pkt_len = sp->rx_ringp[entry]->count & 0x3fff; + struct sk_buff *skb; + int rx_in_place = 0; + + /* Check if the packet is long enough to just accept without + copying to a properly sized skbuff. */ + if (pkt_len > rx_copybreak) { + struct sk_buff *newskb; + char *temp; + + /* Pass up the skb already on the Rx ring. */ + skb = sp->rx_skbuff[entry]; + temp = skb_put(skb, pkt_len); + if (bus_to_virt(sp->rx_ringp[entry]->rx_buf_addr) != temp) + printk(KERN_ERR "%s: Warning -- the skbuff addresses do not match" + " in speedo_rx: %8.8x vs. %p / %p.\n", dev->name, + sp->rx_ringp[entry]->rx_buf_addr, skb->head, temp); + /* Get a fresh skbuff to replace the filled one. */ + newskb = dev_alloc_skb(PKT_BUF_SZ + sizeof(struct RxFD)); + + if (newskb) { + struct RxFD *rxf; + rx_in_place = 1; + sp->rx_skbuff[entry] = newskb; + newskb->dev = dev; + rxf = sp->rx_ringp[entry] = (struct RxFD *)newskb->tail; + skb_reserve(newskb, sizeof(struct RxFD)); + /* Unused by i82557, consistency check only. */ + rxf->rx_buf_addr = virt_to_bus(newskb->tail); + rxf->status = 0x00000001; + } else /* No memory, drop the packet. */ + skb = 0; + } else + skb = dev_alloc_skb(pkt_len + 2); + if (skb == NULL) { + int i; + printk(KERN_ERR "%s: Memory squeeze, deferring packet.\n", dev->name); + /* Check that at least two ring entries are free. + If not, free one and mark stats->rx_dropped++. */ + /* ToDo: This is not correct!!!! We should count the number + of linked-in Rx buffer to very that we have at least two + remaining. */ + for (i = 0; i < RX_RING_SIZE; i++) + if (! ((sp->rx_ringp[(entry+i) % RX_RING_SIZE]->status) + & RX_COMPLETE)) + break; + + if (i > RX_RING_SIZE -2) { + sp->stats.rx_dropped++; + sp->rx_ringp[entry]->status = 0; + sp->cur_rx++; + } + break; + } + skb->dev = dev; + if (! rx_in_place) { + skb_reserve(skb, 2); /* 16 byte align the data fields */ +#if defined(__i386) && notyet + /* Packet is in one chunk -- we can copy + cksum. */ + eth_io_copy_and_sum(skb, bus_to_virt(sp->rx_ringp[entry]->rx_buf_addr), + pkt_len, 0); +#else + memcpy(skb_put(skb, pkt_len), + bus_to_virt(sp->rx_ringp[entry]->rx_buf_addr), pkt_len); +#endif + } + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + sp->stats.rx_packets++; + } + + /* ToDo: This is better than before, but should be checked. */ + { + struct RxFD *rxf = sp->rx_ringp[entry]; + rxf->status = 0xC0000003; /* '3' for verification only */ + rxf->link = 0; /* None yet. */ + rxf->count = 0; + rxf->size = PKT_BUF_SZ; + sp->last_rxf->link = virt_to_bus(rxf); + sp->last_rxf->status &= ~0xC0000000; + sp->last_rxf = rxf; + entry = (++sp->cur_rx) % RX_RING_SIZE; + } + } + + sp->last_rx_time = jiffies; + return 0; +} + +static int +speedo_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (speedo_debug > 1) + printk(KERN_DEBUG "%s: Shutting down ethercard, status was %4.4x.\n", + dev->name, inw(ioaddr + SCBStatus)); + + /* Shut off the media monitoring timer. */ + del_timer(&sp->timer); + + /* Disable interrupts, and stop the chip's Rx process. */ + outw(INT_MASK, ioaddr + SCBCmd); + outw(INT_MASK | RX_ABORT, ioaddr + SCBCmd); + + free_irq(dev->irq, dev); + + /* Free all the skbuffs in the Rx and Tx queues. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = sp->rx_skbuff[i]; + sp->rx_skbuff[i] = 0; + /* Clear the Rx descriptors. */ + if (skb) + dev_kfree_skb(skb, FREE_WRITE); + } + + for (i = 0; i < TX_RING_SIZE; i++) { + struct sk_buff *skb = sp->tx_skbuff[i]; + sp->tx_skbuff[i] = 0; + /* Clear the Tx descriptors. */ + if (skb) + dev_kfree_skb(skb, FREE_WRITE); + } + if (sp->mc_setup_frm) { + kfree(sp->mc_setup_frm); + sp->mc_setup_frm_len = 0; + } + + /* Print a few items for debugging. */ + if (speedo_debug > 3) { + int phy_num = sp->phy[0] & 0x1f; + printk(KERN_DEBUG "%s:Printing Rx ring (next to receive into %d).\n", + dev->name, sp->cur_rx); + + for (i = 0; i < RX_RING_SIZE; i++) + printk(KERN_DEBUG " Rx ring entry %d %8.8x.\n", + i, (int)sp->rx_ringp[i]->status); + + for (i = 0; i < 5; i++) + printk(KERN_DEBUG " PHY index %d register %d is %4.4x.\n", + phy_num, i, mdio_read(ioaddr, phy_num, i)); + for (i = 21; i < 26; i++) + printk(KERN_DEBUG " PHY index %d register %d is %4.4x.\n", + phy_num, i, mdio_read(ioaddr, phy_num, i)); + } + MOD_DEC_USE_COUNT; + + return 0; +} + +/* The Speedo-3 has an especially awkward and unusable method of getting + statistics out of the chip. It takes an unpredictable length of time + for the dump-stats command to complete. To avoid a busy-wait loop we + update the stats with the previous dump results, and then trigger a + new dump. + + These problems are mitigated by the current /proc implementation, which + calls this routine first to judge the output length, and then to emit the + output. + + Oh, and incoming frames are dropped while executing dump-stats! + */ +static struct enet_statistics * +speedo_get_stats(struct device *dev) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int ioaddr = dev->base_addr; + + if (sp->lstats.done_marker == 0xA007) { /* Previous dump finished */ + sp->stats.tx_aborted_errors += sp->lstats.tx_coll16_errs; + sp->stats.tx_window_errors += sp->lstats.tx_late_colls; + sp->stats.tx_fifo_errors += sp->lstats.tx_underruns; + sp->stats.tx_fifo_errors += sp->lstats.tx_lost_carrier; + /*sp->stats.tx_deferred += sp->lstats.tx_deferred;*/ + sp->stats.collisions += sp->lstats.tx_total_colls; + sp->stats.rx_crc_errors += sp->lstats.rx_crc_errs; + sp->stats.rx_frame_errors += sp->lstats.rx_align_errs; + sp->stats.rx_over_errors += sp->lstats.rx_resource_errs; + sp->stats.rx_fifo_errors += sp->lstats.rx_overrun_errs; + sp->stats.rx_length_errors += sp->lstats.rx_runt_errs; + sp->lstats.done_marker = 0x0000; + if (dev->start) { + wait_for_cmd_done(ioaddr + SCBCmd); + outw(CU_DUMPSTATS, ioaddr + SCBCmd); + } + } + return &sp->stats; +} + +static int speedo_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_data; + int phy = sp->phy[0] & 0x1f; + + switch(cmd) { + case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */ + data[0] = phy; + case SIOCDEVPRIVATE+1: /* Read the specified MII register. */ + data[3] = mdio_read(ioaddr, data[0], data[1]); + return 0; + case SIOCDEVPRIVATE+2: /* Write the specified MII register */ + if (!suser()) + return -EPERM; + mdio_write(ioaddr, data[0], data[1], data[2]); + return 0; + default: + return -EOPNOTSUPP; + } +} + +/* Set or clear the multicast filter for this adaptor. + This is very ugly with Intel chips -- we usually have to execute an + entire configuration command, plus process a multicast command. + This is complicated. We must put a large configuration command and + an arbitrarily-sized multicast command in the transmit list. + To minimize the disruption -- the previous command might have already + loaded the link -- we convert the current command block, normally a Tx + command, into a no-op and link it to the new command. +*/ +static void +set_rx_mode(struct device *dev) +{ + struct speedo_private *sp = (struct speedo_private *)dev->priv; + int ioaddr = dev->base_addr; + char new_rx_mode; + unsigned long flags; + int entry, i; + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + new_rx_mode = 3; + } else if ((dev->flags & IFF_ALLMULTI) || + dev->mc_count > multicast_filter_limit) { + new_rx_mode = 1; + } else + new_rx_mode = 0; + + if (sp->cur_tx - sp->dirty_tx >= TX_RING_SIZE - 1) { + /* The Tx ring is full -- don't add anything! Presumably the new mode + is in config_cmd_data and will be added anyway. */ + sp->rx_mode = -1; + return; + } + + if (new_rx_mode != sp->rx_mode) { + /* We must change the configuration. Construct a CmdConfig frame. */ + memcpy(sp->config_cmd_data, basic_config_cmd,sizeof(basic_config_cmd)); + sp->config_cmd_data[1] = (txfifo << 4) | rxfifo; + sp->config_cmd_data[4] = rxdmacount; + sp->config_cmd_data[5] = txdmacount + 0x80; + sp->config_cmd_data[15] = (new_rx_mode & 2) ? 0x49 : 0x48; + sp->config_cmd_data[19] = sp->full_duplex ? 0xC0 : 0x80; + sp->config_cmd_data[21] = (new_rx_mode & 1) ? 0x0D : 0x05; + if (sp->phy[0] & 0x8000) { /* Use the AUI port instead. */ + sp->config_cmd_data[15] |= 0x80; + sp->config_cmd_data[8] = 0; + } + save_flags(flags); + cli(); + /* Fill the "real" tx_ring frame with a no-op and point it to us. */ + entry = sp->cur_tx++ % TX_RING_SIZE; + sp->tx_skbuff[entry] = 0; /* Nothing to free. */ + sp->tx_ring[entry].status = CmdNOp << 16; + sp->tx_ring[entry].link = virt_to_bus(&sp->config_cmd); + sp->config_cmd.status = 0; + sp->config_cmd.command = CmdSuspend | CmdConfigure; + sp->config_cmd.link = + virt_to_bus(&(sp->tx_ring[sp->cur_tx % TX_RING_SIZE])); + sp->last_cmd->command &= ~CmdSuspend; + /* Immediately trigger the command unit resume. */ + wait_for_cmd_done(ioaddr + SCBCmd); + outw(CU_RESUME, ioaddr + SCBCmd); + sp->last_cmd = &sp->config_cmd; + restore_flags(flags); + if (speedo_debug > 5) { + int i; + printk(KERN_DEBUG " CmdConfig frame in entry %d.\n", entry); + for(i = 0; i < 32; i++) + printk(" %2.2x", ((unsigned char *)&sp->config_cmd)[i]); + printk(".\n"); + } + } + + if (new_rx_mode == 0 && dev->mc_count < 3) { + /* The simple case of 0-2 multicast list entries occurs often, and + fits within one tx_ring[] entry. */ + u16 *setup_params, *eaddrs; + struct dev_mc_list *mclist; + + save_flags(flags); + cli(); + entry = sp->cur_tx++ % TX_RING_SIZE; + sp->tx_skbuff[entry] = 0; + sp->tx_ring[entry].status = (CmdSuspend | CmdMulticastList) << 16; + sp->tx_ring[entry].link = + virt_to_bus(&sp->tx_ring[sp->cur_tx % TX_RING_SIZE]); + sp->tx_ring[entry].tx_desc_addr = 0; /* Really MC list count. */ + setup_params = (u16 *)&sp->tx_ring[entry].tx_desc_addr; + *setup_params++ = dev->mc_count*6; + /* Fill in the multicast addresses. */ + for (i = 0, mclist = dev->mc_list; i < dev->mc_count; + i++, mclist = mclist->next) { + eaddrs = (u16 *)mclist->dmi_addr; + *setup_params++ = *eaddrs++; + *setup_params++ = *eaddrs++; + *setup_params++ = *eaddrs++; + } + + sp->last_cmd->command &= ~CmdSuspend; + /* Immediately trigger the command unit resume. */ + wait_for_cmd_done(ioaddr + SCBCmd); + outw(CU_RESUME, ioaddr + SCBCmd); + sp->last_cmd = (struct descriptor *)&sp->tx_ring[entry]; + restore_flags(flags); + } else if (new_rx_mode == 0) { + /* This does not work correctly, but why not? */ + struct dev_mc_list *mclist; + u16 *eaddrs; + struct descriptor *mc_setup_frm = sp->mc_setup_frm; + u16 *setup_params; + int i; + + if (sp->mc_setup_frm_len < 10 + dev->mc_count*6 + || sp->mc_setup_frm == NULL) { + /* Allocate a new frame, 10bytes + addrs, with a few + extra entries for growth. */ + if (sp->mc_setup_frm) + kfree(sp->mc_setup_frm); + sp->mc_setup_frm_len = 10 + dev->mc_count*6 + 24; + sp->mc_setup_frm = kmalloc(sp->mc_setup_frm_len, GFP_ATOMIC); + if (sp->mc_setup_frm == NULL) { + printk(KERN_ERR "%s: Failed to allocate a setup frame.\n", dev->name); + sp->rx_mode = -1; /* We failed, try again. */ + return; + } + } + mc_setup_frm = sp->mc_setup_frm; + /* Construct the new setup frame. */ + if (speedo_debug > 1) + printk(KERN_DEBUG "%s: Constructing a setup frame at %p, " + "%d bytes.\n", + dev->name, sp->mc_setup_frm, sp->mc_setup_frm_len); + mc_setup_frm->status = 0; + mc_setup_frm->command = CmdSuspend | CmdIntr | CmdMulticastList; + /* Link set below. */ + setup_params = (u16 *)mc_setup_frm->params; + *setup_params++ = dev->mc_count*6; + /* Fill in the multicast addresses. */ + for (i = 0, mclist = dev->mc_list; i < dev->mc_count; + i++, mclist = mclist->next) { + eaddrs = (u16 *)mclist->dmi_addr; + *setup_params++ = *eaddrs++; + *setup_params++ = *eaddrs++; + *setup_params++ = *eaddrs++; + } + + /* Disable interrupts while playing with the Tx Cmd list. */ + save_flags(flags); + cli(); + entry = sp->cur_tx++ % TX_RING_SIZE; + + if (speedo_debug > 5) + printk(" CmdMCSetup frame length %d in entry %d.\n", + dev->mc_count, entry); + + /* Change the command to a NoOp, pointing to the CmdMulti command. */ + sp->tx_skbuff[entry] = 0; + sp->tx_ring[entry].status = CmdNOp << 16; + sp->tx_ring[entry].link = virt_to_bus(mc_setup_frm); + + /* Set the link in the setup frame. */ + mc_setup_frm->link = + virt_to_bus(&(sp->tx_ring[sp->cur_tx % TX_RING_SIZE])); + + sp->last_cmd->command &= ~CmdSuspend; + /* Immediately trigger the command unit resume. */ + wait_for_cmd_done(ioaddr + SCBCmd); + outw(CU_RESUME, ioaddr + SCBCmd); + sp->last_cmd = mc_setup_frm; + restore_flags(flags); + if (speedo_debug > 1) + printk(KERN_DEBUG "%s: Last command at %p is %4.4x.\n", + dev->name, sp->last_cmd, sp->last_cmd->command); + } + + sp->rx_mode = new_rx_mode; +} + +#ifdef MODULE + +int +init_module(void) +{ + int cards_found; + + if (debug >= 0) + speedo_debug = debug; + if (speedo_debug) + printk(KERN_INFO "%s", version); + + root_speedo_dev = NULL; + cards_found = eepro100_init(NULL); + return cards_found ? 0 : -ENODEV; +} + +void +cleanup_module(void) +{ + struct device *next_dev; + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_speedo_dev) { + next_dev = ((struct speedo_private *)root_speedo_dev->priv)->next_module; + unregister_netdev(root_speedo_dev); + release_region(root_speedo_dev->base_addr, SPEEDO3_TOTAL_SIZE); + kfree(root_speedo_dev); + root_speedo_dev = next_dev; + } +} +#else /* not MODULE */ +int eepro100_probe(struct device *dev) +{ + int cards_found = 0; + + cards_found = eepro100_init(dev); + + if (speedo_debug > 0 && cards_found) + printk(version); + + return cards_found ? 0 : -ENODEV; +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c eepro100.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * SMP-compile-command: "gcc -D__SMP__ -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c eepro100.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ 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: + */ diff --git a/linux/src/drivers/net/epic100.c b/linux/src/drivers/net/epic100.c new file mode 100644 index 00000000..1ff99fe6 --- /dev/null +++ b/linux/src/drivers/net/epic100.c @@ -0,0 +1,1459 @@ +/* epic100.c: A SMC 83c170 EPIC/100 Fast Ethernet driver for Linux. */ +/* + Written 1997-1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + All other rights reserved. + + This driver is for the SMC83c170/175 "EPIC" series, as used on the + SMC EtherPower II 9432 PCI adapter, and several CardBus cards. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Support and updates available at + http://cesdis.gsfc.nasa.gov/linux/drivers/epic100.html +*/ + +static const char *version = +"epic100.c:v1.03 8/7/98 Donald Becker http://cesdis.gsfc.nasa.gov/linux/drivers/epic100.html\n"; + +/* A few user-configurable values. */ + +/* Keep the ring sizes a power of two for efficiency. + Making the Tx ring too large decreases the effectiveness of channel + bonding and packet priority. + There are no ill effects from too-large receive rings. */ +#define TX_RING_SIZE 16 +#define RX_RING_SIZE 32 + +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + Setting to > 1518 effectively disables this feature. */ +static int rx_copybreak = 200; + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 10; + +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((2000*HZ)/1000) + +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +/* Bytes transferred to chip before transmission starts. */ +#define TX_FIFO_THRESH 256 /* Rounded down to 4 byte units. */ +#define RX_FIFO_THRESH 1 /* 0-3, 0==32, 64,96, or 3==128 bytes */ + +#include <linux/config.h> +#include <linux/version.h> /* Evil, but neccessary */ +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#if LINUX_VERSION_CODE >= 0x20155 +#define PCI_SUPPORT_VER2 +#else +#include <linux/bios32.h> +#endif +#include <linux/delay.h> + +#include <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* Kernel compatibility defines, common to David Hind's PCMCIA package. + This is only in the support-all-kernels source code. */ + +#if ! defined (LINUX_VERSION_CODE) || LINUX_VERSION_CODE < 0x20000 +#warning This driver version is only for kernel versions 2.0.0 and later. +#endif + +#define RUN_AT(x) (jiffies + (x)) + +#if defined(MODULE) && (LINUX_VERSION_CODE >= 0x20115) +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("SMC 83c170 EPIC series Ethernet driver"); +MODULE_PARM(debug, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(max_interrupt_work, "i"); +#endif +#if LINUX_VERSION_CODE < 0x20123 +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif +#if LINUX_VERSION_CODE <= 0x20139 +#define net_device_stats enet_statistics +#define NETSTATS_VER2 +#endif +#if LINUX_VERSION_CODE < 0x20159 +#define DEV_FREE_SKB(skb) dev_kfree_skb(skb, FREE_WRITE); +#else /* Grrr, unneeded incompatible change. */ +#define DEV_FREE_SKB(skb) dev_kfree_skb(skb); +#endif + +/* The I/O extent. */ +#define EPIC_TOTAL_SIZE 0x100 + +static int epic_debug = 1; + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the SMC "EPCI/100", the SMC +single-chip Ethernet controllers for PCI. This chip is used on +the SMC EtherPower II boards. + + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board. The system BIOS will assign the +PCI INTA signal to a (preferably otherwise unused) system IRQ line. +Note: Kernel versions earlier than 1.3.73 do not support shared PCI +interrupt lines. + +III. Driver operation + +IIIa. Ring buffers + +IVb. References + +http://www.smc.com/components/catalog/smc83c170.html +http://cesdis.gsfc.nasa.gov/linux/misc/NWay.html +http://www.national.com/pf/DP/DP83840.html + +IVc. Errata + +*/ + +/* The rest of these values should never change. */ + +static struct device *epic_probe1(int pci_bus, int pci_devfn, + struct device *dev, int card_idx); + +enum pci_flags_bit { + PCI_USES_IO=1, PCI_USES_MEM=2, PCI_USES_MASTER=4, + PCI_ADDR0=0x10<<0, PCI_ADDR1=0x10<<1, PCI_ADDR2=0x10<<2, PCI_ADDR3=0x10<<3, +}; +struct chip_info { + const char *name; + u16 vendor_id, device_id, device_id_mask, pci_flags; + int io_size, min_latency; + struct device *(*probe1)(int pci_bus, int pci_devfn, struct device *dev, + int chip_idx); +} chip_tbl[] = { + {"SMSC EPIC/100", 0x10B8, 0x0005, 0x7fff, + PCI_USES_IO|PCI_USES_MASTER|PCI_ADDR0, EPIC_TOTAL_SIZE, 32, epic_probe1}, + {0,}, +}; + +/* Offsets to registers, using the (ugh) SMC names. */ +enum epic_registers { + COMMAND=0, INTSTAT=4, INTMASK=8, GENCTL=0x0C, NVCTL=0x10, EECTL=0x14, + PCIBurstCnt=0x18, + TEST1=0x1C, CRCCNT=0x20, ALICNT=0x24, MPCNT=0x28, /* Rx error counters. */ + MIICtrl=0x30, MIIData=0x34, MIICfg=0x38, + LAN0=64, /* MAC address. */ + MC0=80, /* Multicast filter table. */ + RxCtrl=96, TxCtrl=112, TxSTAT=0x74, + PRxCDAR=0x84, RxSTAT=0xA4, EarlyRx=0xB0, PTxCDAR=0xC4, TxThresh=0xDC, +}; + +/* Interrupt register bits, using my own meaningful names. */ +enum IntrStatus { + TxIdle=0x40000, RxIdle=0x20000, IntrSummary=0x010000, + PCIBusErr170=0x7000, PCIBusErr175=0x1000, PhyEvent175=0x8000, + RxStarted=0x0800, RxEarlyWarn=0x0400, CntFull=0x0200, TxUnderrun=0x0100, + TxEmpty=0x0080, TxDone=0x0020, RxError=0x0010, + RxOverflow=0x0008, RxFull=0x0004, RxHeader=0x0002, RxDone=0x0001, +}; + +/* The EPIC100 Rx and Tx buffer descriptors. */ + +struct epic_tx_desc { + s16 status; + u16 txlength; + u32 bufaddr; + u16 buflength; + u16 control; + u32 next; +}; + +struct epic_rx_desc { + s16 status; + u16 rxlength; + u32 bufaddr; + u32 buflength; + u32 next; +}; + +struct epic_private { + char devname[8]; /* Used only for kernel debugging. */ + const char *product_name; + struct device *next_module; + + /* Rx and Rx rings here so that they remain paragraph aligned. */ + struct epic_rx_desc rx_ring[RX_RING_SIZE]; + struct epic_tx_desc tx_ring[TX_RING_SIZE]; + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + /* The addresses of receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + + /* Ring pointers. */ + unsigned int cur_rx, cur_tx; /* The next free ring entry */ + unsigned int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + + u8 pci_bus, pci_dev_fn; /* PCI bus location. */ + u16 chip_id; + + struct net_device_stats stats; + struct timer_list timer; /* Media selection timer. */ + unsigned char mc_filter[8]; + signed char phys[4]; /* MII device addresses. */ + unsigned int tx_full:1; /* The Tx queue is full. */ + unsigned int full_duplex:1; /* Current duplex setting. */ + unsigned int force_fd:1; /* Full-duplex operation requested. */ + unsigned int default_port:4; /* Last dev->if_port value. */ + unsigned int media2:4; /* Secondary monitored media port. */ + unsigned int medialock:1; /* Don't sense media type. */ + unsigned int mediasense:1; /* Media sensing in progress. */ + int pad0, pad1; /* Used for 8-byte alignment */ +}; + +/* Used to pass the full-duplex flag, etc. */ +#define MAX_UNITS 8 +static int full_duplex[MAX_UNITS] = {-1, -1, -1, -1, -1, -1, -1, -1}; +static int options[MAX_UNITS] = {-1, -1, -1, -1, -1, -1, -1, -1}; + +static int epic_open(struct device *dev); +static int read_eeprom(long ioaddr, int location); +static int mdio_read(long ioaddr, int phy_id, int location); +static void mdio_write(long ioaddr, int phy_id, int location, int value); +static void epic_restart(struct device *dev); +static void epic_timer(unsigned long data); +static void epic_tx_timeout(struct device *dev); +static void epic_init_ring(struct device *dev); +static int epic_start_xmit(struct sk_buff *skb, struct device *dev); +static int epic_rx(struct device *dev); +static void epic_interrupt(int irq, void *dev_instance, struct pt_regs *regs); +static int mii_ioctl(struct device *dev, struct ifreq *rq, int cmd); +static int epic_close(struct device *dev); +static struct net_device_stats *epic_get_stats(struct device *dev); +static void set_rx_mode(struct device *dev); + + +/* A list of all installed EPIC devices, for removing the driver module. */ +static struct device *root_epic_dev = NULL; + +#ifndef CARDBUS +int epic100_probe(struct device *dev) +{ + int cards_found = 0; + int chip_idx; + u16 pci_command, new_command; + unsigned char pci_bus, pci_device_fn; + +#ifdef PCI_SUPPORT_VER2 + struct pci_dev *pcidev = NULL; + while ((pcidev = pci_find_class(PCI_CLASS_NETWORK_ETHERNET << 8, pcidev)) + != NULL) { + long pci_ioaddr = pcidev->base_address[0] & ~3; + int vendor = pcidev->vendor; + int device = pcidev->device; + + for (chip_idx = 0; chip_tbl[chip_idx].vendor_id; chip_idx++) + if (vendor == chip_tbl[chip_idx].vendor_id + && (device & chip_tbl[chip_idx].device_id_mask) == + chip_tbl[chip_idx].device_id) + break; + if (chip_tbl[chip_idx].vendor_id == 0 /* Compiled out! */ + || check_region(pci_ioaddr, chip_tbl[chip_idx].io_size)) + continue; + pci_bus = pcidev->bus->number; + pci_device_fn = pcidev->devfn; +#else + int pci_index; + + if ( ! pcibios_present()) + return -ENODEV; + + for (pci_index = 0; pci_index < 0xff; pci_index++) { + u16 vendor, device; + u32 pci_ioaddr; + + if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8, + pci_index, &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + break; + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + + for (chip_idx = 0; chip_tbl[chip_idx].vendor_id; chip_idx++) + if (vendor == chip_tbl[chip_idx].vendor_id + && (device & chip_tbl[chip_idx].device_id_mask) == + chip_tbl[chip_idx].device_id) + break; + if (chip_tbl[chip_idx].vendor_id == 0) /* Compiled out! */ + continue; + + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + + if (check_region(pci_ioaddr, chip_tbl[chip_idx].io_size)) + continue; +#endif + + /* EPIC-specific code: Soft-reset the chip ere setting as master. */ + outl(0x0001, pci_ioaddr + GENCTL); + + /* Activate the card: fix for brain-damaged Win98 BIOSes. */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled Ethernet" + " device %4.4x-%4.4x." + " Updating PCI command %4.4x->%4.4x.\n", + vendor, device, pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + + dev = chip_tbl[chip_idx].probe1(pci_bus, pci_device_fn, + dev, cards_found); + + /* Check the latency timer. */ + if (dev) { + u8 pci_latency; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < chip_tbl[chip_idx].min_latency) { + printk(KERN_INFO " PCI latency timer (CFLT) value of %d is " + "unreasonably low, setting to %d.\n", pci_latency, + chip_tbl[chip_idx].min_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, + chip_tbl[chip_idx].min_latency); + } + dev = 0; + cards_found++; + } + } + + return cards_found ? 0 : -ENODEV; +} +#endif /* not CARDBUS */ + +static struct device *epic_probe1(int bus, int devfn, struct device *dev, + int card_idx) +{ + static int did_version = 0; /* Already printed version info. */ + struct epic_private *ep; + int i, option = 0, duplex = 0; + u16 chip_id; + u32 ioaddr; + + if (epic_debug > 0 && did_version++ == 0) + printk(KERN_INFO "%s", version); + + if (dev && dev->mem_start) { + option = dev->mem_start; + duplex = (dev->mem_start & 16) ? 1 : 0; + } else if (card_idx >= 0 && card_idx < MAX_UNITS) { + if (options[card_idx] >= 0) + option = options[card_idx]; + if (full_duplex[card_idx] >= 0) + duplex = full_duplex[card_idx]; + } + + dev = init_etherdev(dev, 0); + + { /* Grrrr, badly consider interface change. */ +#if defined(PCI_SUPPORT_VER2) + struct pci_dev *pdev = pci_find_slot(bus, devfn); + ioaddr = pdev->base_address[0] & ~3; + dev->irq = pdev->irq; + chip_id = pdev->device; +#else + u8 irq; + u32 ioaddr0; + pcibios_read_config_dword(bus, devfn, PCI_BASE_ADDRESS_0, &ioaddr0); + pcibios_read_config_byte(bus, devfn, PCI_INTERRUPT_LINE, &irq); + pcibios_read_config_word(bus, devfn, PCI_DEVICE_ID, &chip_id); + ioaddr = ioaddr0 & ~3; + dev->irq = irq; +#endif + } + + dev->base_addr = ioaddr; + printk(KERN_INFO "%s: SMC EPIC/100 (chip ID %4.4x) at %#3x, IRQ %d, ", + dev->name, chip_id, ioaddr, dev->irq); + + /* Bring the chip out of low-power mode. */ + outl(0x4200, ioaddr + GENCTL); + /* Magic?! If we don't set this bit the MII interface won't work. */ + outl(0x0008, ioaddr + TEST1); + + /* Turn on the MII transceiver. */ + outl(0x12, ioaddr + MIICfg); + if (chip_id == 6) + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + outl(0x0200, ioaddr + GENCTL); + + /* This could also be read from the EEPROM. */ + for (i = 0; i < 3; i++) + ((u16 *)dev->dev_addr)[i] = inw(ioaddr + LAN0 + i*4); + + for (i = 0; i < 5; i++) + printk("%2.2x:", dev->dev_addr[i]); + printk("%2.2x.\n", dev->dev_addr[i]); + + if (epic_debug > 1) { + printk(KERN_DEBUG "%s: EEPROM contents\n", dev->name); + for (i = 0; i < 64; i++) + printk(" %4.4x%s", read_eeprom(ioaddr, i), + i % 16 == 15 ? "\n" : ""); + } + + /* We do a request_region() to register /proc/ioports info. */ + request_region(ioaddr, EPIC_TOTAL_SIZE, "SMC EPIC/100"); + + /* The data structures must be quadword aligned. */ + ep = kmalloc(sizeof(*ep), GFP_KERNEL | GFP_DMA); + memset(ep, 0, sizeof(*ep)); + dev->priv = ep; + + ep->next_module = root_epic_dev; + root_epic_dev = dev; + + ep->pci_bus = bus; + ep->pci_dev_fn = devfn; + ep->chip_id = chip_id; + + /* Find the connected MII xcvrs. + Doing this in open() would allow detecting external xcvrs later, but + takes too much time. */ + { + int phy, phy_idx; + for (phy = 1, phy_idx = 0; phy < 32 && phy_idx < sizeof(ep->phys); + phy++) { + int mii_status = mdio_read(ioaddr, phy, 1); + if (mii_status != 0xffff && mii_status != 0x0000) { + ep->phys[phy_idx++] = phy; + printk(KERN_INFO "%s: MII transceiver #%d control " + "%4.4x status %4.4x.\n" + KERN_INFO "%s: Autonegotiation advertising %4.4x " + "link partner %4.4x.\n", + dev->name, phy, mdio_read(ioaddr, phy, 0), mii_status, + dev->name, mdio_read(ioaddr, phy, 4), + mdio_read(ioaddr, phy, 5)); + } + } + if (phy_idx == 0) { + printk(KERN_WARNING "%s: ***WARNING***: No MII transceiver found!\n", + dev->name); + /* Use the known PHY address of the EPII. */ + ep->phys[0] = 3; + } + } + + /* Turn off the MII xcvr (175 only!), leave the chip in low-power mode. */ + if (ep->chip_id == 6) + outl(inl(ioaddr + NVCTL) & ~0x483C, ioaddr + NVCTL); + outl(0x0008, ioaddr + GENCTL); + + /* The lower four bits are the media type. */ + ep->force_fd = duplex; + ep->default_port = option; + if (ep->default_port) + ep->medialock = 1; + + /* The Epic-specific entries in the device structure. */ + dev->open = &epic_open; + dev->hard_start_xmit = &epic_start_xmit; + dev->stop = &epic_close; + dev->get_stats = &epic_get_stats; + dev->set_multicast_list = &set_rx_mode; + dev->do_ioctl = &mii_ioctl; + + return dev; +} + +/* Serial EEPROM section. */ + +/* EEPROM_Ctrl bits. */ +#define EE_SHIFT_CLK 0x04 /* EEPROM shift clock. */ +#define EE_CS 0x02 /* EEPROM chip select. */ +#define EE_DATA_WRITE 0x08 /* EEPROM chip data in. */ +#define EE_WRITE_0 0x01 +#define EE_WRITE_1 0x09 +#define EE_DATA_READ 0x10 /* EEPROM chip data out. */ +#define EE_ENB (0x0001 | EE_CS) + +/* Delay between EEPROM clock transitions. + No extra delay is needed with 33Mhz PCI, but 66Mhz is untested. + */ + +#ifdef _LINUX_DELAY_H +#define eeprom_delay(nanosec) udelay(1) +#else +#define eeprom_delay(nanosec) do { ; } while (0) +#endif + +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD (5 << 6) +#define EE_READ64_CMD (6 << 6) +#define EE_READ256_CMD (6 << 8) +#define EE_ERASE_CMD (7 << 6) + +static int read_eeprom(long ioaddr, int location) +{ + int i; + int retval = 0; + long ee_addr = ioaddr + EECTL; + int read_cmd = location | + (inl(ee_addr) & 0x40) ? EE_READ64_CMD : EE_READ256_CMD; + + printk("EEctrl is %x.\n", inl(ee_addr)); + outl(EE_ENB & ~EE_CS, ee_addr); + outl(EE_ENB, ee_addr); + + /* Shift the read command bits out. */ + for (i = 12; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_WRITE_1 : EE_WRITE_0; + outl(EE_ENB | dataval, ee_addr); + eeprom_delay(100); + outl(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr); + eeprom_delay(150); + } + outl(EE_ENB, ee_addr); + + for (i = 16; i > 0; i--) { + outl(EE_ENB | EE_SHIFT_CLK, ee_addr); + eeprom_delay(100); + retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0); + outl(EE_ENB, ee_addr); + eeprom_delay(100); + } + + /* Terminate the EEPROM access. */ + outl(EE_ENB & ~EE_CS, ee_addr); + return retval; +} + +#define MII_READOP 1 +#define MII_WRITEOP 2 +static int mdio_read(long ioaddr, int phy_id, int location) +{ + int i; + + outl((phy_id << 9) | (location << 4) | MII_READOP, ioaddr + MIICtrl); + /* Typical operation takes < 50 ticks. */ + for (i = 4000; i > 0; i--) + if ((inl(ioaddr + MIICtrl) & MII_READOP) == 0) + return inw(ioaddr + MIIData); + return 0xffff; +} + +static void mdio_write(long ioaddr, int phy_id, int location, int value) +{ + int i; + + outw(value, ioaddr + MIIData); + outl((phy_id << 9) | (location << 4) | MII_WRITEOP, ioaddr + MIICtrl); + for (i = 10000; i > 0; i--) { + if ((inl(ioaddr + MIICtrl) & MII_WRITEOP) == 0) + break; + } + return; +} + + +static int +epic_open(struct device *dev) +{ + struct epic_private *ep = (struct epic_private *)dev->priv; + long ioaddr = dev->base_addr; + int i; + int mii_reg5; + ep->full_duplex = ep->force_fd; + + /* Soft reset the chip. */ + outl(0x4001, ioaddr + GENCTL); + + if (request_irq(dev->irq, &epic_interrupt, SA_SHIRQ, "SMC EPIC/100", dev)) + return -EAGAIN; + + MOD_INC_USE_COUNT; + + epic_init_ring(dev); + + outl(0x4000, ioaddr + GENCTL); + /* This next magic! line by Ken Yamaguchi.. ?? */ + outl(0x0008, ioaddr + TEST1); + + /* Pull the chip out of low-power mode, enable interrupts, and set for + PCI read multiple. The MIIcfg setting and strange write order are + required by the details of which bits are reset and the transceiver + wiring on the Ositech CardBus card. + */ + outl(0x12, ioaddr + MIICfg); + if (ep->chip_id == 6) + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + +#if defined(__powerpc__) || defined(__sparc__) /* Big endian */ + outl(0x0432 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#else + outl(0x0412 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#endif + + for (i = 0; i < 3; i++) + outl(((u16*)dev->dev_addr)[i], ioaddr + LAN0 + i*4); + + outl(TX_FIFO_THRESH, ioaddr + TxThresh); + + mii_reg5 = mdio_read(ioaddr, ep->phys[0], 5); + if (mii_reg5 != 0xffff) { + if ((mii_reg5 & 0x0100) || (mii_reg5 & 0x01C0) == 0x0040) + ep->full_duplex = 1; + else if (! (mii_reg5 & 0x4000)) + mdio_write(ioaddr, ep->phys[0], 0, 0x1200); + if (epic_debug > 1) + printk(KERN_INFO "%s: Setting %s-duplex based on MII xcvr %d" + " register read of %4.4x.\n", dev->name, + ep->full_duplex ? "full" : "half", ep->phys[0], mii_reg5); + } + + outl(ep->full_duplex ? 0x7F : 0x79, ioaddr + TxCtrl); + outl(virt_to_bus(ep->rx_ring), ioaddr + PRxCDAR); + outl(virt_to_bus(ep->tx_ring), ioaddr + PTxCDAR); + + /* Start the chip's Rx process. */ + set_rx_mode(dev); + outl(0x000A, ioaddr + COMMAND); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* Enable interrupts by setting the interrupt mask. */ + outl((ep->chip_id == 6 ? PCIBusErr175 : PCIBusErr170) + | CntFull | TxUnderrun | TxDone + | RxError | RxOverflow | RxFull | RxHeader | RxDone, + ioaddr + INTMASK); + + if (epic_debug > 1) + printk(KERN_DEBUG "%s: epic_open() ioaddr %lx IRQ %d status %4.4x " + "%s-duplex.\n", + dev->name, ioaddr, dev->irq, inl(ioaddr + GENCTL), + ep->full_duplex ? "full" : "half"); + + /* Set the timer to switch to check for link beat and perhaps switch + to an alternate media type. */ + init_timer(&ep->timer); + ep->timer.expires = RUN_AT((24*HZ)/10); /* 2.4 sec. */ + ep->timer.data = (unsigned long)dev; + ep->timer.function = &epic_timer; /* timer handler */ + add_timer(&ep->timer); + + return 0; +} + +/* Reset the chip to recover from a PCI transaction error. + This may occur at interrupt time. */ +static void epic_pause(struct device *dev) +{ + long ioaddr = dev->base_addr; + struct epic_private *ep = (struct epic_private *)dev->priv; + + /* Disable interrupts by clearing the interrupt mask. */ + outl(0x00000000, ioaddr + INTMASK); + /* Stop the chip's Tx and Rx DMA processes. */ + outw(0x0061, ioaddr + COMMAND); + + /* Update the error counts. */ + if (inw(ioaddr + COMMAND) != 0xffff) { + ep->stats.rx_missed_errors += inb(ioaddr + MPCNT); + ep->stats.rx_frame_errors += inb(ioaddr + ALICNT); + ep->stats.rx_crc_errors += inb(ioaddr + CRCCNT); + } + + /* Remove the packets on the Rx queue. */ + epic_rx(dev); +} + +static void epic_restart(struct device *dev) +{ + long ioaddr = dev->base_addr; + struct epic_private *ep = (struct epic_private *)dev->priv; + int i; + + printk(KERN_DEBUG "%s: Restarting the EPIC chip, Rx %d/%d Tx %d/%d.\n", + dev->name, ep->cur_rx, ep->dirty_rx, ep->dirty_tx, ep->cur_tx); + /* Soft reset the chip. */ + outl(0x0001, ioaddr + GENCTL); + + udelay(1); + /* Duplicate code from epic_open(). */ + outl(0x0008, ioaddr + TEST1); + +#if defined(__powerpc__) /* Big endian */ + outl(0x0432 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#else + outl(0x0412 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#endif + outl(0x12, ioaddr + MIICfg); + if (ep->chip_id == 6) + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + + for (i = 0; i < 3; i++) + outl(((u16*)dev->dev_addr)[i], ioaddr + LAN0 + i*4); + + outl(TX_FIFO_THRESH, ioaddr + TxThresh); + outl(ep->full_duplex ? 0x7F : 0x79, ioaddr + TxCtrl); + outl(virt_to_bus(&ep->rx_ring[ep->cur_rx%RX_RING_SIZE]), ioaddr + PRxCDAR); + outl(virt_to_bus(&ep->tx_ring[ep->dirty_tx%TX_RING_SIZE]), + ioaddr + PTxCDAR); + + /* Start the chip's Rx process. */ + set_rx_mode(dev); + outl(0x000A, ioaddr + COMMAND); + + /* Enable interrupts by setting the interrupt mask. */ + outl((ep->chip_id == 6 ? PCIBusErr175 : PCIBusErr170) + | CntFull | TxUnderrun | TxDone + | RxError | RxOverflow | RxFull | RxHeader | RxDone, + ioaddr + INTMASK); + printk(KERN_DEBUG "%s: epic_restart() done, cmd status %4.4x, ctl %4.4x" + " interrupt %4.4x.\n", + dev->name, inl(ioaddr + COMMAND), inl(ioaddr + GENCTL), + inl(ioaddr + INTSTAT)); + return; +} + +static void epic_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct epic_private *ep = (struct epic_private *)dev->priv; + long ioaddr = dev->base_addr; + int next_tick = 0; + int mii_reg5 = mdio_read(ioaddr, ep->phys[0], 5); + + if (epic_debug > 3) { + printk(KERN_DEBUG "%s: Media selection tick, Tx status %8.8x.\n", + dev->name, inl(ioaddr + TxSTAT)); + printk(KERN_DEBUG "%s: Other registers are IntMask %4.4x " + "IntStatus %4.4x RxStatus %4.4x.\n", + dev->name, inl(ioaddr + INTMASK), inl(ioaddr + INTSTAT), + inl(ioaddr + RxSTAT)); + } + if (! ep->force_fd && mii_reg5 != 0xffff) { + int duplex = (mii_reg5&0x0100) || (mii_reg5 & 0x01C0) == 0x0040; + if (ep->full_duplex != duplex) { + ep->full_duplex = duplex; + printk(KERN_INFO "%s: Setting %s-duplex based on MII #%d link" + " partner capability of %4.4x.\n", dev->name, + ep->full_duplex ? "full" : "half", ep->phys[0], mii_reg5); + outl(ep->full_duplex ? 0x7F : 0x79, ioaddr + TxCtrl); + } + next_tick = 60*HZ; + } + + if (next_tick) { + ep->timer.expires = RUN_AT(next_tick); + add_timer(&ep->timer); + } +} + +static void epic_tx_timeout(struct device *dev) +{ + struct epic_private *ep = (struct epic_private *)dev->priv; + long ioaddr = dev->base_addr; + + if (epic_debug > 0) { + printk(KERN_WARNING "%s: Transmit timeout using MII device, " + "Tx status %4.4x.\n", + dev->name, inw(ioaddr + TxSTAT)); + if (epic_debug > 1) { + printk(KERN_DEBUG "%s: Tx indices: dirty_tx %d, cur_tx %d.\n", + dev->name, ep->dirty_tx, ep->cur_tx); + } + } + if (inw(ioaddr + TxSTAT) & 0x10) { /* Tx FIFO underflow. */ + ep->stats.tx_fifo_errors++; + /* Restart the transmit process. */ + outl(0x0080, ioaddr + COMMAND); + } + + /* Perhaps stop and restart the chip's Tx processes . */ + /* Trigger a transmit demand. */ + outl(0x0004, dev->base_addr + COMMAND); + + dev->trans_start = jiffies; + ep->stats.tx_errors++; + return; +} + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void +epic_init_ring(struct device *dev) +{ + struct epic_private *ep = (struct epic_private *)dev->priv; + int i; + + ep->tx_full = 0; + ep->cur_rx = ep->cur_tx = 0; + ep->dirty_rx = ep->dirty_tx = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + ep->rx_ring[i].status = 0x8000; /* Owned by Epic chip */ + ep->rx_ring[i].buflength = PKT_BUF_SZ; + { + /* Note the receive buffer must be longword aligned. + dev_alloc_skb() provides 16 byte alignment. But do *not* + use skb_reserve() to align the IP header! */ + struct sk_buff *skb; + skb = dev_alloc_skb(PKT_BUF_SZ); + ep->rx_skbuff[i] = skb; + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + ep->rx_ring[i].bufaddr = virt_to_bus(skb->tail); + } + ep->rx_ring[i].next = virt_to_bus(&ep->rx_ring[i+1]); + } + /* Mark the last entry as wrapping the ring. */ + ep->rx_ring[i-1].next = virt_to_bus(&ep->rx_ring[0]); + + /* The Tx buffer descriptor is filled in as needed, but we + do need to clear the ownership bit. */ + for (i = 0; i < TX_RING_SIZE; i++) { + ep->tx_skbuff[i] = 0; + ep->tx_ring[i].status = 0x0000; + ep->tx_ring[i].next = virt_to_bus(&ep->tx_ring[i+1]); + } + ep->tx_ring[i-1].next = virt_to_bus(&ep->tx_ring[0]); +} + +static int +epic_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct epic_private *ep = (struct epic_private *)dev->priv; + int entry; + u32 flag; + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start < TX_TIMEOUT) + return 1; + epic_tx_timeout(dev); + return 1; + } + + /* Caution: the write order is important here, set the base address + with the "ownership" bits last. */ + + /* Calculate the next Tx descriptor entry. */ + entry = ep->cur_tx % TX_RING_SIZE; + + ep->tx_skbuff[entry] = skb; + ep->tx_ring[entry].txlength = (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN); + ep->tx_ring[entry].bufaddr = virt_to_bus(skb->data); + ep->tx_ring[entry].buflength = skb->len; + + if (ep->cur_tx - ep->dirty_tx < TX_RING_SIZE/2) {/* Typical path */ + flag = 0x10; /* No interrupt */ + clear_bit(0, (void*)&dev->tbusy); + } else if (ep->cur_tx - ep->dirty_tx == TX_RING_SIZE/2) { + flag = 0x14; /* Tx-done intr. */ + clear_bit(0, (void*)&dev->tbusy); + } else if (ep->cur_tx - ep->dirty_tx < TX_RING_SIZE - 2) { + flag = 0x10; /* No Tx-done intr. */ + clear_bit(0, (void*)&dev->tbusy); + } else { + /* Leave room for two additional entries. */ + flag = 0x14; /* Tx-done intr. */ + ep->tx_full = 1; + } + + ep->tx_ring[entry].control = flag; + ep->tx_ring[entry].status = 0x8000; /* Pass ownership to the chip. */ + ep->cur_tx++; + /* Trigger an immediate transmit demand. */ + outl(0x0004, dev->base_addr + COMMAND); + + dev->trans_start = jiffies; + if (epic_debug > 4) + printk(KERN_DEBUG "%s: Queued Tx packet size %d to slot %d, " + "flag %2.2x Tx status %8.8x.\n", + dev->name, (int)skb->len, entry, flag, + inl(dev->base_addr + TxSTAT)); + + return 0; +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void epic_interrupt(int irq, void *dev_instance, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_instance; + struct epic_private *ep; + int status, ioaddr, boguscnt = max_interrupt_work; + + ioaddr = dev->base_addr; + ep = (struct epic_private *)dev->priv; + +#if defined(__i386__) + /* A lock to prevent simultaneous entry bug on Intel SMP machines. */ + if (test_and_set_bit(0, (void*)&dev->interrupt)) { + printk(KERN_ERR"%s: SMP simultaneous entry of an interrupt handler.\n", + dev->name); + dev->interrupt = 0; /* Avoid halting machine. */ + return; + } +#else + if (dev->interrupt) { + printk(KERN_ERR "%s: Re-entering the interrupt handler.\n", dev->name); + return; + } + dev->interrupt = 1; +#endif + + do { + status = inl(ioaddr + INTSTAT); + /* Acknowledge all of the current interrupt sources ASAP. */ + outl(status & 0x00007fff, ioaddr + INTSTAT); + + if (epic_debug > 4) + printk("%s: interrupt interrupt=%#8.8x new intstat=%#8.8x.\n", + dev->name, status, inl(ioaddr + INTSTAT)); + + if ((status & IntrSummary) == 0) + break; + + if (status & (RxDone | RxStarted | RxEarlyWarn)) + epic_rx(dev); + + if (status & (TxEmpty | TxDone)) { + int dirty_tx; + + for (dirty_tx = ep->dirty_tx; dirty_tx < ep->cur_tx; dirty_tx++) { + int entry = dirty_tx % TX_RING_SIZE; + int txstatus = ep->tx_ring[entry].status; + + if (txstatus < 0) + break; /* It still hasn't been Txed */ + + if ( ! (txstatus & 0x0001)) { + /* There was an major error, log it. */ +#ifndef final_version + if (epic_debug > 1) + printk("%s: Transmit error, Tx status %8.8x.\n", + dev->name, txstatus); +#endif + ep->stats.tx_errors++; + if (txstatus & 0x1050) ep->stats.tx_aborted_errors++; + if (txstatus & 0x0008) ep->stats.tx_carrier_errors++; + if (txstatus & 0x0040) ep->stats.tx_window_errors++; + if (txstatus & 0x0010) ep->stats.tx_fifo_errors++; +#ifdef ETHER_STATS + if (txstatus & 0x1000) ep->stats.collisions16++; +#endif + } else { +#ifdef ETHER_STATS + if ((txstatus & 0x0002) != 0) ep->stats.tx_deferred++; +#endif + ep->stats.collisions += (txstatus >> 8) & 15; + ep->stats.tx_packets++; + } + + /* Free the original skb. */ + DEV_FREE_SKB(ep->tx_skbuff[entry]); + ep->tx_skbuff[entry] = 0; + } + +#ifndef final_version + if (ep->cur_tx - dirty_tx > TX_RING_SIZE) { + printk("%s: Out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dev->name, dirty_tx, ep->cur_tx, ep->tx_full); + dirty_tx += TX_RING_SIZE; + } +#endif + + if (ep->tx_full && dev->tbusy + && dirty_tx > ep->cur_tx - TX_RING_SIZE + 2) { + /* The ring is no longer full, clear tbusy. */ + ep->tx_full = 0; + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + + ep->dirty_tx = dirty_tx; + } + + /* Check uncommon events all at once. */ + if (status & (CntFull | TxUnderrun | RxOverflow | + PCIBusErr170 | PCIBusErr175)) { + if (status == 0xffffffff) /* Chip failed or removed (CardBus). */ + break; + /* Always update the error counts to avoid overhead later. */ + ep->stats.rx_missed_errors += inb(ioaddr + MPCNT); + ep->stats.rx_frame_errors += inb(ioaddr + ALICNT); + ep->stats.rx_crc_errors += inb(ioaddr + CRCCNT); + + if (status & TxUnderrun) { /* Tx FIFO underflow. */ + ep->stats.tx_fifo_errors++; + outl(1536, ioaddr + TxThresh); + /* Restart the transmit process. */ + outl(0x0080, ioaddr + COMMAND); + } + if (status & RxOverflow) { /* Missed a Rx frame. */ + ep->stats.rx_errors++; + } + if (status & PCIBusErr170) { + printk(KERN_ERR "%s: PCI Bus Error! EPIC status %4.4x.\n", + dev->name, status); + epic_pause(dev); + epic_restart(dev); + } + /* Clear all error sources. */ + outl(status & 0x7f18, ioaddr + INTSTAT); + } + if (--boguscnt < 0) { + printk(KERN_ERR "%s: Too much work at interrupt, " + "IntrStatus=0x%8.8x.\n", + dev->name, status); + /* Clear all interrupt sources. */ + outl(0x0001ffff, ioaddr + INTSTAT); + break; + } + } while (1); + + if (epic_debug > 3) + printk(KERN_DEBUG "%s: exiting interrupt, intr_status=%#4.4x.\n", + dev->name, inl(ioaddr + INTSTAT)); + +#if defined(__i386__) + clear_bit(0, (void*)&dev->interrupt); +#else + dev->interrupt = 0; +#endif + return; +} + +static int epic_rx(struct device *dev) +{ + struct epic_private *ep = (struct epic_private *)dev->priv; + int entry = ep->cur_rx % RX_RING_SIZE; + int work_done = 0; + + if (epic_debug > 4) + printk(KERN_DEBUG " In epic_rx(), entry %d %8.8x.\n", entry, + ep->rx_ring[entry].status); + /* If we own the next entry, it's a new packet. Send it up. */ + while (ep->rx_ring[entry].status >= 0 && ep->rx_skbuff[entry]) { + int status = ep->rx_ring[entry].status; + + if (epic_debug > 4) + printk(KERN_DEBUG " epic_rx() status was %8.8x.\n", status); + if (status & 0x2006) { + if (status & 0x2000) { + printk(KERN_WARNING "%s: Oversized Ethernet frame spanned " + "multiple buffers, status %4.4x!\n", dev->name, status); + ep->stats.rx_length_errors++; + } else if (status & 0x0006) + /* Rx Frame errors are counted in hardware. */ + ep->stats.rx_errors++; + } else { + /* Malloc up new buffer, compatible with net-2e. */ + /* Omit the four octet CRC from the length. */ + short pkt_len = ep->rx_ring[entry].rxlength - 4; + struct sk_buff *skb; + + /* Check if the packet is long enough to accept without copying + to a minimally-sized skbuff. */ + if (pkt_len < rx_copybreak + && (skb = dev_alloc_skb(pkt_len + 2)) != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); /* 16 byte align the IP header */ +#if 1 /* USE_IP_COPYSUM */ + eth_copy_and_sum(skb, bus_to_virt(ep->rx_ring[entry].bufaddr), + pkt_len, 0); + skb_put(skb, pkt_len); +#else + memcpy(skb_put(skb, pkt_len), + bus_to_virt(ep->rx_ring[entry].bufaddr), pkt_len); +#endif + } else { + skb_put(skb = ep->rx_skbuff[entry], pkt_len); + ep->rx_skbuff[entry] = NULL; + } + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + ep->stats.rx_packets++; + } + work_done++; + entry = (++ep->cur_rx) % RX_RING_SIZE; + } + + /* Refill the Rx ring buffers. */ + for (; ep->cur_rx - ep->dirty_rx > 0; ep->dirty_rx++) { + entry = ep->dirty_rx % RX_RING_SIZE; + if (ep->rx_skbuff[entry] == NULL) { + struct sk_buff *skb; + skb = ep->rx_skbuff[entry] = dev_alloc_skb(PKT_BUF_SZ); + if (skb == NULL) + break; + skb->dev = dev; /* Mark as being used by this device. */ + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + ep->rx_ring[entry].bufaddr = virt_to_bus(skb->tail); + work_done++; + } + ep->rx_ring[entry].status = 0x8000; + } + return work_done; +} + +static int epic_close(struct device *dev) +{ + long ioaddr = dev->base_addr; + struct epic_private *ep = (struct epic_private *)dev->priv; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (epic_debug > 1) + printk(KERN_DEBUG "%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, inl(ioaddr + INTSTAT)); + + /* Disable interrupts by clearing the interrupt mask. */ + outl(0x00000000, ioaddr + INTMASK); + /* Stop the chip's Tx and Rx DMA processes. */ + outw(0x0061, ioaddr + COMMAND); + + /* Update the error counts. */ + ep->stats.rx_missed_errors += inb(ioaddr + MPCNT); + ep->stats.rx_frame_errors += inb(ioaddr + ALICNT); + ep->stats.rx_crc_errors += inb(ioaddr + CRCCNT); + + del_timer(&ep->timer); + + free_irq(dev->irq, dev); + + /* Free all the skbuffs in the Rx queue. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = ep->rx_skbuff[i]; + ep->rx_skbuff[i] = 0; + ep->rx_ring[i].status = 0; /* Not owned by Epic chip. */ + ep->rx_ring[i].buflength = 0; + ep->rx_ring[i].bufaddr = 0xBADF00D0; /* An invalid address. */ + if (skb) { +#if LINUX_VERSION_CODE < 0x20100 + skb->free = 1; +#endif + DEV_FREE_SKB(skb); + } + } + for (i = 0; i < TX_RING_SIZE; i++) { + if (ep->tx_skbuff[i]) + DEV_FREE_SKB(ep->tx_skbuff[i]); + ep->tx_skbuff[i] = 0; + } + + + /* Green! Leave the chip in low-power mode. */ + outl(0x0008, ioaddr + GENCTL); + + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct net_device_stats *epic_get_stats(struct device *dev) +{ + struct epic_private *ep = (struct epic_private *)dev->priv; + long ioaddr = dev->base_addr; + + if (dev->start) { + /* Update the error counts. */ + ep->stats.rx_missed_errors += inb(ioaddr + MPCNT); + ep->stats.rx_frame_errors += inb(ioaddr + ALICNT); + ep->stats.rx_crc_errors += inb(ioaddr + CRCCNT); + } + + return &ep->stats; +} + +/* Set or clear the multicast filter for this adaptor. + Note that we only use exclusion around actually queueing the + new frame, not around filling ep->setup_frame. This is non-deterministic + when re-entered but still correct. */ + +/* The little-endian AUTODIN II ethernet CRC calculation. + N.B. Do not use for bulk data, use a table-based routine instead. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial_le = 0xedb88320U; +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + + +static void set_rx_mode(struct device *dev) +{ + long ioaddr = dev->base_addr; + struct epic_private *ep = (struct epic_private *)dev->priv; + unsigned char mc_filter[8]; /* Multicast hash filter */ + int i; + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + outl(0x002C, ioaddr + RxCtrl); + /* Unconditionally log net taps. */ + printk(KERN_INFO "%s: Promiscuous mode enabled.\n", dev->name); + memset(mc_filter, 0xff, sizeof(mc_filter)); + } else if ((dev->mc_count > 0) || (dev->flags & IFF_ALLMULTI)) { + /* There is apparently a chip bug, so the multicast filter + is never enabled. */ + /* Too many to filter perfectly -- accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + outl(0x000C, ioaddr + RxCtrl); + } else if (dev->mc_count == 0) { + outl(0x0004, ioaddr + RxCtrl); + return; + } else { /* Never executed, for now. */ + struct dev_mc_list *mclist; + + memset(mc_filter, 0, sizeof(mc_filter)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + set_bit(ether_crc_le(ETH_ALEN, mclist->dmi_addr) & 0x3f, + mc_filter); + } + /* ToDo: perhaps we need to stop the Tx and Rx process here? */ + if (memcmp(mc_filter, ep->mc_filter, sizeof(mc_filter))) { + for (i = 0; i < 4; i++) + outw(((u16 *)mc_filter)[i], ioaddr + MC0 + i*4); + memcpy(ep->mc_filter, mc_filter, sizeof(mc_filter)); + } + return; +} + +static int mii_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + long ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_data; + + switch(cmd) { + case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */ + data[0] = ((struct epic_private *)dev->priv)->phys[0] & 0x1f; + /* Fall Through */ + case SIOCDEVPRIVATE+1: /* Read the specified MII register. */ + if (! dev->start) { + outl(0x0200, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + } + data[3] = mdio_read(ioaddr, data[0] & 0x1f, data[1] & 0x1f); + if (! dev->start) { +#ifdef notdef + outl(0x0008, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x483C) | 0x0000, ioaddr + NVCTL); +#endif + } + return 0; + case SIOCDEVPRIVATE+2: /* Write the specified MII register */ + if (!suser()) + return -EPERM; + if (! dev->start) { + outl(0x0200, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + } + mdio_write(ioaddr, data[0] & 0x1f, data[1] & 0x1f, data[2]); + if (! dev->start) { +#ifdef notdef + outl(0x0008, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x483C) | 0x0000, ioaddr + NVCTL); +#endif + } + return 0; + default: + return -EOPNOTSUPP; + } +} + + +#ifdef CARDBUS + +#include <pcmcia/driver_ops.h> + +static dev_node_t *epic_attach(dev_locator_t *loc) +{ + struct device *dev; + u16 dev_id; + u32 io; + u8 bus, devfn, irq; + + if (loc->bus != LOC_PCI) return NULL; + bus = loc->b.pci.bus; devfn = loc->b.pci.devfn; + printk(KERN_INFO "epic_attach(bus %d, function %d)\n", bus, devfn); + pcibios_read_config_dword(bus, devfn, PCI_BASE_ADDRESS_0, &io); + pcibios_read_config_byte(bus, devfn, PCI_INTERRUPT_LINE, &irq); + pcibios_read_config_word(bus, devfn, PCI_DEVICE_ID, &dev_id); + io &= ~3; + dev = epic_probe1(bus, devfn, NULL, -1); + if (dev) { + dev_node_t *node = kmalloc(sizeof(dev_node_t), GFP_KERNEL); + strcpy(node->dev_name, dev->name); + node->major = node->minor = 0; + node->next = NULL; + MOD_INC_USE_COUNT; + return node; + } + return NULL; +} + +static void epic_suspend(dev_node_t *node) +{ + struct device **devp, **next; + printk(KERN_INFO "epic_suspend(%s)\n", node->dev_name); + for (devp = &root_epic_dev; *devp; devp = next) { + next = &((struct epic_private *)(*devp)->priv)->next_module; + if (strcmp((*devp)->name, node->dev_name) == 0) break; + } + if (*devp) { + long ioaddr = (*devp)->base_addr; + epic_pause(*devp); + /* Put the chip into low-power mode. */ + outl(0x0008, ioaddr + GENCTL); + } +} +static void epic_resume(dev_node_t *node) +{ + struct device **devp, **next; + printk(KERN_INFO "epic_resume(%s)\n", node->dev_name); + for (devp = &root_epic_dev; *devp; devp = next) { + next = &((struct epic_private *)(*devp)->priv)->next_module; + if (strcmp((*devp)->name, node->dev_name) == 0) break; + } + if (*devp) { + epic_restart(*devp); + } +} +static void epic_detach(dev_node_t *node) +{ + struct device **devp, **next; + printk(KERN_INFO "epic_detach(%s)\n", node->dev_name); + for (devp = &root_epic_dev; *devp; devp = next) { + next = &((struct epic_private *)(*devp)->priv)->next_module; + if (strcmp((*devp)->name, node->dev_name) == 0) break; + } + if (*devp) { + unregister_netdev(*devp); + kfree(*devp); + *devp = *next; + kfree(node); + MOD_DEC_USE_COUNT; + } +} + +struct driver_operations epic_ops = { + "epic_cb", epic_attach, epic_suspend, epic_resume, epic_detach +}; + +#endif /* Cardbus support */ + + +#ifdef MODULE + +/* An additional parameter that may be passed in... */ +static int debug = -1; + +int +init_module(void) +{ + if (debug >= 0) + epic_debug = debug; + +#ifdef CARDBUS + register_driver(&epic_ops); + return 0; +#else + return epic100_probe(0); +#endif +} + +void +cleanup_module(void) +{ + struct device *next_dev; + +#ifdef CARDBUS + unregister_driver(&epic_ops); +#endif + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_epic_dev) { + next_dev = ((struct epic_private *)root_epic_dev->priv)->next_module; + unregister_netdev(root_epic_dev); + release_region(root_epic_dev->base_addr, EPIC_TOTAL_SIZE); + kfree(root_epic_dev); + root_epic_dev = next_dev; + } +} + +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c epic100.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * cardbus-compile-command: "gcc -DCARDBUS -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c epic100.c -o epic_cb.o -I/usr/src/pcmcia-cs-3.0.5/include/" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/eth16i.c b/linux/src/drivers/net/eth16i.c new file mode 100644 index 00000000..59921bfc --- /dev/null +++ b/linux/src/drivers/net/eth16i.c @@ -0,0 +1,1604 @@ +/* eth16i.c An ICL EtherTeam 16i and 32 EISA ethernet driver for Linux + + Written 1994-1998 by Mika Kuoppala + + Copyright (C) 1994-1998 by Mika Kuoppala + Based on skeleton.c and heavily on at1700.c by Donald Becker + + This software may be used and distributed according to the terms + of the GNU Public Licence, incorporated herein by reference. + + The author may be reached as miku@iki.fi + + This driver supports following cards : + - ICL EtherTeam 16i + - ICL EtherTeam 32 EISA + (Uses true 32 bit transfers rather than 16i compability mode) + + Example Module usage: + insmod eth16i.o ioaddr=0x2a0 mediatype=bnc + + mediatype can be one of the following: bnc,tp,dix,auto,eprom + + 'auto' will try to autoprobe mediatype. + 'eprom' will use whatever type defined in eprom. + + I have benchmarked driver with PII/300Mhz as a ftp client + and 486/33Mhz as a ftp server. Top speed was 1128.37 kilobytes/sec. + + Sources: + - skeleton.c a sample network driver core for linux, + written by Donald Becker <becker@CESDIS.gsfc.nasa.gov> + - at1700.c a driver for Allied Telesis AT1700, written + by Donald Becker. + - e16iSRV.asm a Netware 3.X Server Driver for ICL EtherTeam16i + written by Markku Viima + - The Fujitsu MB86965 databook. + + Author thanks following persons due to their valueble assistance: + Markku Viima (ICL) + Ari Valve (ICL) + Donald Becker + Kurt Huwig <kurt@huwig.de> + + Revision history: + + Version Date Description + + 0.01 15.12-94 Initial version (card detection) + 0.02 23.01-95 Interrupt is now hooked correctly + 0.03 01.02-95 Rewrote initialization part + 0.04 07.02-95 Base skeleton done... + Made a few changes to signature checking + to make it a bit reliable. + - fixed bug in tx_buf mapping + - fixed bug in initialization (DLC_EN + wasn't enabled when initialization + was done.) + 0.05 08.02-95 If there were more than one packet to send, + transmit was jammed due to invalid + register write...now fixed + 0.06 19.02-95 Rewrote interrupt handling + 0.07 13.04-95 Wrote EEPROM read routines + Card configuration now set according to + data read from EEPROM + 0.08 23.06-95 Wrote part that tries to probe used interface + port if AUTO is selected + + 0.09 01.09-95 Added module support + + 0.10 04.09-95 Fixed receive packet allocation to work + with kernels > 1.3.x + + 0.20 20.09-95 Added support for EtherTeam32 EISA + + 0.21 17.10-95 Removed the unnecessary extern + init_etherdev() declaration. Some + other cleanups. + + 0.22 22.02-96 Receive buffer was not flushed + correctly when faulty packet was + received. Now fixed. + + 0.23 26.02-96 Made resetting the adapter + more reliable. + + 0.24 27.02-96 Rewrote faulty packet handling in eth16i_rx + + 0.25 22.05-96 kfree() was missing from cleanup_module. + + 0.26 11.06-96 Sometimes card was not found by + check_signature(). Now made more reliable. + + 0.27 23.06-96 Oops. 16 consecutive collisions halted + adapter. Now will try to retransmit + MAX_COL_16 times before finally giving up. + + 0.28 28.10-97 Added dev_id parameter (NULL) for free_irq + + 0.29 29.10-97 Multiple card support for module users + + 0.30 30.10-97 Fixed irq allocation bug. + (request_irq moved from probe to open) + + 0.30a 21.08-98 Card detection made more relaxed. Driver + had problems with some TCP/IP-PROM boots + to find the card. Suggested by + Kurt Huwig <kurt@huwig.de> + + 0.31 28.08-98 Media interface port can now be selected + with module parameters or kernel + boot parameters. + + 0.32 31.08-98 IRQ was never freed if open/close + pair wasn't called. Now fixed. + + 0.33 10.09-98 When eth16i_open() was called after + eth16i_close() chip never recovered. + Now more shallow reset is made on + close. + + Bugs: + In some cases the media interface autoprobing code doesn't find + the correct interface type. In this case you can + manually choose the interface type in DOS with E16IC.EXE which is + configuration software for EtherTeam16i and EtherTeam32 cards. + This is also true for IRQ setting. You cannot use module + parameter to configure IRQ of the card (yet). + + To do: + - Real multicast support + - Rewrite the media interface autoprobing code. Its _horrible_ ! + - Possibly merge all the MB86965 specific code to external + module for use by eth16.c and Donald's at1700.c + - IRQ configuration with module parameter. I will do + this when i will get enough info about setting + irq without configuration utility. +*/ + +static char *version = + "eth16i.c: v0.33 10-09-98 Mika Kuoppala (miku@iki.fi)\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/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#ifndef LINUX_VERSION_CODE +#include <linux/version.h> +#endif + +#if LINUX_VERSION_CODE >= 0x20123 +#include <linux/init.h> +#else +#define __init +#define __initdata +#define __initfunc(x) x +#endif + +#if LINUX_VERSION_CODE < 0x20138 +#define test_and_set_bit(val,addr) set_bit(val,addr) +#endif + +#if LINUX_VERSION_CODE < 0x020100 +typedef struct enet_statistics eth16i_stats_type; +#else +typedef struct net_device_stats eth16i_stats_type; +#endif + +/* Few macros */ +#define BIT(a) ( (1 << (a)) ) +#define BITSET(ioaddr, bnum) ((outb(((inb(ioaddr)) | (bnum)), ioaddr))) +#define BITCLR(ioaddr, bnum) ((outb(((inb(ioaddr)) & (~(bnum))), ioaddr))) + +/* This is the I/O address space for Etherteam 16i adapter. */ +#define ETH16I_IO_EXTENT 32 + +/* Ticks before deciding that transmit has timed out */ +#define TX_TIMEOUT (400*HZ/1000) + +/* Maximum loop count when receiving packets */ +#define MAX_RX_LOOP 20 + +/* Some interrupt masks */ +#define ETH16I_INTR_ON 0xef8a /* Higher is receive mask */ +#define ETH16I_INTR_OFF 0x0000 + +/* Buffers header status byte meanings */ +#define PKT_GOOD BIT(5) +#define PKT_GOOD_RMT BIT(4) +#define PKT_SHORT BIT(3) +#define PKT_ALIGN_ERR BIT(2) +#define PKT_CRC_ERR BIT(1) +#define PKT_RX_BUF_OVERFLOW BIT(0) + +/* Transmit status register (DLCR0) */ +#define TX_STATUS_REG 0 +#define TX_DONE BIT(7) +#define NET_BUSY BIT(6) +#define TX_PKT_RCD BIT(5) +#define CR_LOST BIT(4) +#define TX_JABBER_ERR BIT(3) +#define COLLISION BIT(2) +#define COLLISIONS_16 BIT(1) + +/* Receive status register (DLCR1) */ +#define RX_STATUS_REG 1 +#define RX_PKT BIT(7) /* Packet received */ +#define BUS_RD_ERR BIT(6) +#define SHORT_PKT_ERR BIT(3) +#define ALIGN_ERR BIT(2) +#define CRC_ERR BIT(1) +#define RX_BUF_OVERFLOW BIT(0) + +/* Transmit Interrupt Enable Register (DLCR2) */ +#define TX_INTR_REG 2 +#define TX_INTR_DONE BIT(7) +#define TX_INTR_COL BIT(2) +#define TX_INTR_16_COL BIT(1) + +/* Receive Interrupt Enable Register (DLCR3) */ +#define RX_INTR_REG 3 +#define RX_INTR_RECEIVE BIT(7) +#define RX_INTR_SHORT_PKT BIT(3) +#define RX_INTR_CRC_ERR BIT(1) +#define RX_INTR_BUF_OVERFLOW BIT(0) + +/* Transmit Mode Register (DLCR4) */ +#define TRANSMIT_MODE_REG 4 +#define LOOPBACK_CONTROL BIT(1) +#define CONTROL_OUTPUT BIT(2) + +/* Receive Mode Register (DLCR5) */ +#define RECEIVE_MODE_REG 5 +#define RX_BUFFER_EMPTY BIT(6) +#define ACCEPT_BAD_PACKETS BIT(5) +#define RECEIVE_SHORT_ADDR BIT(4) +#define ACCEPT_SHORT_PACKETS BIT(3) +#define REMOTE_RESET BIT(2) + +#define ADDRESS_FILTER_MODE BIT(1) | BIT(0) +#define REJECT_ALL 0 +#define ACCEPT_ALL 3 +#define MODE_1 1 /* NODE ID, BC, MC, 2-24th bit */ +#define MODE_2 2 /* NODE ID, BC, MC, Hash Table */ + +/* Configuration Register 0 (DLCR6) */ +#define CONFIG_REG_0 6 +#define DLC_EN BIT(7) +#define SRAM_CYCLE_TIME_100NS BIT(6) +#define SYSTEM_BUS_WIDTH_8 BIT(5) /* 1 = 8bit, 0 = 16bit */ +#define BUFFER_WIDTH_8 BIT(4) /* 1 = 8bit, 0 = 16bit */ +#define TBS1 BIT(3) +#define TBS0 BIT(2) +#define SRAM_BS1 BIT(1) /* 00=8kb, 01=16kb */ +#define SRAM_BS0 BIT(0) /* 10=32kb, 11=64kb */ + +#ifndef ETH16I_TX_BUF_SIZE /* 0 = 2kb, 1 = 4kb */ +#define ETH16I_TX_BUF_SIZE 3 /* 2 = 8kb, 3 = 16kb */ +#endif +#define TX_BUF_1x2048 0 +#define TX_BUF_2x2048 1 +#define TX_BUF_2x4098 2 +#define TX_BUF_2x8192 3 + +/* Configuration Register 1 (DLCR7) */ +#define CONFIG_REG_1 7 +#define POWERUP BIT(5) + +/* Transmit start register */ +#define TRANSMIT_START_REG 10 +#define TRANSMIT_START_RB 2 +#define TX_START BIT(7) /* Rest of register bit indicate*/ + /* number of packets in tx buffer*/ +/* Node ID registers (DLCR8-13) */ +#define NODE_ID_0 8 +#define NODE_ID_RB 0 + +/* Hash Table registers (HT8-15) */ +#define HASH_TABLE_0 8 +#define HASH_TABLE_RB 1 + +/* Buffer memory ports */ +#define BUFFER_MEM_PORT_LB 8 +#define DATAPORT BUFFER_MEM_PORT_LB +#define BUFFER_MEM_PORT_HB 9 + +/* 16 Collision control register (BMPR11) */ +#define COL_16_REG 11 +#define HALT_ON_16 0x00 +#define RETRANS_AND_HALT_ON_16 0x02 + +/* Maximum number of attempts to send after 16 concecutive collisions */ +#define MAX_COL_16 10 + +/* DMA Burst and Transceiver Mode Register (BMPR13) */ +#define TRANSCEIVER_MODE_REG 13 +#define TRANSCEIVER_MODE_RB 2 +#define IO_BASE_UNLOCK BIT(7) +#define LOWER_SQUELCH_TRESH BIT(6) +#define LINK_TEST_DISABLE BIT(5) +#define AUI_SELECT BIT(4) +#define DIS_AUTO_PORT_SEL BIT(3) + +/* Filter Self Receive Register (BMPR14) */ +#define FILTER_SELF_RX_REG 14 +#define SKIP_RX_PACKET BIT(2) +#define FILTER_SELF_RECEIVE BIT(0) + +/* EEPROM Control Register (BMPR 16) */ +#define EEPROM_CTRL_REG 16 + +/* EEPROM Data Register (BMPR 17) */ +#define EEPROM_DATA_REG 17 + +/* NMC93CSx6 EEPROM Control Bits */ +#define CS_0 0x00 +#define CS_1 0x20 +#define SK_0 0x00 +#define SK_1 0x40 +#define DI_0 0x00 +#define DI_1 0x80 + +/* NMC93CSx6 EEPROM Instructions */ +#define EEPROM_READ 0x80 + +/* NMC93CSx6 EEPROM Addresses */ +#define E_NODEID_0 0x02 +#define E_NODEID_1 0x03 +#define E_NODEID_2 0x04 +#define E_PORT_SELECT 0x14 + #define E_PORT_BNC 0x00 + #define E_PORT_DIX 0x01 + #define E_PORT_TP 0x02 + #define E_PORT_AUTO 0x03 + #define E_PORT_FROM_EPROM 0x04 +#define E_PRODUCT_CFG 0x30 + + +/* Macro to slow down io between EEPROM clock transitions */ +#define eeprom_slow_io() do { int _i = 40; while(--_i > 0) { inb(0x80); }}while(0) + +/* Jumperless Configuration Register (BMPR19) */ +#define JUMPERLESS_CONFIG 19 + +/* ID ROM registers, writing to them also resets some parts of chip */ +#define ID_ROM_0 24 +#define ID_ROM_7 31 +#define RESET ID_ROM_0 + +/* This is the I/O address list to be probed when seeking the card */ +static unsigned int eth16i_portlist[] = + { 0x260, 0x280, 0x2A0, 0x240, 0x340, 0x320, 0x380, 0x300, 0 }; + +static unsigned int eth32i_portlist[] = + { 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, 0x6000, 0x7000, 0x8000, + 0x9000, 0xA000, 0xB000, 0xC000, 0xD000, 0xE000, 0xF000, 0 }; + +/* This is the Interrupt lookup table for Eth16i card */ +static unsigned int eth16i_irqmap[] = { 9, 10, 5, 15, 0 }; +#define NUM_OF_ISA_IRQS 4 + +/* This is the Interrupt lookup table for Eth32i card */ +static unsigned int eth32i_irqmap[] = { 3, 5, 7, 9, 10, 11, 12, 15, 0 }; +#define EISA_IRQ_REG 0xc89 +#define NUM_OF_EISA_IRQS 8 + +static unsigned int eth16i_tx_buf_map[] = { 2048, 2048, 4096, 8192 }; +static unsigned int boot = 1; + +/* Use 0 for production, 1 for verification, >2 for debug */ +#ifndef ETH16I_DEBUG +#define ETH16I_DEBUG 0 +#endif +static unsigned int eth16i_debug = ETH16I_DEBUG; + +/* Information for each board */ + +struct eth16i_local { + eth16i_stats_type stats; + unsigned char tx_started; + unsigned char tx_buf_busy; + unsigned short tx_queue; /* Number of packets in transmit buffer */ + unsigned short tx_queue_len; + unsigned int tx_buf_size; + unsigned long open_time; + unsigned long tx_buffered_packets; + unsigned long col_16; +}; + +/* Function prototypes */ + +extern int eth16i_probe(struct device *dev); + +static int eth16i_probe1(struct device *dev, int ioaddr); +static int eth16i_check_signature(int ioaddr); +static int eth16i_probe_port(int ioaddr); +static void eth16i_set_port(int ioaddr, int porttype); +static int eth16i_send_probe_packet(int ioaddr, unsigned char *b, int l); +static int eth16i_receive_probe_packet(int ioaddr); +static int eth16i_get_irq(int ioaddr); +static int eth16i_read_eeprom(int ioaddr, int offset); +static int eth16i_read_eeprom_word(int ioaddr); +static void eth16i_eeprom_cmd(int ioaddr, unsigned char command); +static int eth16i_open(struct device *dev); +static int eth16i_close(struct device *dev); +static int eth16i_tx(struct sk_buff *skb, struct device *dev); +static void eth16i_rx(struct device *dev); +static void eth16i_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void eth16i_reset(struct device *dev); +static void eth16i_skip_packet(struct device *dev); +static void eth16i_multicast(struct device *dev); +static void eth16i_select_regbank(unsigned char regbank, int ioaddr); +static void eth16i_initialize(struct device *dev); + +#if 0 +static int eth16i_set_irq(struct device *dev); +#endif + +#ifdef MODULE +static ushort eth16i_parse_mediatype(const char* s); +#endif + +static struct enet_statistics *eth16i_get_stats(struct device *dev); + +static char *cardname = "ICL EtherTeam 16i/32"; + +#ifdef HAVE_DEVLIST + +/* Support for alternate probe manager */ +/struct netdev_entry eth16i_drv = + {"eth16i", eth16i_probe1, ETH16I_IO_EXTENT, eth16i_probe_list}; + +#else /* Not HAVE_DEVLIST */ + +__initfunc(int eth16i_probe(struct device *dev)) +{ + int i; + int ioaddr; + int base_addr = dev ? dev->base_addr : 0; + + if(eth16i_debug > 4) + printk(KERN_DEBUG "Probing started for %s\n", cardname); + + if(base_addr > 0x1ff) /* Check only single location */ + return eth16i_probe1(dev, base_addr); + else if(base_addr != 0) /* Don't probe at all */ + return ENXIO; + + /* Seek card from the ISA io address space */ + for(i = 0; (ioaddr = eth16i_portlist[i]) ; i++) { + if(check_region(ioaddr, ETH16I_IO_EXTENT)) + continue; + if(eth16i_probe1(dev, ioaddr) == 0) + return 0; + } + + /* Seek card from the EISA io address space */ + for(i = 0; (ioaddr = eth32i_portlist[i]) ; i++) { + if(check_region(ioaddr, ETH16I_IO_EXTENT)) + continue; + if(eth16i_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif /* Not HAVE_DEVLIST */ + +__initfunc(static int eth16i_probe1(struct device *dev, int ioaddr)) +{ + static unsigned version_printed = 0; + boot = 1; /* To inform initilization that we are in boot probe */ + + /* + The MB86985 chip has on register which holds information in which + io address the chip lies. First read this register and compare + it to our current io address and if match then this could + be our chip. + */ + + if(ioaddr < 0x1000) { + + if(eth16i_portlist[(inb(ioaddr + JUMPERLESS_CONFIG) & 0x07)] + != ioaddr) + return -ENODEV; + } + + /* Now we will go a bit deeper and try to find the chip's signature */ + + if(eth16i_check_signature(ioaddr) != 0) + return -ENODEV; + + /* + Now it seems that we have found a ethernet chip in this particular + ioaddr. The MB86985 chip has this feature, that when you read a + certain register it will increase it's io base address to next + configurable slot. Now when we have found the chip, first thing is + to make sure that the chip's ioaddr will hold still here. + */ + + eth16i_select_regbank(TRANSCEIVER_MODE_RB, ioaddr); + outb(0x00, ioaddr + TRANSCEIVER_MODE_REG); + + outb(0x00, ioaddr + RESET); /* Reset some parts of chip */ + BITSET(ioaddr + CONFIG_REG_0, BIT(7)); /* Disable the data link */ + + if(dev == NULL) + dev = init_etherdev(0, 0); + + if( (eth16i_debug & version_printed++) == 0) + printk(KERN_INFO "%s", version); + + dev->base_addr = ioaddr; + +#if 0 + if(dev->irq) { + if(eth16i_set_irq(dev)) { + dev->irq = eth16i_get_irq(ioaddr); + } + + } + else { +#endif + + dev->irq = eth16i_get_irq(ioaddr); + + /* Try to obtain interrupt vector */ + + if (request_irq(dev->irq, (void *)ð16i_interrupt, 0, "eth16i", dev)) { + printk(KERN_WARNING "%s: %s at %#3x, but is unusable due conflicting IRQ %d.\n", + dev->name, cardname, ioaddr, dev->irq); + return -EAGAIN; + } + +#if 0 + irq2dev_map[dev->irq] = dev; +#endif + + printk(KERN_INFO "%s: %s at %#3x, IRQ %d, ", + dev->name, cardname, ioaddr, dev->irq); + + /* Let's grab the region */ + request_region(ioaddr, ETH16I_IO_EXTENT, "eth16i"); + + /* Now we will have to lock the chip's io address */ + eth16i_select_regbank(TRANSCEIVER_MODE_RB, ioaddr); + outb(0x38, ioaddr + TRANSCEIVER_MODE_REG); + + eth16i_initialize(dev); /* Initialize rest of the chip's registers */ + + /* Now let's same some energy by shutting down the chip ;) */ + BITCLR(ioaddr + CONFIG_REG_1, POWERUP); + + /* Initialize the device structure */ + if(dev->priv == NULL) { + dev->priv = kmalloc(sizeof(struct eth16i_local), GFP_KERNEL); + if(dev->priv == NULL) + return -ENOMEM; + } + + memset(dev->priv, 0, sizeof(struct eth16i_local)); + + dev->open = eth16i_open; + dev->stop = eth16i_close; + dev->hard_start_xmit = eth16i_tx; + dev->get_stats = eth16i_get_stats; + dev->set_multicast_list = ð16i_multicast; + + /* Fill in the fields of the device structure with ethernet values. */ + ether_setup(dev); + + boot = 0; + + return 0; +} + + +static void eth16i_initialize(struct device *dev) +{ + int ioaddr = dev->base_addr; + int i, node_w = 0; + unsigned char node_byte = 0; + + /* Setup station address */ + eth16i_select_regbank(NODE_ID_RB, ioaddr); + for(i = 0 ; i < 3 ; i++) { + unsigned short node_val = eth16i_read_eeprom(ioaddr, E_NODEID_0 + i); + ((unsigned short *)dev->dev_addr)[i] = ntohs(node_val); + } + + for(i = 0; i < 6; i++) { + outb( ((unsigned char *)dev->dev_addr)[i], ioaddr + NODE_ID_0 + i); + if(boot) { + printk("%02x", inb(ioaddr + NODE_ID_0 + i)); + if(i != 5) + printk(":"); + } + } + + /* Now we will set multicast addresses to accept none */ + eth16i_select_regbank(HASH_TABLE_RB, ioaddr); + for(i = 0; i < 8; i++) + outb(0x00, ioaddr + HASH_TABLE_0 + i); + + /* + Now let's disable the transmitter and receiver, set the buffer ram + cycle time, bus width and buffer data path width. Also we shall + set transmit buffer size and total buffer size. + */ + + eth16i_select_regbank(2, ioaddr); + + node_byte = 0; + node_w = eth16i_read_eeprom(ioaddr, E_PRODUCT_CFG); + + if( (node_w & 0xFF00) == 0x0800) + node_byte |= BUFFER_WIDTH_8; + + node_byte |= SRAM_BS1; + + if( (node_w & 0x00FF) == 64) + node_byte |= SRAM_BS0; + + node_byte |= DLC_EN | SRAM_CYCLE_TIME_100NS | (ETH16I_TX_BUF_SIZE << 2); + + outb(node_byte, ioaddr + CONFIG_REG_0); + + /* We shall halt the transmitting, if 16 collisions are detected */ + outb(HALT_ON_16, ioaddr + COL_16_REG); + +#ifdef MODULE + /* if_port already set by init_module() */ +#else + dev->if_port = (dev->mem_start < E_PORT_FROM_EPROM) ? + dev->mem_start : E_PORT_FROM_EPROM; +#endif + + /* Set interface port type */ + if(boot) { + char *porttype[] = {"BNC", "DIX", "TP", "AUTO", "FROM_EPROM" }; + + switch(dev->if_port) + { + + case E_PORT_FROM_EPROM: + dev->if_port = eth16i_read_eeprom(ioaddr, E_PORT_SELECT); + break; + + case E_PORT_AUTO: + dev->if_port = eth16i_probe_port(ioaddr); + break; + + case E_PORT_BNC: + case E_PORT_TP: + case E_PORT_DIX: + break; + } + + printk(" %s interface.\n", porttype[dev->if_port]); + + eth16i_set_port(ioaddr, dev->if_port); + } + + /* Set Receive Mode to normal operation */ + outb(MODE_2, ioaddr + RECEIVE_MODE_REG); +} + +static int eth16i_probe_port(int ioaddr) +{ + int i; + int retcode; + unsigned char dummy_packet[64] = { 0 }; + + /* Powerup the chip */ + outb(0xc0 | POWERUP, ioaddr + CONFIG_REG_1); + + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + + eth16i_select_regbank(NODE_ID_RB, ioaddr); + + for(i = 0; i < 6; i++) { + dummy_packet[i] = inb(ioaddr + NODE_ID_0 + i); + dummy_packet[i+6] = inb(ioaddr + NODE_ID_0 + i); + } + + dummy_packet[12] = 0x00; + dummy_packet[13] = 0x04; + + eth16i_select_regbank(2, ioaddr); + + for(i = 0; i < 3; i++) { + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + BITCLR(ioaddr + CONFIG_REG_0, DLC_EN); + eth16i_set_port(ioaddr, i); + + if(eth16i_debug > 1) + printk(KERN_DEBUG "Set port number %d\n", i); + + retcode = eth16i_send_probe_packet(ioaddr, dummy_packet, 64); + if(retcode == 0) { + retcode = eth16i_receive_probe_packet(ioaddr); + if(retcode != -1) { + if(eth16i_debug > 1) + printk(KERN_DEBUG "Eth16i interface port found at %d\n", i); + return i; + } + } + else { + if(eth16i_debug > 1) + printk(KERN_DEBUG "TRANSMIT_DONE timeout when probing interface port\n"); + } + } + + if( eth16i_debug > 1) + printk(KERN_DEBUG "Using default port\n"); + + return E_PORT_BNC; +} + +static void eth16i_set_port(int ioaddr, int porttype) +{ + unsigned short temp = 0; + + eth16i_select_regbank(TRANSCEIVER_MODE_RB, ioaddr); + outb(LOOPBACK_CONTROL, ioaddr + TRANSMIT_MODE_REG); + + temp |= DIS_AUTO_PORT_SEL; + + switch(porttype) { + + case E_PORT_BNC : + temp |= AUI_SELECT; + break; + + case E_PORT_TP : + break; + + case E_PORT_DIX : + temp |= AUI_SELECT; + BITSET(ioaddr + TRANSMIT_MODE_REG, CONTROL_OUTPUT); + break; + } + + outb(temp, ioaddr + TRANSCEIVER_MODE_REG); + + if(eth16i_debug > 1) { + printk(KERN_DEBUG "TRANSMIT_MODE_REG = %x\n", inb(ioaddr + TRANSMIT_MODE_REG)); + printk(KERN_DEBUG "TRANSCEIVER_MODE_REG = %x\n", + inb(ioaddr+TRANSCEIVER_MODE_REG)); + } +} + +static int eth16i_send_probe_packet(int ioaddr, unsigned char *b, int l) +{ + int starttime; + + outb(0xff, ioaddr + TX_STATUS_REG); + + outw(l, ioaddr + DATAPORT); + outsw(ioaddr + DATAPORT, (unsigned short *)b, (l + 1) >> 1); + + starttime = jiffies; + outb(TX_START | 1, ioaddr + TRANSMIT_START_REG); + + while( (inb(ioaddr + TX_STATUS_REG) & 0x80) == 0) { + if( (jiffies - starttime) > TX_TIMEOUT) { + return -1; + } + } + + return 0; +} + +static int eth16i_receive_probe_packet(int ioaddr) +{ + int starttime; + + starttime = jiffies; + + while((inb(ioaddr + TX_STATUS_REG) & 0x20) == 0) { + if( (jiffies - starttime) > TX_TIMEOUT) { + + if(eth16i_debug > 1) + printk(KERN_DEBUG "Timeout occured waiting transmit packet received\n"); + starttime = jiffies; + while((inb(ioaddr + RX_STATUS_REG) & 0x80) == 0) { + if( (jiffies - starttime) > TX_TIMEOUT) { + if(eth16i_debug > 1) + printk(KERN_DEBUG "Timeout occured waiting receive packet\n"); + return -1; + } + } + + if(eth16i_debug > 1) + printk(KERN_DEBUG "RECEIVE_PACKET\n"); + return(0); /* Found receive packet */ + } + } + + if(eth16i_debug > 1) { + printk(KERN_DEBUG "TRANSMIT_PACKET_RECEIVED %x\n", inb(ioaddr + TX_STATUS_REG)); + printk(KERN_DEBUG "RX_STATUS_REG = %x\n", inb(ioaddr + RX_STATUS_REG)); + } + + return(0); /* Return success */ +} + +#if 0 +static int eth16i_set_irq(struct device* dev) +{ + const int ioaddr = dev->base_addr; + const int irq = dev->irq; + int i = 0; + + if(ioaddr < 0x1000) { + while(eth16i_irqmap[i] && eth16i_irqmap[i] != irq) + i++; + + if(i < NUM_OF_ISA_IRQS) { + u8 cbyte = inb(ioaddr + JUMPERLESS_CONFIG); + cbyte = (cbyte & 0x3F) | (i << 6); + outb(cbyte, ioaddr + JUMPERLESS_CONFIG); + return 0; + } + } + else { + printk(KERN_NOTICE "%s: EISA Interrupt cannot be set. Use EISA Configuration utility.\n", dev->name); + } + + return -1; + +} +#endif + +static int eth16i_get_irq(int ioaddr) +{ + unsigned char cbyte; + + if( ioaddr < 0x1000) { + cbyte = inb(ioaddr + JUMPERLESS_CONFIG); + return( eth16i_irqmap[ ((cbyte & 0xC0) >> 6) ] ); + } else { /* Oh..the card is EISA so method getting IRQ different */ + unsigned short index = 0; + cbyte = inb(ioaddr + EISA_IRQ_REG); + while( (cbyte & 0x01) == 0) { + cbyte = cbyte >> 1; + index++; + } + return( eth32i_irqmap[ index ] ); + } +} + +static int eth16i_check_signature(int ioaddr) +{ + int i; + unsigned char creg[4] = { 0 }; + + for(i = 0; i < 4 ; i++) { + + creg[i] = inb(ioaddr + TRANSMIT_MODE_REG + i); + + if(eth16i_debug > 1) + printk("eth16i: read signature byte %x at %x\n", + creg[i], + ioaddr + TRANSMIT_MODE_REG + i); + } + + creg[0] &= 0x0F; /* Mask collision cnr */ + creg[2] &= 0x7F; /* Mask DCLEN bit */ + +#ifdef 0 + /* + This was removed because the card was sometimes left to state + from which it couldn't be find anymore. If there is need + to more strict check still this have to be fixed. + */ + if( ! ((creg[0] == 0x06) && (creg[1] == 0x41)) ) { + if(creg[1] != 0x42) + return -1; + } +#endif + + if( !((creg[2] == 0x36) && (creg[3] == 0xE0)) ) { + creg[2] &= 0x40; + creg[3] &= 0x03; + + if( !((creg[2] == 0x40) && (creg[3] == 0x00)) ) + return -1; + } + + if(eth16i_read_eeprom(ioaddr, E_NODEID_0) != 0) + return -1; + + if((eth16i_read_eeprom(ioaddr, E_NODEID_1) & 0xFF00) != 0x4B00) + return -1; + + return 0; +} + +static int eth16i_read_eeprom(int ioaddr, int offset) +{ + int data = 0; + + eth16i_eeprom_cmd(ioaddr, EEPROM_READ | offset); + outb(CS_1, ioaddr + EEPROM_CTRL_REG); + data = eth16i_read_eeprom_word(ioaddr); + outb(CS_0 | SK_0, ioaddr + EEPROM_CTRL_REG); + + return(data); +} + +static int eth16i_read_eeprom_word(int ioaddr) +{ + int i; + int data = 0; + + for(i = 16; i > 0; i--) { + outb(CS_1 | SK_0, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + outb(CS_1 | SK_1, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + data = (data << 1) | + ((inb(ioaddr + EEPROM_DATA_REG) & DI_1) ? 1 : 0); + + eeprom_slow_io(); + } + + return(data); +} + +static void eth16i_eeprom_cmd(int ioaddr, unsigned char command) +{ + int i; + + outb(CS_0 | SK_0, ioaddr + EEPROM_CTRL_REG); + outb(DI_0, ioaddr + EEPROM_DATA_REG); + outb(CS_1 | SK_0, ioaddr + EEPROM_CTRL_REG); + outb(DI_1, ioaddr + EEPROM_DATA_REG); + outb(CS_1 | SK_1, ioaddr + EEPROM_CTRL_REG); + + for(i = 7; i >= 0; i--) { + short cmd = ( (command & (1 << i)) ? DI_1 : DI_0 ); + outb(cmd, ioaddr + EEPROM_DATA_REG); + outb(CS_1 | SK_0, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + outb(CS_1 | SK_1, ioaddr + EEPROM_CTRL_REG); + eeprom_slow_io(); + } +} + +static int eth16i_open(struct device *dev) +{ + struct eth16i_local *lp = (struct eth16i_local *)dev->priv; + int ioaddr = dev->base_addr; + + /* Powerup the chip */ + outb(0xc0 | POWERUP, ioaddr + CONFIG_REG_1); + + /* Initialize the chip */ + eth16i_initialize(dev); + + /* Set the transmit buffer size */ + lp->tx_buf_size = eth16i_tx_buf_map[ETH16I_TX_BUF_SIZE & 0x03]; + + if(eth16i_debug > 0) + printk(KERN_DEBUG "%s: transmit buffer size %d\n", + dev->name, lp->tx_buf_size); + + /* Now enable Transmitter and Receiver sections */ + BITCLR(ioaddr + CONFIG_REG_0, DLC_EN); + + /* Now switch to register bank 2, for run time operation */ + eth16i_select_regbank(2, ioaddr); + + lp->open_time = jiffies; + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + + /* Turn on interrupts*/ + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + MOD_INC_USE_COUNT; + + return 0; +} + +static int eth16i_close(struct device *dev) +{ + struct eth16i_local *lp = (struct eth16i_local *)dev->priv; + int ioaddr = dev->base_addr; + + eth16i_reset(dev); + + /* Turn off interrupts*/ + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + + dev->start = 0; + dev->tbusy = 1; + + lp->open_time = 0; + + /* Disable transmit and receive */ + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + + /* Reset the chip */ + /* outb(0xff, ioaddr + RESET); */ + /* outw(0xffff, ioaddr + TX_STATUS_REG); */ + + outb(0x00, ioaddr + CONFIG_REG_1); + + MOD_DEC_USE_COUNT; + + return 0; +} + +static int eth16i_tx(struct sk_buff *skb, struct device *dev) +{ + struct eth16i_local *lp = (struct eth16i_local *)dev->priv; + int ioaddr = dev->base_addr; + int status = 0; + + if(dev->tbusy) { + + /* + If we get here, some higher level has decided that + we are broken. There should really be a "kick me" + function call instead. + */ + + int tickssofar = jiffies - dev->trans_start; + if(tickssofar < TX_TIMEOUT) + return 1; + + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + + printk(KERN_WARNING "%s: transmit timed out with status %04x, %s ?\n", + dev->name, + inw(ioaddr + TX_STATUS_REG), + (inb(ioaddr + TX_STATUS_REG) & TX_DONE) ? + "IRQ conflict" : "network cable problem"); + + dev->trans_start = jiffies; + + /* Let's dump all registers */ + if(eth16i_debug > 0) { + printk(KERN_DEBUG "%s: timeout: %02x %02x %02x %02x %02x %02x %02x %02x.\n", + dev->name, inb(ioaddr + 0), + inb(ioaddr + 1), inb(ioaddr + 2), + inb(ioaddr + 3), inb(ioaddr + 4), + inb(ioaddr + 5), + inb(ioaddr + 6), inb(ioaddr + 7)); + + printk(KERN_DEBUG "%s: transmit start reg: %02x. collision reg %02x\n", + dev->name, inb(ioaddr + TRANSMIT_START_REG), + inb(ioaddr + COL_16_REG)); + + printk(KERN_DEBUG "lp->tx_queue = %d\n", lp->tx_queue); + printk(KERN_DEBUG "lp->tx_queue_len = %d\n", lp->tx_queue_len); + printk(KERN_DEBUG "lp->tx_started = %d\n", lp->tx_started); + + } + + lp->stats.tx_errors++; + + eth16i_reset(dev); + + dev->trans_start = jiffies; + + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + + } + + /* + 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 LINUX_VERSION_CODE < 0x020100 + dev_tint(dev); +#endif + if(eth16i_debug > 0) + printk(KERN_WARNING "%s: Missed tx-done interrupt.\n", dev->name); + return 0; + } + + /* Block a timer based transmitter from overlapping. + This could better be done with atomic_swap(1, dev->tbusy), + but set_bit() works as well. */ + + set_bit(0, (void *)&lp->tx_buf_busy); + + /* Turn off TX interrupts */ + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + + if(test_and_set_bit(0, (void *)&dev->tbusy) != 0) { + printk(KERN_WARNING "%s: Transmitter access conflict.\n", dev->name); + status = -1; + } + else { + ushort length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + unsigned char *buf = skb->data; + + if( (length + 2) > (lp->tx_buf_size - lp->tx_queue_len)) { + if(eth16i_debug > 0) + printk(KERN_WARNING "%s: Transmit buffer full.\n", dev->name); + } + else { + outw(length, ioaddr + DATAPORT); + + if( ioaddr < 0x1000 ) + outsw(ioaddr + DATAPORT, buf, (length + 1) >> 1); + else { + unsigned char frag = length % 4; + + outsl(ioaddr + DATAPORT, buf, length >> 2); + + if( frag != 0 ) { + outsw(ioaddr + DATAPORT, (buf + (length & 0xFFFC)), 1); + if( frag == 3 ) + outsw(ioaddr + DATAPORT, + (buf + (length & 0xFFFC) + 2), 1); + } + } + + lp->tx_buffered_packets++; + lp->tx_queue++; + lp->tx_queue_len += length + 2; + + } + + lp->tx_buf_busy = 0; + + if(lp->tx_started == 0) { + /* If the transmitter is idle..always trigger a transmit */ + outb(TX_START | lp->tx_queue, ioaddr + TRANSMIT_START_REG); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + lp->tx_started = 1; + dev->tbusy = 0; + } + else if(lp->tx_queue_len < lp->tx_buf_size - (ETH_FRAME_LEN + 2)) { + /* There is still more room for one more packet in tx buffer */ + dev->tbusy = 0; + } + + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + + /* Turn TX interrupts back on */ + /* outb(TX_INTR_DONE | TX_INTR_16_COL, ioaddr + TX_INTR_REG); */ + status = 0; + } + +#if LINUX_VERSION_CODE >= 0x020100 + dev_kfree_skb(skb); +#else + dev_kfree_skb(skb, FREE_WRITE); +#endif + + return status; +} + +static void eth16i_rx(struct device *dev) +{ + struct eth16i_local *lp = (struct eth16i_local *)dev->priv; + int ioaddr = dev->base_addr; + int boguscount = MAX_RX_LOOP; + + /* Loop until all packets have been read */ + while( (inb(ioaddr + RECEIVE_MODE_REG) & RX_BUFFER_EMPTY) == 0) { + + /* Read status byte from receive buffer */ + ushort status = inw(ioaddr + DATAPORT); + + /* Get the size of the packet from receive buffer */ + ushort pkt_len = inw(ioaddr + DATAPORT); + + if(eth16i_debug > 4) + printk(KERN_DEBUG "%s: Receiving packet mode %02x status %04x.\n", + dev->name, + inb(ioaddr + RECEIVE_MODE_REG), status); + + if( !(status & PKT_GOOD) ) { + lp->stats.rx_errors++; + + if( (pkt_len < ETH_ZLEN) || (pkt_len > ETH_FRAME_LEN) ) { + lp->stats.rx_length_errors++; + eth16i_reset(dev); + return; + } + else { + eth16i_skip_packet(dev); + lp->stats.rx_dropped++; + } + } + else { /* Ok so now we should have a good packet */ + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len + 3); + if( skb == NULL ) { + printk(KERN_WARNING "%s: Could'n allocate memory for packet (len %d)\n", + dev->name, pkt_len); + eth16i_skip_packet(dev); + lp->stats.rx_dropped++; + break; + } + + skb->dev = dev; + skb_reserve(skb,2); + + /* + Now let's get the packet out of buffer. + size is (pkt_len + 1) >> 1, cause we are now reading words + and it have to be even aligned. + */ + + if(ioaddr < 0x1000) + insw(ioaddr + DATAPORT, skb_put(skb, pkt_len), + (pkt_len + 1) >> 1); + else { + unsigned char *buf = skb_put(skb, pkt_len); + unsigned char frag = pkt_len % 4; + + insl(ioaddr + DATAPORT, buf, pkt_len >> 2); + + if(frag != 0) { + unsigned short rest[2]; + rest[0] = inw( ioaddr + DATAPORT ); + if(frag == 3) + rest[1] = inw( ioaddr + DATAPORT ); + + memcpy(buf + (pkt_len & 0xfffc), (char *)rest, frag); + } + } + + skb->protocol=eth_type_trans(skb, dev); + netif_rx(skb); + lp->stats.rx_packets++; + + if( eth16i_debug > 5 ) { + int i; + printk(KERN_DEBUG "%s: Received packet of length %d.\n", + dev->name, pkt_len); + for(i = 0; i < 14; i++) + printk(KERN_DEBUG " %02x", skb->data[i]); + printk(KERN_DEBUG ".\n"); + } + + } /* else */ + + if(--boguscount <= 0) + break; + + } /* while */ + +#if 0 + { + int i; + + for(i = 0; i < 20; i++) { + if( (inb(ioaddr+RECEIVE_MODE_REG) & RX_BUFFER_EMPTY) == + RX_BUFFER_EMPTY) + break; + inw(ioaddr + DATAPORT); + outb(SKIP_RX_PACKET, ioaddr + FILTER_SELF_RX_REG); + } + + if(eth16i_debug > 1) + printk(KERN_DEBUG "%s: Flushed receive buffer.\n", dev->name); + } +#endif + + return; +} + +static void eth16i_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = dev_id; + struct eth16i_local *lp; + int ioaddr = 0, + status; + + if(dev == NULL) { + printk(KERN_WARNING "eth16i_interrupt(): irq %d for unknown device. \n", irq); + return; + } + + /* Turn off all interrupts from adapter */ + outw(ETH16I_INTR_OFF, ioaddr + TX_INTR_REG); + + set_bit(0, (void *)&dev->tbusy); /* Set the device busy so that */ + /* eth16i_tx wont be called */ + + if(dev->interrupt) + printk(KERN_WARNING "%s: Re-entering the interrupt handler.\n", dev->name); + dev->interrupt = 1; + + ioaddr = dev->base_addr; + lp = (struct eth16i_local *)dev->priv; + status = inw(ioaddr + TX_STATUS_REG); /* Get the status */ + outw(status, ioaddr + TX_STATUS_REG); /* Clear status bits */ + + if(eth16i_debug > 3) + printk(KERN_DEBUG "%s: Interrupt with status %04x.\n", dev->name, status); + + if( status & 0x7f00 ) { + + lp->stats.rx_errors++; + + if(status & (BUS_RD_ERR << 8) ) + printk(KERN_WARNING "%s: Bus read error.\n",dev->name); + if(status & (SHORT_PKT_ERR << 8) ) lp->stats.rx_length_errors++; + if(status & (ALIGN_ERR << 8) ) lp->stats.rx_frame_errors++; + if(status & (CRC_ERR << 8) ) lp->stats.rx_crc_errors++; + if(status & (RX_BUF_OVERFLOW << 8) ) lp->stats.rx_over_errors++; + } + if( status & 0x001a) { + + lp->stats.tx_errors++; + + if(status & CR_LOST) lp->stats.tx_carrier_errors++; + if(status & TX_JABBER_ERR) lp->stats.tx_window_errors++; + +#if 0 + if(status & COLLISION) { + lp->stats.collisions += + ((inb(ioaddr+TRANSMIT_MODE_REG) & 0xF0) >> 4); + } +#endif + if(status & COLLISIONS_16) { + if(lp->col_16 < MAX_COL_16) { + lp->col_16++; + lp->stats.collisions++; + /* Resume transmitting, skip failed packet */ + outb(0x02, ioaddr + COL_16_REG); + } + else { + printk(KERN_WARNING "%s: bailing out due to many consecutive 16-in-a-row collisions. Network cable problem?\n", dev->name); + } + } + } + + if( status & 0x00ff ) { /* Let's check the transmit status reg */ + + if(status & TX_DONE) { /* The transmit has been done */ + lp->stats.tx_packets = lp->tx_buffered_packets; + lp->col_16 = 0; + + if(lp->tx_queue) { /* Is there still packets ? */ + /* There was packet(s) so start transmitting and write also + how many packets there is to be sended */ + outb(TX_START | lp->tx_queue, ioaddr + TRANSMIT_START_REG); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + lp->tx_started = 1; + dev->trans_start = jiffies; + mark_bh(NET_BH); + } + else { + lp->tx_started = 0; + mark_bh(NET_BH); + } + } + } + + if( ( status & 0x8000 ) || + ( (inb(ioaddr + RECEIVE_MODE_REG) & RX_BUFFER_EMPTY) == 0) ) { + eth16i_rx(dev); /* We have packet in receive buffer */ + } + + dev->interrupt = 0; + + /* Turn interrupts back on */ + outw(ETH16I_INTR_ON, ioaddr + TX_INTR_REG); + + if(lp->tx_queue_len < lp->tx_buf_size - (ETH_FRAME_LEN + 2)) { + /* There is still more room for one more packet in tx buffer */ + dev->tbusy = 0; + } + + return; +} + +static void eth16i_skip_packet(struct device *dev) +{ + int ioaddr = dev->base_addr; + + inw(ioaddr + DATAPORT); + inw(ioaddr + DATAPORT); + inw(ioaddr + DATAPORT); + + outb(SKIP_RX_PACKET, ioaddr + FILTER_SELF_RX_REG); + while( inb( ioaddr + FILTER_SELF_RX_REG ) != 0); +} + +static void eth16i_reset(struct device *dev) +{ + struct eth16i_local *lp = (struct eth16i_local *)dev->priv; + int ioaddr = dev->base_addr; + + if(eth16i_debug > 1) + printk(KERN_DEBUG "%s: Resetting device.\n", dev->name); + + BITSET(ioaddr + CONFIG_REG_0, DLC_EN); + outw(0xffff, ioaddr + TX_STATUS_REG); + eth16i_select_regbank(2, ioaddr); + + lp->tx_started = 0; + lp->tx_buf_busy = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + + dev->interrupt = 0; + dev->start = 1; + dev->tbusy = 0; + BITCLR(ioaddr + CONFIG_REG_0, DLC_EN); +} + +static void eth16i_multicast(struct device *dev) +{ + int ioaddr = dev->base_addr; + + if(dev->mc_count || dev->flags&(IFF_ALLMULTI|IFF_PROMISC)) + { + dev->flags|=IFF_PROMISC; /* Must do this */ + outb(3, ioaddr + RECEIVE_MODE_REG); + } else { + outb(2, ioaddr + RECEIVE_MODE_REG); + } +} + +static struct enet_statistics *eth16i_get_stats(struct device *dev) +{ + struct eth16i_local *lp = (struct eth16i_local *)dev->priv; + + return &lp->stats; +} + +static void eth16i_select_regbank(unsigned char banknbr, int ioaddr) +{ + unsigned char data; + + data = inb(ioaddr + CONFIG_REG_1); + outb( ((data & 0xF3) | ( (banknbr & 0x03) << 2)), ioaddr + CONFIG_REG_1); +} + +#ifdef MODULE + +static ushort eth16i_parse_mediatype(const char* s) +{ + if(!s) + return E_PORT_FROM_EPROM; + + if (!strncmp(s, "bnc", 3)) + return E_PORT_BNC; + else if (!strncmp(s, "tp", 2)) + return E_PORT_TP; + else if (!strncmp(s, "dix", 3)) + return E_PORT_DIX; + else if (!strncmp(s, "auto", 4)) + return E_PORT_AUTO; + else + return E_PORT_FROM_EPROM; +} + +#define MAX_ETH16I_CARDS 4 /* Max number of Eth16i cards per module */ +#define NAMELEN 8 /* number of chars for storing dev->name */ + +static char namelist[NAMELEN * MAX_ETH16I_CARDS] = { 0, }; +static struct device dev_eth16i[MAX_ETH16I_CARDS] = { + { + NULL, + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int ioaddr[MAX_ETH16I_CARDS] = { 0, }; +#if 0 +static int irq[MAX_ETH16I_CARDS] = { 0, }; +#endif +static char* mediatype[MAX_ETH16I_CARDS] = { 0, }; +static int debug = -1; + +#if (LINUX_VERSION_CODE >= 0x20115) +MODULE_AUTHOR("Mika Kuoppala <miku@iki.fi>"); +MODULE_DESCRIPTION("ICL EtherTeam 16i/32 driver"); + +MODULE_PARM(ioaddr, "1-" __MODULE_STRING(MAX_ETH16I_CARDS) "i"); +MODULE_PARM_DESC(ioaddr, "eth16i io base address"); + +#if 0 +MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_ETH16I_CARDS) "i"); +MODULE_PARM_DESC(irq, "eth16i interrupt request number"); +#endif + +MODULE_PARM(mediatype, "1-" __MODULE_STRING(MAX_ETH16I_CARDS) "s"); +MODULE_PARM_DESC(mediatype, "eth16i interfaceport mediatype"); + +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, "eth16i debug level (0-4)"); +#endif + +int init_module(void) +{ + int this_dev, found = 0; + + for(this_dev = 0; this_dev < MAX_ETH16I_CARDS; this_dev++) + { + struct device *dev = &dev_eth16i[this_dev]; + + dev->name = namelist + (NAMELEN*this_dev); + dev->irq = 0; /* irq[this_dev]; */ + dev->base_addr = ioaddr[this_dev]; + dev->init = eth16i_probe; + + if(debug != -1) + eth16i_debug = debug; + + if(eth16i_debug > 1) + printk(KERN_NOTICE "eth16i(%d): interface type %s\n", this_dev, mediatype[this_dev] ? mediatype[this_dev] : "none" ); + + dev->if_port = eth16i_parse_mediatype(mediatype[this_dev]); + + if(ioaddr[this_dev] == 0) + { + if(this_dev != 0) break; /* Only autoprobe 1st one */ + + printk(KERN_NOTICE "eth16i.c: Presently autoprobing (not recommended) for a single card.\n"); + } + + if(register_netdev(dev) != 0) + { + printk(KERN_WARNING "eth16i.c No Eth16i card found (i/o = 0x%x).\n", + ioaddr[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 < MAX_ETH16I_CARDS; this_dev++) + { + struct device* dev = &dev_eth16i[this_dev]; + + if(dev->priv != NULL) + { + unregister_netdev(dev); + kfree(dev->priv); + dev->priv = NULL; + + free_irq(dev->irq, dev); + release_region(dev->base_addr, ETH16I_IO_EXTENT); + + } + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c eth16i.c" + * alt-compile-command: "gcc -DMODVERSIONS -DMODULE -D__KERNEL__ -Wall -Wstrict -prototypes -O6 -c eth16i.c" + * tab-width: 8 + * c-basic-offset: 8 + * c-indent-level: 8 + * End: + */ + +/* End of file eth16i.c */ diff --git a/linux/src/drivers/net/eth82586.h b/linux/src/drivers/net/eth82586.h new file mode 100644 index 00000000..c2178ffa --- /dev/null +++ b/linux/src/drivers/net/eth82586.h @@ -0,0 +1,172 @@ +/* + * eth82586.h: Intel EtherExpress defines + * + * Written 1995 by John Sullivan + * See eexpress.c for further details + * documentation and usage to do. + */ + +/* + * EtherExpress card register addresses + * as offsets from the base IO region (dev->base_addr) + */ + +#define DATAPORT 0x0000 +#define WRITE_PTR 0x0002 +#define READ_PTR 0x0004 +#define SIGNAL_CA 0x0006 +#define SET_IRQ 0x0007 +#define SM_PTR 0x0008 +#define MEM_Ctrl 0x000b +#define MEM_Page_Ctrl 0x000c +#define Config 0x000d +#define EEPROM_Ctrl 0x000e +#define ID_PORT 0x000f + +/* + * offset to shadowed memory, 0 <= x <= 31. We don't use this yet, + * but may in the future. Is shadow memory access any faster than + * dataport access? + */ +#define SM_ADDR(x) (0x4000+((x&0x10)<<10)+(x&0xf)) + +/* Always mirrors eexp-memory at 0x0008-0x000f */ +#define SCB_STATUS 0xc008 +#define SCB_CMD 0xc00a +#define SCB_CBL 0xc00c +#define SCB_RFA 0xc00e + + + +/* + * card register defines + */ + +/* SET_IRQ */ +#define SIRQ_en 0x08 +#define SIRQ_dis 0x00 + +/* Config */ +#define set_loopback outb(inb(ioaddr+Config)|0x02,ioaddr+Config) +#define clear_loopback outb(inb(ioaddr+Config)&0xfd,ioaddr+Config) + +/* EEPROM_Ctrl */ +#define EC_Clk 0x01 +#define EC_CS 0x02 +#define EC_Wr 0x04 +#define EC_Rd 0x08 +#define ASIC_RST 0x40 +#define i586_RST 0x80 + +#define eeprom_delay() { int _i = 40; while (--_i>0) { __SLOW_DOWN_IO; }} + +/* + * i82586 Memory Configuration + */ + +/* (System Configuration Pointer) System start up block, read after 586_RST */ +#define SCP_START 0xfff6 + + +/* Intermediate System Configuration Pointer */ +#define ISCP_START 0x0000 +/* System Command Block */ +#define SCB_START 0x0008 + +/* + * Start of buffer region. If we have 64k memory, eexp_hw_probe() may raise + * NUM_TX_BUFS. RX_BUF_END is set to the end of memory, and all space between + * the transmit buffer region and end of memory used for as many receive buffers + * as we can fit. See eexp_hw_[(rx)(tx)]init(). + */ +#define TX_BUF_START 0x0100 +#define TX_BUF_SIZE ((24+ETH_FRAME_LEN+31)&~0x1f) +#define RX_BUF_SIZE ((32+ETH_FRAME_LEN+31)&~0x1f) + + + +/* + * SCB defines + */ + +/* these functions take the SCB status word and test the relevant status bit */ +#define SCB_complete(s) ((s&0x8000)!=0) +#define SCB_rxdframe(s) ((s&0x4000)!=0) +#define SCB_CUdead(s) ((s&0x2000)!=0) +#define SCB_RUdead(s) ((s&0x1000)!=0) +#define SCB_ack(s) (s & 0xf000) + +/* Command unit status: 0=idle, 1=suspended, 2=active */ +#define SCB_CUstat(s) ((s&0x0300)>>8) + +/* Receive unit status: 0=idle, 1=suspended, 2=out of resources, 4=ready */ +#define SCB_RUstat(s) ((s&0x0070)>>4) + +/* SCB commands */ +#define SCB_CUnop 0x0000 +#define SCB_CUstart 0x0100 +#define SCB_CUresume 0x0200 +#define SCB_CUsuspend 0x0300 +#define SCB_CUabort 0x0400 + +/* ? */ +#define SCB_resetchip 0x0080 + +#define SCB_RUnop 0x0000 +#define SCB_RUstart 0x0010 +#define SCB_RUresume 0x0020 +#define SCB_RUsuspend 0x0030 +#define SCB_RUabort 0x0040 + + +/* + * Command block defines + */ + +#define Stat_Done(s) ((s&0x8000)!=0) +#define Stat_Busy(s) ((s&0x4000)!=0) +#define Stat_OK(s) ((s&0x2000)!=0) +#define Stat_Abort(s) ((s&0x1000)!=0) +#define Stat_STFail ((s&0x0800)!=0) +#define Stat_TNoCar(s) ((s&0x0400)!=0) +#define Stat_TNoCTS(s) ((s&0x0200)!=0) +#define Stat_TNoDMA(s) ((s&0x0100)!=0) +#define Stat_TDefer(s) ((s&0x0080)!=0) +#define Stat_TColl(s) ((s&0x0040)!=0) +#define Stat_TXColl(s) ((s&0x0020)!=0) +#define Stat_NoColl(s) (s&0x000f) + +/* Cmd_END will end AFTER the command if this is the first + * command block after an SCB_CUstart, but BEFORE the command + * for all subsequent commands. Best strategy is to place + * Cmd_INT on the last command in the sequence, followed by a + * dummy Cmd_Nop with Cmd_END after this. + */ +#define Cmd_END 0x8000 +#define Cmd_SUS 0x4000 +#define Cmd_INT 0x2000 + +#define Cmd_Nop 0x0000 +#define Cmd_SetAddr 0x0001 +#define Cmd_Config 0x0002 +#define Cmd_MCast 0x0003 +#define Cmd_Xmit 0x0004 +#define Cmd_TDR 0x0005 +#define Cmd_Dump 0x0006 +#define Cmd_Diag 0x0007 + + +/* + * Frame Descriptor (Receive block) defines + */ + +#define FD_Done(s) ((s&0x8000)!=0) +#define FD_Busy(s) ((s&0x4000)!=0) +#define FD_OK(s) ((s&0x2000)!=0) + +#define FD_CRC(s) ((s&0x0800)!=0) +#define FD_Align(s) ((s&0x0400)!=0) +#define FD_Resrc(s) ((s&0x0200)!=0) +#define FD_DMA(s) ((s&0x0100)!=0) +#define FD_Short(s) ((s&0x0080)!=0) +#define FD_NoEOF(s) ((s&0x0040)!=0) diff --git a/linux/src/drivers/net/ewrk3.c b/linux/src/drivers/net/ewrk3.c new file mode 100644 index 00000000..f91315ff --- /dev/null +++ b/linux/src/drivers/net/ewrk3.c @@ -0,0 +1,1920 @@ +/* ewrk3.c: A DIGITAL EtherWORKS 3 ethernet driver for Linux. + + Written 1994 by David C. Davies. + + Copyright 1994 Digital Equipment Corporation. + + This software may be used and distributed according to the terms of + the GNU Public License, incorporated herein by reference. + + This driver is written for the Digital Equipment Corporation series + of EtherWORKS ethernet cards: + + DE203 Turbo (BNC) + DE204 Turbo (TP) + DE205 Turbo (TP BNC) + + The driver has been tested on a relatively busy network using the DE205 + card and benchmarked with 'ttcp': it transferred 16M of data at 975kB/s + (7.8Mb/s) to a DECstation 5000/200. + + The author may be reached at davies@maniac.ultranet.com. + + ========================================================================= + This driver has been written substantially from scratch, although its + inheritance of style and stack interface from 'depca.c' and in turn from + Donald Becker's 'lance.c' should be obvious. + + The DE203/4/5 boards all use a new proprietary chip in place of the + LANCE chip used in prior cards (DEPCA, DE100, DE200/1/2, DE210, DE422). + Use the depca.c driver in the standard distribution for the LANCE based + cards from DIGITAL; this driver will not work with them. + + The DE203/4/5 cards have 2 main modes: shared memory and I/O only. I/O + only makes all the card accesses through I/O transactions and no high + (shared) memory is used. This mode provides a >48% performance penalty + and is deprecated in this driver, although allowed to provide initial + setup when hardstrapped. + + The shared memory mode comes in 3 flavours: 2kB, 32kB and 64kB. There is + no point in using any mode other than the 2kB mode - their performances + are virtually identical, although the driver has been tested in the 2kB + and 32kB modes. I would suggest you uncomment the line: + + FORCE_2K_MODE; + + to allow the driver to configure the card as a 2kB card at your current + base address, thus leaving more room to clutter your system box with + other memory hungry boards. + + As many ISA and EISA cards can be supported under this driver as you + wish, limited primarily by the available IRQ lines, rather than by the + available I/O addresses (24 ISA, 16 EISA). I have checked different + configurations of multiple depca cards and ewrk3 cards and have not + found a problem yet (provided you have at least depca.c v0.38) ... + + The board IRQ setting must be at an unused IRQ which is auto-probed + using Donald Becker's autoprobe routines. All these cards are at + {5,10,11,15}. + + No 16MB memory limitation should exist with this driver as DMA is not + used and the common memory area is in low memory on the network card (my + current system has 20MB and I've not had problems yet). + + The ability to load this driver as a loadable module has been included + and used extensively during the driver development (to save those long + reboot sequences). To utilise this ability, you have to do 8 things: + + 0) have a copy of the loadable modules code installed on your system. + 1) copy ewrk3.c from the /linux/drivers/net directory to your favourite + temporary directory. + 2) edit the source code near line 1898 to reflect the I/O address and + IRQ you're using. + 3) compile ewrk3.c, but include -DMODULE in the command line to ensure + that the correct bits are compiled (see end of source code). + 4) if you are wanting to add a new card, goto 5. Otherwise, recompile a + kernel with the ewrk3 configuration turned off and reboot. + 5) insmod ewrk3.o + [Alan Cox: Changed this so you can insmod ewrk3.o irq=x io=y] + 6) run the net startup bits for your new eth?? interface manually + (usually /etc/rc.inet[12] at boot time). + 7) enjoy! + + Note that autoprobing is not allowed in loadable modules - the system is + already up and running and you're messing with interrupts. + + To unload a module, turn off the associated interface + 'ifconfig eth?? down' then 'rmmod ewrk3'. + + Promiscuous mode has been turned off in this driver, but all the + multicast address bits have been turned on. This improved the send + performance on a busy network by about 13%. + + Ioctl's have now been provided (primarily because I wanted to grab some + packet size statistics). They are patterned after 'plipconfig.c' from a + suggestion by Alan Cox. Using these ioctls, you can enable promiscuous + mode, add/delete multicast addresses, change the hardware address, get + packet size distribution statistics and muck around with the control and + status register. I'll add others if and when the need arises. + + TO DO: + ------ + + + Revision History + ---------------- + + Version Date Description + + 0.1 26-aug-94 Initial writing. ALPHA code release. + 0.11 31-aug-94 Fixed: 2k mode memory base calc., + LeMAC version calc., + IRQ vector assignments during autoprobe. + 0.12 31-aug-94 Tested working on LeMAC2 (DE20[345]-AC) card. + Fixed up MCA hash table algorithm. + 0.20 4-sep-94 Added IOCTL functionality. + 0.21 14-sep-94 Added I/O mode. + 0.21axp 15-sep-94 Special version for ALPHA AXP Linux V1.0. + 0.22 16-sep-94 Added more IOCTLs & tidied up. + 0.23 21-sep-94 Added transmit cut through. + 0.24 31-oct-94 Added uid checks in some ioctls. + 0.30 1-nov-94 BETA code release. + 0.31 5-dec-94 Added check/allocate region code. + 0.32 16-jan-95 Broadcast packet fix. + 0.33 10-Feb-95 Fix recognition bug reported by <bkm@star.rl.ac.uk>. + 0.40 27-Dec-95 Rationalise MODULE and autoprobe code. + Rewrite for portability & updated. + ALPHA support from <jestabro@amt.tay1.dec.com> + Added verify_area() calls in ewrk3_ioctl() from + suggestion by <heiko@colossus.escape.de>. + Add new multicasting code. + 0.41 20-Jan-96 Fix IRQ set up problem reported by + <kenneth@bbs.sas.ntu.ac.sg>. + 0.42 22-Apr-96 Fix alloc_device() bug <jari@markkus2.fimr.fi> + 0.43 16-Aug-96 Update alloc_device() to conform to de4x5.c + + ========================================================================= +*/ + +static const char *version = "ewrk3.c:v0.43 96/8/16 davies@maniac.ultranet.com\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/segment.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unistd.h> +#include <linux/ctype.h> + +#include "ewrk3.h" + +#ifdef EWRK3_DEBUG +static int ewrk3_debug = EWRK3_DEBUG; +#else +static int ewrk3_debug = 1; +#endif + +#define EWRK3_NDA 0xffe0 /* No Device Address */ + +#define PROBE_LENGTH 32 +#define ETH_PROM_SIG 0xAA5500FFUL + +#ifndef EWRK3_SIGNATURE +#define EWRK3_SIGNATURE {"DE203","DE204","DE205",""} +#define EWRK3_STRLEN 8 +#endif + +#ifndef EWRK3_RAM_BASE_ADDRESSES +#define EWRK3_RAM_BASE_ADDRESSES {0xc0000,0xd0000,0x00000} +#endif + +/* +** Sets up the I/O area for the autoprobe. +*/ +#define EWRK3_IO_BASE 0x100 /* Start address for probe search */ +#define EWRK3_IOP_INC 0x20 /* I/O address increment */ +#define EWRK3_TOTAL_SIZE 0x20 /* required I/O address length */ + +#ifndef MAX_NUM_EWRK3S +#define MAX_NUM_EWRK3S 21 +#endif + +#ifndef EWRK3_EISA_IO_PORTS +#define EWRK3_EISA_IO_PORTS 0x0c00 /* I/O port base address, slot 0 */ +#endif + +#ifndef MAX_EISA_SLOTS +#define MAX_EISA_SLOTS 16 +#define EISA_SLOT_INC 0x1000 +#endif + +#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */ +#define CRC_POLYNOMIAL_LE 0xedb88320UL /* Ethernet CRC, little endian */ + +#define QUEUE_PKT_TIMEOUT (1*HZ) /* Jiffies */ + +/* +** EtherWORKS 3 shared memory window sizes +*/ +#define IO_ONLY 0x00 +#define SHMEM_2K 0x800 +#define SHMEM_32K 0x8000 +#define SHMEM_64K 0x10000 + +/* +** EtherWORKS 3 IRQ ENABLE/DISABLE +*/ +#define ENABLE_IRQs { \ + icr |= lp->irq_mask;\ + outb(icr, EWRK3_ICR); /* Enable the IRQs */\ +} + +#define DISABLE_IRQs { \ + icr = inb(EWRK3_ICR);\ + icr &= ~lp->irq_mask;\ + outb(icr, EWRK3_ICR); /* Disable the IRQs */\ +} + +/* +** EtherWORKS 3 START/STOP +*/ +#define START_EWRK3 { \ + csr = inb(EWRK3_CSR);\ + csr &= ~(CSR_TXD|CSR_RXD);\ + outb(csr, EWRK3_CSR); /* Enable the TX and/or RX */\ +} + +#define STOP_EWRK3 { \ + csr = (CSR_TXD|CSR_RXD);\ + outb(csr, EWRK3_CSR); /* Disable the TX and/or RX */\ +} + +/* +** The EtherWORKS 3 private structure +*/ +#define EWRK3_PKT_STAT_SZ 16 +#define EWRK3_PKT_BIN_SZ 128 /* Should be >=100 unless you + increase EWRK3_PKT_STAT_SZ */ + +struct ewrk3_private { + char adapter_name[80]; /* Name exported to /proc/ioports */ + u_long shmem_base; /* Shared memory start address */ + u_long shmem_length; /* Shared memory window length */ + struct enet_statistics stats; /* Public stats */ + struct { + u32 bins[EWRK3_PKT_STAT_SZ]; /* Private stats counters */ + u32 unicast; + u32 multicast; + u32 broadcast; + u32 excessive_collisions; + u32 tx_underruns; + u32 excessive_underruns; + } pktStats; + u_char irq_mask; /* Adapter IRQ mask bits */ + u_char mPage; /* Maximum 2kB Page number */ + u_char lemac; /* Chip rev. level */ + u_char hard_strapped; /* Don't allow a full open */ + u_char lock; /* Lock the page register */ + u_char txc; /* Transmit cut through */ + u_char *mctbl; /* Pointer to the multicast table */ +}; + +/* +** Force the EtherWORKS 3 card to be in 2kB MODE +*/ +#define FORCE_2K_MODE { \ + shmem_length = SHMEM_2K;\ + outb(((mem_start - 0x80000) >> 11), EWRK3_MBR);\ +} + +/* +** Public Functions +*/ +static int ewrk3_open(struct device *dev); +static int ewrk3_queue_pkt(struct sk_buff *skb, struct device *dev); +static void ewrk3_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int ewrk3_close(struct device *dev); +static struct enet_statistics *ewrk3_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev); +static int ewrk3_ioctl(struct device *dev, struct ifreq *rq, int cmd); + +/* +** Private functions +*/ +static int ewrk3_hw_init(struct device *dev, u_long iobase); +static void ewrk3_init(struct device *dev); +static int ewrk3_rx(struct device *dev); +static int ewrk3_tx(struct device *dev); + +static void EthwrkSignature(char * name, char *eeprom_image); +static int DevicePresent(u_long iobase); +static void SetMulticastFilter(struct device *dev); +static int EISA_signature(char *name, s32 eisa_id); + +static int Read_EEPROM(u_long iobase, u_char eaddr); +static int Write_EEPROM(short data, u_long iobase, u_char eaddr); +static u_char get_hw_addr (struct device *dev, u_char *eeprom_image, char chipType); + +static void isa_probe(struct device *dev, u_long iobase); +static void eisa_probe(struct device *dev, u_long iobase); +static struct device *alloc_device(struct device *dev, u_long iobase); +static int ewrk3_dev_index(char *s); +static struct device *insert_device(struct device *dev, u_long iobase, int (*init)(struct device *)); + + +#ifdef MODULE +int init_module(void); +void cleanup_module(void); +static int autoprobed = 1, loading_module = 1; + +# else +static u_char irq[] = {5,0,10,3,11,9,15,12}; +static int autoprobed = 0, loading_module = 0; + +#endif /* MODULE */ + +static char name[EWRK3_STRLEN + 1]; +static int num_ewrk3s = 0, num_eth = 0; + +/* +** Miscellaneous defines... +*/ +#define INIT_EWRK3 {\ + outb(EEPROM_INIT, EWRK3_IOPR);\ + udelay(1000);\ +} + + + + +int ewrk3_probe(struct device *dev) +{ + int tmp = num_ewrk3s, status = -ENODEV; + u_long iobase = dev->base_addr; + + if ((iobase == 0) && loading_module){ + printk("Autoprobing is not supported when loading a module based driver.\n"); + status = -EIO; + } else { /* First probe for the Ethernet */ + /* Address PROM pattern */ + isa_probe(dev, iobase); + eisa_probe(dev, iobase); + + if ((tmp == num_ewrk3s) && (iobase != 0) && loading_module) { + printk("%s: ewrk3_probe() cannot find device at 0x%04lx.\n", dev->name, + iobase); + } + + /* + ** Walk the device list to check that at least one device + ** initialised OK + */ + for (; (dev->priv == NULL) && (dev->next != NULL); dev = dev->next); + + if (dev->priv) status = 0; + if (iobase == 0) autoprobed = 1; + } + + return status; +} + +static int +ewrk3_hw_init(struct device *dev, u_long iobase) +{ + struct ewrk3_private *lp; + int i, status=0; + u_long mem_start, shmem_length; + u_char cr, cmr, icr, nicsr, lemac, hard_strapped = 0; + u_char eeprom_image[EEPROM_MAX], chksum, eisa_cr = 0; + + /* + ** Stop the EWRK3. Enable the DBR ROM. Disable interrupts and remote boot. + ** This also disables the EISA_ENABLE bit in the EISA Control Register. + */ + if (iobase > 0x400) eisa_cr = inb(EISA_CR); + INIT_EWRK3; + + nicsr = inb(EWRK3_CSR); + + icr = inb(EWRK3_ICR); + icr &= 0x70; + outb(icr, EWRK3_ICR); /* Disable all the IRQs */ + + if (nicsr == (CSR_TXD|CSR_RXD)) { + + /* Check that the EEPROM is alive and well and not living on Pluto... */ + for (chksum=0, i=0; i<EEPROM_MAX; i+=2) { + union { + short val; + char c[2]; + } tmp; + + tmp.val = (short)Read_EEPROM(iobase, (i>>1)); + eeprom_image[i] = tmp.c[0]; + eeprom_image[i+1] = tmp.c[1]; + chksum += eeprom_image[i] + eeprom_image[i+1]; + } + + if (chksum != 0) { /* Bad EEPROM Data! */ + printk("%s: Device has a bad on-board EEPROM.\n", dev->name); + status = -ENXIO; + } else { + EthwrkSignature(name, eeprom_image); + if (*name != '\0') { /* found a EWRK3 device */ + dev->base_addr = iobase; + + if (iobase > 0x400) { + outb(eisa_cr, EISA_CR); /* Rewrite the EISA CR */ + } + + lemac = eeprom_image[EEPROM_CHIPVER]; + cmr = inb(EWRK3_CMR); + + if (((lemac == LeMAC) && ((cmr & CMR_NO_EEPROM) != CMR_NO_EEPROM)) || + ((lemac == LeMAC2) && !(cmr & CMR_HS))) { + printk("%s: %s at %#4lx", dev->name, name, iobase); + hard_strapped = 1; + } else if ((iobase&0x0fff)==EWRK3_EISA_IO_PORTS) { + /* EISA slot address */ + printk("%s: %s at %#4lx (EISA slot %ld)", + dev->name, name, iobase, ((iobase>>12)&0x0f)); + } else { /* ISA port address */ + printk("%s: %s at %#4lx", dev->name, name, iobase); + } + + if (!status) { + printk(", h/w address "); + if (lemac!=LeMAC2) DevicePresent(iobase);/* need after EWRK3_INIT */ + status = get_hw_addr(dev, eeprom_image, lemac); + for (i = 0; i < ETH_ALEN - 1; i++) { /* get the ethernet addr. */ + printk("%2.2x:", dev->dev_addr[i]); + } + printk("%2.2x,\n", dev->dev_addr[i]); + + if (status) { + printk(" which has an EEPROM CRC error.\n"); + status = -ENXIO; + } else { + if (lemac == LeMAC2) { /* Special LeMAC2 CMR things */ + cmr &= ~(CMR_RA | CMR_WB | CMR_LINK | CMR_POLARITY | CMR_0WS); + if (eeprom_image[EEPROM_MISC0] & READ_AHEAD) cmr |= CMR_RA; + if (eeprom_image[EEPROM_MISC0] & WRITE_BEHIND) cmr |= CMR_WB; + if (eeprom_image[EEPROM_NETMAN0] & NETMAN_POL) cmr |= CMR_POLARITY; + if (eeprom_image[EEPROM_NETMAN0] & NETMAN_LINK) cmr |= CMR_LINK; + if (eeprom_image[EEPROM_MISC0] & _0WS_ENA) cmr |= CMR_0WS; + } + if (eeprom_image[EEPROM_SETUP] & SETUP_DRAM) cmr |= CMR_DRAM; + outb(cmr, EWRK3_CMR); + + cr = inb(EWRK3_CR); /* Set up the Control Register */ + cr |= eeprom_image[EEPROM_SETUP] & SETUP_APD; + if (cr & SETUP_APD) cr |= eeprom_image[EEPROM_SETUP] & SETUP_PS; + cr |= eeprom_image[EEPROM_MISC0] & FAST_BUS; + cr |= eeprom_image[EEPROM_MISC0] & ENA_16; + outb(cr, EWRK3_CR); + + /* + ** Determine the base address and window length for the EWRK3 + ** RAM from the memory base register. + */ + mem_start = inb(EWRK3_MBR); + shmem_length = 0; + if (mem_start != 0) { + if ((mem_start >= 0x0a) && (mem_start <= 0x0f)) { + mem_start *= SHMEM_64K; + shmem_length = SHMEM_64K; + } else if ((mem_start >= 0x14) && (mem_start <= 0x1f)) { + mem_start *= SHMEM_32K; + shmem_length = SHMEM_32K; + } else if ((mem_start >= 0x40) && (mem_start <= 0xff)) { + mem_start = mem_start * SHMEM_2K + 0x80000; + shmem_length = SHMEM_2K; + } else { + status = -ENXIO; + } + } + + /* + ** See the top of this source code for comments about + ** uncommenting this line. + */ +/* FORCE_2K_MODE;*/ + + if (!status) { + if (hard_strapped) { + printk(" is hard strapped.\n"); + } else if (mem_start) { + printk(" has a %dk RAM window", (int)(shmem_length >> 10)); + printk(" at 0x%.5lx", mem_start); + } else { + printk(" is in I/O only mode"); + } + + /* private area & initialise */ + dev->priv = (void *) kmalloc(sizeof(struct ewrk3_private), + GFP_KERNEL); + if (dev->priv == NULL) { + return -ENOMEM; + } + lp = (struct ewrk3_private *)dev->priv; + memset(dev->priv, 0, sizeof(struct ewrk3_private)); + lp->shmem_base = mem_start; + lp->shmem_length = shmem_length; + lp->lemac = lemac; + lp->hard_strapped = hard_strapped; + + lp->mPage = 64; + if (cmr & CMR_DRAM) lp->mPage <<= 1 ;/* 2 DRAMS on module */ + + sprintf(lp->adapter_name,"%s (%s)", name, dev->name); + request_region(iobase, EWRK3_TOTAL_SIZE, lp->adapter_name); + + lp->irq_mask = ICR_TNEM|ICR_TXDM|ICR_RNEM|ICR_RXDM; + + if (!hard_strapped) { + /* + ** Enable EWRK3 board interrupts for autoprobing + */ + icr |= ICR_IE; /* Enable interrupts */ + outb(icr, EWRK3_ICR); + + /* The DMA channel may be passed in on this parameter. */ + dev->dma = 0; + + /* To auto-IRQ we enable the initialization-done and DMA err, + interrupts. For now we will always get a DMA error. */ + if (dev->irq < 2) { +#ifndef MODULE + u_char irqnum; + + autoirq_setup(0); + + /* + ** Trigger a TNE interrupt. + */ + icr |=ICR_TNEM; + outb(1,EWRK3_TDQ); /* Write to the TX done queue */ + outb(icr, EWRK3_ICR); /* Unmask the TXD interrupt */ + + irqnum = irq[((icr & IRQ_SEL) >> 4)]; + + dev->irq = autoirq_report(1); + if ((dev->irq) && (irqnum == dev->irq)) { + printk(" and uses IRQ%d.\n", dev->irq); + } else { + if (!dev->irq) { + printk(" and failed to detect IRQ line.\n"); + } else if ((irqnum == 1) && (lemac == LeMAC2)) { + printk(" and an illegal IRQ line detected.\n"); + } else { + printk(", but incorrect IRQ line detected.\n"); + } + status = -ENXIO; + } + + DISABLE_IRQs; /* Mask all interrupts */ + +#endif /* MODULE */ + } else { + printk(" and requires IRQ%d.\n", dev->irq); + } + } + if (status) release_region(iobase, EWRK3_TOTAL_SIZE); + } else { + status = -ENXIO; + } + } + } + } else { + status = -ENXIO; + } + } + + if (!status) { + if (ewrk3_debug > 1) { + printk(version); + } + + /* The EWRK3-specific entries in the device structure. */ + dev->open = &ewrk3_open; + dev->hard_start_xmit = &ewrk3_queue_pkt; + dev->stop = &ewrk3_close; + dev->get_stats = &ewrk3_get_stats; + dev->set_multicast_list = &set_multicast_list; + dev->do_ioctl = &ewrk3_ioctl; + + dev->mem_start = 0; + + /* Fill in the generic field of the device structure. */ + ether_setup(dev); + } + } else { + status = -ENXIO; + } + + return status; +} + + +static int +ewrk3_open(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + u_long iobase = dev->base_addr; + int i, status = 0; + u_char icr, csr; + + /* + ** Stop the TX and RX... + */ + STOP_EWRK3; + + if (!lp->hard_strapped) { + irq2dev_map[dev->irq] = dev; /* For latched interrupts */ + + if (request_irq(dev->irq, (void *)ewrk3_interrupt, 0, "ewrk3", NULL)) { + printk("ewrk3_open(): Requested IRQ%d is busy\n",dev->irq); + status = -EAGAIN; + } else { + + /* + ** Re-initialize the EWRK3... + */ + ewrk3_init(dev); + + if (ewrk3_debug > 1){ + printk("%s: ewrk3 open with irq %d\n",dev->name,dev->irq); + printk(" physical address: "); + for (i=0;i<5;i++){ + printk("%2.2x:",(u_char)dev->dev_addr[i]); + } + printk("%2.2x\n",(u_char)dev->dev_addr[i]); + if (lp->shmem_length == 0) { + printk(" no shared memory, I/O only mode\n"); + } else { + printk(" start of shared memory: 0x%08lx\n",lp->shmem_base); + printk(" window length: 0x%04lx\n",lp->shmem_length); + } + printk(" # of DRAMS: %d\n",((inb(EWRK3_CMR) & 0x02) ? 2 : 1)); + printk(" csr: 0x%02x\n", inb(EWRK3_CSR)); + printk(" cr: 0x%02x\n", inb(EWRK3_CR)); + printk(" icr: 0x%02x\n", inb(EWRK3_ICR)); + printk(" cmr: 0x%02x\n", inb(EWRK3_CMR)); + printk(" fmqc: 0x%02x\n", inb(EWRK3_FMQC)); + } + + dev->tbusy = 0; + dev->start = 1; + dev->interrupt = UNMASK_INTERRUPTS; + + /* + ** Unmask EWRK3 board interrupts + */ + icr = inb(EWRK3_ICR); + ENABLE_IRQs; + + } + } else { + dev->start = 0; + dev->tbusy = 1; + printk("%s: ewrk3 available for hard strapped set up only.\n", dev->name); + printk(" Run the 'ewrk3setup' utility or remove the hard straps.\n"); + } + + MOD_INC_USE_COUNT; + + return status; +} + +/* +** Initialize the EtherWORKS 3 operating conditions +*/ +static void +ewrk3_init(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + u_char csr, page; + u_long iobase = dev->base_addr; + + /* + ** Enable any multicasts + */ + set_multicast_list(dev); + + /* + ** Clean out any remaining entries in all the queues here + */ + while (inb(EWRK3_TQ)); + while (inb(EWRK3_TDQ)); + while (inb(EWRK3_RQ)); + while (inb(EWRK3_FMQ)); + + /* + ** Write a clean free memory queue + */ + for (page=1;page<lp->mPage;page++) { /* Write the free page numbers */ + outb(page, EWRK3_FMQ); /* to the Free Memory Queue */ + } + + lp->lock = 0; /* Ensure there are no locks */ + + START_EWRK3; /* Enable the TX and/or RX */ +} + +/* +** Writes a socket buffer to the free page queue +*/ +static int +ewrk3_queue_pkt(struct sk_buff *skb, struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + u_long iobase = dev->base_addr; + int status = 0; + u_char icr, csr; + + /* Transmitter timeout, serious problems. */ + if (dev->tbusy || lp->lock) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < QUEUE_PKT_TIMEOUT) { + status = -1; + } else if (!lp->hard_strapped) { + printk("%s: transmit timed/locked out, status %04x, resetting.\n", + dev->name, inb(EWRK3_CSR)); + + /* + ** Mask all board interrupts + */ + DISABLE_IRQs; + + /* + ** Stop the TX and RX... + */ + STOP_EWRK3; + + ewrk3_init(dev); + + /* + ** Unmask EWRK3 board interrupts + */ + ENABLE_IRQs; + + dev->tbusy=0; + dev->trans_start = jiffies; + dev_kfree_skb(skb, FREE_WRITE); + } + } else if (skb == NULL) { + dev_tint(dev); + } else if (skb->len > 0) { + + /* + ** Block a timer-based transmit from overlapping. This could better be + ** done with atomic_swap(1, dev->tbusy), but set_bit() works as well. + */ + if (set_bit(0, (void*)&dev->tbusy) != 0) + printk("%s: Transmitter access conflict.\n", dev->name); + + DISABLE_IRQs; /* So that the page # remains correct */ + + /* + ** Get a free page from the FMQ when resources are available + */ + if (inb(EWRK3_FMQC) > 0) { + u_long buf = 0; + u_char page; + + if ((page = inb(EWRK3_FMQ)) < lp->mPage) { + /* + ** Set up shared memory window and pointer into the window + */ + while (set_bit(0, (void *)&lp->lock) != 0); /* Wait for lock to free */ + if (lp->shmem_length == IO_ONLY) { + outb(page, EWRK3_IOPR); + } else if (lp->shmem_length == SHMEM_2K) { + buf = lp->shmem_base; + outb(page, EWRK3_MPR); + } else if (lp->shmem_length == SHMEM_32K) { + buf = ((((short)page << 11) & 0x7800) + lp->shmem_base); + outb((page >> 4), EWRK3_MPR); + } else if (lp->shmem_length == SHMEM_64K) { + buf = ((((short)page << 11) & 0xf800) + lp->shmem_base); + outb((page >> 5), EWRK3_MPR); + } else { + status = -1; + printk("%s: Oops - your private data area is hosed!\n",dev->name); + } + + if (!status) { + + /* + ** Set up the buffer control structures and copy the data from + ** the socket buffer to the shared memory . + */ + + if (lp->shmem_length == IO_ONLY) { + int i; + u_char *p = skb->data; + + outb((char)(TCR_QMODE | TCR_PAD | TCR_IFC), EWRK3_DATA); + outb((char)(skb->len & 0xff), EWRK3_DATA); + outb((char)((skb->len >> 8) & 0xff), EWRK3_DATA); + outb((char)0x04, EWRK3_DATA); + for (i=0; i<skb->len; i++) { + outb(*p++, EWRK3_DATA); + } + outb(page, EWRK3_TQ); /* Start sending pkt */ + } else { + writeb((char)(TCR_QMODE|TCR_PAD|TCR_IFC), (char *)buf);/* ctrl byte*/ + buf+=1; + writeb((char)(skb->len & 0xff), (char *)buf);/* length (16 bit xfer)*/ + buf+=1; + if (lp->txc) { + writeb((char)(((skb->len >> 8) & 0xff) | XCT), (char *)buf); + buf+=1; + writeb(0x04, (char *)buf); /* index byte */ + buf+=1; + writeb(0x00, (char *)(buf + skb->len)); /* Write the XCT flag */ + memcpy_toio(buf, skb->data, PRELOAD);/* Write PRELOAD bytes*/ + outb(page, EWRK3_TQ); /* Start sending pkt */ + memcpy_toio(buf+PRELOAD, skb->data+PRELOAD, skb->len-PRELOAD); + writeb(0xff, (char *)(buf + skb->len)); /* Write the XCT flag */ + } else { + writeb((char)((skb->len >> 8) & 0xff), (char *)buf); + buf+=1; + writeb(0x04, (char *)buf); /* index byte */ + buf+=1; + memcpy_toio((char *)buf, skb->data, skb->len);/* Write data bytes */ + outb(page, EWRK3_TQ); /* Start sending pkt */ + } + } + + dev->trans_start = jiffies; + dev_kfree_skb (skb, FREE_WRITE); + + } else { /* return unused page to the free memory queue */ + outb(page, EWRK3_FMQ); + } + lp->lock = 0; /* unlock the page register */ + } else { + printk("ewrk3_queue_pkt(): Invalid free memory page (%d).\n", + (u_char) page); + } + } else { + printk("ewrk3_queue_pkt(): No free resources...\n"); + printk("ewrk3_queue_pkt(): CSR: %02x ICR: %02x FMQC: %02x\n",inb(EWRK3_CSR),inb(EWRK3_ICR),inb(EWRK3_FMQC)); + } + + /* Check for free resources: clear 'tbusy' if there are some */ + if (inb(EWRK3_FMQC) > 0) { + dev->tbusy = 0; + } + + ENABLE_IRQs; + } + + return status; +} + +/* +** The EWRK3 interrupt handler. +*/ +static void +ewrk3_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct ewrk3_private *lp; + u_long iobase; + u_char icr, cr, csr; + + if (dev == NULL) { + printk ("ewrk3_interrupt(): irq %d for unknown device.\n", irq); + } else { + lp = (struct ewrk3_private *)dev->priv; + iobase = dev->base_addr; + + if (dev->interrupt) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + + dev->interrupt = MASK_INTERRUPTS; + + /* get the interrupt information */ + csr = inb(EWRK3_CSR); + + /* + ** Mask the EWRK3 board interrupts and turn on the LED + */ + DISABLE_IRQs; + + cr = inb(EWRK3_CR); + cr |= CR_LED; + outb(cr, EWRK3_CR); + + if (csr & CSR_RNE) /* Rx interrupt (packet[s] arrived) */ + ewrk3_rx(dev); + + if (csr & CSR_TNE) /* Tx interrupt (packet sent) */ + ewrk3_tx(dev); + + /* + ** Now deal with the TX/RX disable flags. These are set when there + ** are no more resources. If resources free up then enable these + ** interrupts, otherwise mask them - failure to do this will result + ** in the system hanging in an interrupt loop. + */ + if (inb(EWRK3_FMQC)) { /* any resources available? */ + lp->irq_mask |= ICR_TXDM|ICR_RXDM;/* enable the interrupt source */ + csr &= ~(CSR_TXD|CSR_RXD);/* ensure restart of a stalled TX or RX */ + outb(csr, EWRK3_CSR); + dev->tbusy = 0; /* clear TX busy flag */ + mark_bh(NET_BH); + } else { + lp->irq_mask &= ~(ICR_TXDM|ICR_RXDM);/* disable the interrupt source */ + } + + /* Unmask the EWRK3 board interrupts and turn off the LED */ + cr &= ~CR_LED; + outb(cr, EWRK3_CR); + + dev->interrupt = UNMASK_INTERRUPTS; + ENABLE_IRQs; + } + + return; +} + +static int +ewrk3_rx(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + u_long iobase = dev->base_addr; + int i, status = 0; + u_char page, tmpPage = 0, tmpLock = 0; + u_long buf = 0; + + while (inb(EWRK3_RQC) && !status) { /* Whilst there's incoming data */ + if ((page = inb(EWRK3_RQ)) < lp->mPage) {/* Get next entry's buffer page */ + /* + ** Preempt any process using the current page register. Check for + ** an existing lock to reduce time taken in I/O transactions. + */ + if ((tmpLock = set_bit(0, (void *)&lp->lock)) == 1) { /* Assert lock */ + if (lp->shmem_length == IO_ONLY) { /* Get existing page */ + tmpPage = inb(EWRK3_IOPR); + } else { + tmpPage = inb(EWRK3_MPR); + } + } + + /* + ** Set up shared memory window and pointer into the window + */ + if (lp->shmem_length == IO_ONLY) { + outb(page, EWRK3_IOPR); + } else if (lp->shmem_length == SHMEM_2K) { + buf = lp->shmem_base; + outb(page, EWRK3_MPR); + } else if (lp->shmem_length == SHMEM_32K) { + buf = ((((short)page << 11) & 0x7800) + lp->shmem_base); + outb((page >> 4), EWRK3_MPR); + } else if (lp->shmem_length == SHMEM_64K) { + buf = ((((short)page << 11) & 0xf800) + lp->shmem_base); + outb((page >> 5), EWRK3_MPR); + } else { + status = -1; + printk("%s: Oops - your private data area is hosed!\n",dev->name); + } + + if (!status) { + char rx_status; + int pkt_len; + + if (lp->shmem_length == IO_ONLY) { + rx_status = inb(EWRK3_DATA); + pkt_len = inb(EWRK3_DATA); + pkt_len |= ((u_short)inb(EWRK3_DATA) << 8); + } else { + rx_status = readb(buf); + buf+=1; + pkt_len = readw(buf); + buf+=3; + } + + if (!(rx_status & R_ROK)) { /* There was an error. */ + lp->stats.rx_errors++; /* Update the error stats. */ + if (rx_status & R_DBE) lp->stats.rx_frame_errors++; + if (rx_status & R_CRC) lp->stats.rx_crc_errors++; + if (rx_status & R_PLL) lp->stats.rx_fifo_errors++; + } else { + struct sk_buff *skb; + + if ((skb = dev_alloc_skb(pkt_len+2)) != NULL) { + unsigned char *p; + skb->dev = dev; + skb_reserve(skb,2); /* Align to 16 bytes */ + p = skb_put(skb,pkt_len); + + if (lp->shmem_length == IO_ONLY) { + *p = inb(EWRK3_DATA); /* dummy read */ + for (i=0; i<pkt_len; i++) { + *p++ = inb(EWRK3_DATA); + } + } else { + memcpy_fromio(p, buf, pkt_len); + } + + /* + ** Notify the upper protocol layers that there is another + ** packet to handle + */ + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + + /* + ** Update stats + */ + lp->stats.rx_packets++; + for (i=1; i<EWRK3_PKT_STAT_SZ-1; i++) { + if (pkt_len < i*EWRK3_PKT_BIN_SZ) { + lp->pktStats.bins[i]++; + i = EWRK3_PKT_STAT_SZ; + } + } + p = skb->data; /* Look at the dest addr */ + if (p[0] & 0x01) { /* Multicast/Broadcast */ + if ((*(s32 *)&p[0] == -1) && (*(s16 *)&p[4] == -1)) { + lp->pktStats.broadcast++; + } else { + lp->pktStats.multicast++; + } + } else if ((*(s32 *)&p[0] == *(s32 *)&dev->dev_addr[0]) && + (*(s16 *)&p[4] == *(s16 *)&dev->dev_addr[4])) { + lp->pktStats.unicast++; + } + + lp->pktStats.bins[0]++; /* Duplicates stats.rx_packets */ + if (lp->pktStats.bins[0] == 0) { /* Reset counters */ + memset(&lp->pktStats, 0, sizeof(lp->pktStats)); + } + } else { + printk("%s: Insufficient memory; nuking packet.\n", dev->name); + lp->stats.rx_dropped++; /* Really, deferred. */ + break; + } + } + } + /* + ** Return the received buffer to the free memory queue + */ + outb(page, EWRK3_FMQ); + + if (tmpLock) { /* If a lock was preempted */ + if (lp->shmem_length == IO_ONLY) { /* Replace old page */ + outb(tmpPage, EWRK3_IOPR); + } else { + outb(tmpPage, EWRK3_MPR); + } + } + lp->lock = 0; /* Unlock the page register */ + } else { + printk("ewrk3_rx(): Illegal page number, page %d\n",page); + printk("ewrk3_rx(): CSR: %02x ICR: %02x FMQC: %02x\n",inb(EWRK3_CSR),inb(EWRK3_ICR),inb(EWRK3_FMQC)); + } + } + return status; +} + +/* +** Buffer sent - check for TX buffer errors. +*/ +static int +ewrk3_tx(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + u_long iobase = dev->base_addr; + u_char tx_status; + + while ((tx_status = inb(EWRK3_TDQ)) > 0) { /* Whilst there's old buffers */ + if (tx_status & T_VSTS) { /* The status is valid */ + if (tx_status & T_TXE) { + lp->stats.tx_errors++; + if (tx_status & T_NCL) lp->stats.tx_carrier_errors++; + if (tx_status & T_LCL) lp->stats.tx_window_errors++; + if (tx_status & T_CTU) { + if ((tx_status & T_COLL) ^ T_XUR) { + lp->pktStats.tx_underruns++; + } else { + lp->pktStats.excessive_underruns++; + } + } else if (tx_status & T_COLL) { + if ((tx_status & T_COLL) ^ T_XCOLL) { + lp->stats.collisions++; + } else { + lp->pktStats.excessive_collisions++; + } + } + } else { + lp->stats.tx_packets++; + } + } + } + + return 0; +} + +static int +ewrk3_close(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + u_long iobase = dev->base_addr; + u_char icr, csr; + + dev->start = 0; + dev->tbusy = 1; + + if (ewrk3_debug > 1) { + printk("%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, inb(EWRK3_CSR)); + } + + /* + ** We stop the EWRK3 here... mask interrupts and stop TX & RX + */ + DISABLE_IRQs; + + STOP_EWRK3; + + /* + ** Clean out the TX and RX queues here (note that one entry + ** may get added to either the TXD or RX queues if the TX or RX + ** just starts processing a packet before the STOP_EWRK3 command + ** is received. This will be flushed in the ewrk3_open() call). + */ + while (inb(EWRK3_TQ)); + while (inb(EWRK3_TDQ)); + while (inb(EWRK3_RQ)); + + if (!lp->hard_strapped) { + free_irq(dev->irq, NULL); + + irq2dev_map[dev->irq] = 0; + } + + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics * +ewrk3_get_stats(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + + /* Null body since there is no framing error counter */ + + return &lp->stats; +} + +/* +** Set or clear the multicast filter for this adapter. +*/ +static void +set_multicast_list(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + u_long iobase = dev->base_addr; + u_char csr; + + if (irq2dev_map[dev->irq] != NULL) { + csr = inb(EWRK3_CSR); + + if (lp->shmem_length == IO_ONLY) { + lp->mctbl = (char *) PAGE0_HTE; + } else { + lp->mctbl = (char *)(lp->shmem_base + PAGE0_HTE); + } + + csr &= ~(CSR_PME | CSR_MCE); + if (dev->flags & IFF_PROMISC) { /* set promiscuous mode */ + csr |= CSR_PME; + outb(csr, EWRK3_CSR); + } else { + SetMulticastFilter(dev); + csr |= CSR_MCE; + outb(csr, EWRK3_CSR); + } + } +} + +/* +** Calculate the hash code and update the logical address filter +** from a list of ethernet multicast addresses. +** Little endian crc one liner from Matt Thomas, DEC. +** +** Note that when clearing the table, the broadcast bit must remain asserted +** to receive broadcast messages. +*/ +static void SetMulticastFilter(struct device *dev) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + struct dev_mc_list *dmi=dev->mc_list; + u_long iobase = dev->base_addr; + int i; + char *addrs, j, bit, byte; + short *p = (short *) lp->mctbl; + u16 hashcode; + s32 crc, poly = CRC_POLYNOMIAL_LE; + + while (set_bit(0, (void *)&lp->lock) != 0); /* Wait for lock to free */ + + if (lp->shmem_length == IO_ONLY) { + outb(0, EWRK3_IOPR); + outw(EEPROM_OFFSET(lp->mctbl), EWRK3_PIR1); + } else { + outb(0, EWRK3_MPR); + } + + if (dev->flags & IFF_ALLMULTI) { + for (i=0; i<(HASH_TABLE_LEN >> 3); i++) { + if (lp->shmem_length == IO_ONLY) { + outb(0xff, EWRK3_DATA); + } else { /* memset didn't work here */ + writew(0xffff, p); + p++; i++; + } + } + } else { + /* Clear table except for broadcast bit */ + if (lp->shmem_length == IO_ONLY) { + for (i=0; i<(HASH_TABLE_LEN >> 4) - 1; i++) { + outb(0x00, EWRK3_DATA); + } + outb(0x80, EWRK3_DATA); i++; /* insert the broadcast bit */ + for (; i<(HASH_TABLE_LEN >> 3); i++) { + outb(0x00, EWRK3_DATA); + } + } else { + memset_io(lp->mctbl, 0, (HASH_TABLE_LEN >> 3)); + writeb(0x80, (char *)(lp->mctbl + (HASH_TABLE_LEN >> 4) - 1)); + } + + /* Update table */ + for (i=0;i<dev->mc_count;i++) { /* for each address in the list */ + addrs=dmi->dmi_addr; + dmi=dmi->next; + if ((*addrs & 0x01) == 1) { /* multicast address? */ + crc = 0xffffffff; /* init CRC for each address */ + for (byte=0;byte<ETH_ALEN;byte++) { /* for each address byte */ + /* process each address bit */ + for (bit = *addrs++,j=0;j<8;j++, bit>>=1) { + crc = (crc >> 1) ^ (((crc ^ bit) & 0x01) ? poly : 0); + } + } + hashcode = crc & ((1 << 9) - 1); /* hashcode is 9 LSb of CRC */ + + byte = hashcode >> 3; /* bit[3-8] -> byte in filter */ + bit = 1 << (hashcode & 0x07); /* bit[0-2] -> bit in byte */ + + if (lp->shmem_length == IO_ONLY) { + u_char tmp; + + outw((short)((long)lp->mctbl) + byte, EWRK3_PIR1); + tmp = inb(EWRK3_DATA); + tmp |= bit; + outw((short)((long)lp->mctbl) + byte, EWRK3_PIR1); + outb(tmp, EWRK3_DATA); + } else { + writeb(readb(lp->mctbl + byte) | bit, lp->mctbl + byte); + } + } + } + } + + lp->lock = 0; /* Unlock the page register */ + + return; +} + +/* +** ISA bus I/O device probe +*/ +static void isa_probe(struct device *dev, u_long ioaddr) +{ + int i = num_ewrk3s, maxSlots; + u_long iobase; + + if (!ioaddr && autoprobed) return ; /* Been here before ! */ + if (ioaddr >= 0x400) return; /* Not ISA */ + + if (ioaddr == 0) { /* Autoprobing */ + iobase = EWRK3_IO_BASE; /* Get the first slot address */ + maxSlots = 24; + } else { /* Probe a specific location */ + iobase = ioaddr; + maxSlots = i + 1; + } + + for (; (i<maxSlots) && (dev!=NULL);iobase+=EWRK3_IOP_INC, i++) { + if (!check_region(iobase, EWRK3_TOTAL_SIZE)) { + if (DevicePresent(iobase) == 0) { + if ((dev = alloc_device(dev, iobase)) != NULL) { + if (ewrk3_hw_init(dev, iobase) == 0) { + num_ewrk3s++; + } + num_eth++; + } + } + } else if (autoprobed) { + printk("%s: region already allocated at 0x%04lx.\n", dev->name, iobase); + } + } + + return; +} + +/* +** EISA bus I/O device probe. Probe from slot 1 since slot 0 is usually +** the motherboard. +*/ +static void eisa_probe(struct device *dev, u_long ioaddr) +{ + int i, maxSlots; + u_long iobase; + char name[EWRK3_STRLEN]; + + if (!ioaddr && autoprobed) return ; /* Been here before ! */ + if (ioaddr < 0x1000) return; /* Not EISA */ + + if (ioaddr == 0) { /* Autoprobing */ + iobase = EISA_SLOT_INC; /* Get the first slot address */ + i = 1; + maxSlots = MAX_EISA_SLOTS; + } else { /* Probe a specific location */ + iobase = ioaddr; + i = (ioaddr >> 12); + maxSlots = i + 1; + } + + for (i=1; (i<maxSlots) && (dev!=NULL); i++, iobase+=EISA_SLOT_INC) { + if (EISA_signature(name, EISA_ID) == 0) { + if (!check_region(iobase, EWRK3_TOTAL_SIZE)) { + if (DevicePresent(iobase) == 0) { + if ((dev = alloc_device(dev, iobase)) != NULL) { + if (ewrk3_hw_init(dev, iobase) == 0) { + num_ewrk3s++; + } + num_eth++; + } + } + } else if (autoprobed) { + printk("%s: region already allocated at 0x%04lx.\n", dev->name, iobase); + } + } + } + + return; +} + +/* +** Search the entire 'eth' device list for a fixed probe. If a match isn't +** found then check for an autoprobe or unused device location. If they +** are not available then insert a new device structure at the end of +** the current list. +*/ +static struct device * +alloc_device(struct device *dev, u_long iobase) +{ + struct device *adev = NULL; + int fixed = 0, new_dev = 0; + + num_eth = ewrk3_dev_index(dev->name); + if (loading_module) return dev; + + while (1) { + if (((dev->base_addr == EWRK3_NDA) || (dev->base_addr==0)) && !adev) { + adev=dev; + } else if ((dev->priv == NULL) && (dev->base_addr==iobase)) { + fixed = 1; + } else { + if (dev->next == NULL) { + new_dev = 1; + } else if (strncmp(dev->next->name, "eth", 3) != 0) { + new_dev = 1; + } + } + if ((dev->next == NULL) || new_dev || fixed) break; + dev = dev->next; + num_eth++; + } + if (adev && !fixed) { + dev = adev; + num_eth = ewrk3_dev_index(dev->name); + new_dev = 0; + } + + if (((dev->next == NULL) && + ((dev->base_addr != EWRK3_NDA) && (dev->base_addr != 0)) && !fixed) || + new_dev) { + num_eth++; /* New device */ + dev = insert_device(dev, iobase, ewrk3_probe); + } + + return dev; +} + +/* +** If at end of eth device list and can't use current entry, malloc +** one up. If memory could not be allocated, print an error message. +*/ +static struct device * +insert_device(struct device *dev, u_long iobase, int (*init)(struct device *)) +{ + struct device *new; + + new = (struct device *)kmalloc(sizeof(struct device)+8, GFP_KERNEL); + if (new == NULL) { + printk("eth%d: Device not initialised, insufficient memory\n",num_eth); + return NULL; + } else { + new->next = dev->next; + dev->next = new; + dev = dev->next; /* point to the new device */ + dev->name = (char *)(dev + 1); + if (num_eth > 9999) { + sprintf(dev->name,"eth????");/* New device name */ + } else { + sprintf(dev->name,"eth%d", num_eth);/* New device name */ + } + dev->base_addr = iobase; /* assign the io address */ + dev->init = init; /* initialisation routine */ + } + + return dev; +} + +static int +ewrk3_dev_index(char *s) +{ + int i=0, j=0; + + for (;*s; s++) { + if (isdigit(*s)) { + j=1; + i = (i * 10) + (*s - '0'); + } else if (j) break; + } + + return i; +} + +/* +** Read the EWRK3 EEPROM using this routine +*/ +static int Read_EEPROM(u_long iobase, u_char eaddr) +{ + int i; + + outb((eaddr & 0x3f), EWRK3_PIR1); /* set up 6 bits of address info */ + outb(EEPROM_RD, EWRK3_IOPR); /* issue read command */ + for (i=0;i<5000;i++) inb(EWRK3_CSR); /* wait 1msec */ + + return inw(EWRK3_EPROM1); /* 16 bits data return */ +} + +/* +** Write the EWRK3 EEPROM using this routine +*/ +static int Write_EEPROM(short data, u_long iobase, u_char eaddr) +{ + int i; + + outb(EEPROM_WR_EN, EWRK3_IOPR); /* issue write enable command */ + for (i=0;i<5000;i++) inb(EWRK3_CSR); /* wait 1msec */ + outw(data, EWRK3_EPROM1); /* write data to register */ + outb((eaddr & 0x3f), EWRK3_PIR1); /* set up 6 bits of address info */ + outb(EEPROM_WR, EWRK3_IOPR); /* issue write command */ + for (i=0;i<75000;i++) inb(EWRK3_CSR); /* wait 15msec */ + outb(EEPROM_WR_DIS, EWRK3_IOPR); /* issue write disable command */ + for (i=0;i<5000;i++) inb(EWRK3_CSR); /* wait 1msec */ + + return 0; +} + +/* +** Look for a particular board name in the on-board EEPROM. +*/ +static void EthwrkSignature(char *name, char *eeprom_image) +{ + u_long i,j,k; + char *signatures[] = EWRK3_SIGNATURE; + + strcpy(name, ""); + for (i=0;*signatures[i] != '\0' && *name == '\0';i++) { + for (j=EEPROM_PNAME7,k=0;j<=EEPROM_PNAME0 && k<strlen(signatures[i]);j++) { + if (signatures[i][k] == eeprom_image[j]) { /* track signature */ + k++; + } else { /* lost signature; begin search again */ + k=0; + } + } + if (k == strlen(signatures[i])) { + for (k=0; k<EWRK3_STRLEN; k++) { + name[k] = eeprom_image[EEPROM_PNAME7 + k]; + name[EWRK3_STRLEN] = '\0'; + } + } + } + + return; /* return the device name string */ +} + +/* +** Look for a special sequence in the Ethernet station address PROM that +** is common across all EWRK3 products. +** +** Search the Ethernet address ROM for the signature. Since the ROM address +** counter can start at an arbitrary point, the search must include the entire +** probe sequence length plus the (length_of_the_signature - 1). +** Stop the search IMMEDIATELY after the signature is found so that the +** PROM address counter is correctly positioned at the start of the +** ethernet address for later read out. +*/ + +static int DevicePresent(u_long iobase) +{ + union { + struct { + u32 a; + u32 b; + } llsig; + char Sig[sizeof(u32) << 1]; + } dev; + short sigLength; + char data; + int i, j, status = 0; + + dev.llsig.a = ETH_PROM_SIG; + dev.llsig.b = ETH_PROM_SIG; + sigLength = sizeof(u32) << 1; + + for (i=0,j=0;j<sigLength && i<PROBE_LENGTH+sigLength-1;i++) { + data = inb(EWRK3_APROM); + if (dev.Sig[j] == data) { /* track signature */ + j++; + } else { /* lost signature; begin search again */ + if (data == dev.Sig[0]) { + j=1; + } else { + j=0; + } + } + } + + if (j!=sigLength) { + status = -ENODEV; /* search failed */ + } + + return status; +} + +static u_char get_hw_addr(struct device *dev, u_char *eeprom_image, char chipType) +{ + int i, j, k; + u_short chksum; + u_char crc, lfsr, sd, status = 0; + u_long iobase = dev->base_addr; + u16 tmp; + + if (chipType == LeMAC2) { + for (crc=0x6a, j=0; j<ETH_ALEN; j++) { + sd = dev->dev_addr[j] = eeprom_image[EEPROM_PADDR0 + j]; + outb(dev->dev_addr[j], EWRK3_PAR0 + j); + for (k=0; k<8; k++, sd >>= 1) { + lfsr = ((((crc & 0x02) >> 1) ^ (crc & 0x01)) ^ (sd & 0x01)) << 7; + crc = (crc >> 1) + lfsr; + } + } + if (crc != eeprom_image[EEPROM_PA_CRC]) status = -1; + } else { + for (i=0,k=0;i<ETH_ALEN;) { + k <<= 1 ; + if (k > 0xffff) k-=0xffff; + + k += (u_char) (tmp = inb(EWRK3_APROM)); + dev->dev_addr[i] = (u_char) tmp; + outb(dev->dev_addr[i], EWRK3_PAR0 + i); + i++; + k += (u_short) ((tmp = inb(EWRK3_APROM)) << 8); + dev->dev_addr[i] = (u_char) tmp; + outb(dev->dev_addr[i], EWRK3_PAR0 + i); + i++; + + if (k > 0xffff) k-=0xffff; + } + if (k == 0xffff) k=0; + chksum = inb(EWRK3_APROM); + chksum |= (inb(EWRK3_APROM)<<8); + if (k != chksum) status = -1; + } + + return status; +} + +/* +** Look for a particular board name in the EISA configuration space +*/ +static int EISA_signature(char *name, s32 eisa_id) +{ + u_long i; + char *signatures[] = EWRK3_SIGNATURE; + char ManCode[EWRK3_STRLEN]; + union { + s32 ID; + char Id[4]; + } Eisa; + int status = 0; + + *name = '\0'; + for (i=0; i<4; i++) { + Eisa.Id[i] = inb(eisa_id + i); + } + + ManCode[0]=(((Eisa.Id[0]>>2)&0x1f)+0x40); + ManCode[1]=(((Eisa.Id[1]&0xe0)>>5)+((Eisa.Id[0]&0x03)<<3)+0x40); + ManCode[2]=(((Eisa.Id[2]>>4)&0x0f)+0x30); + ManCode[3]=((Eisa.Id[2]&0x0f)+0x30); + ManCode[4]=(((Eisa.Id[3]>>4)&0x0f)+0x30); + ManCode[5]='\0'; + + for (i=0;(*signatures[i] != '\0') && (*name == '\0');i++) { + if (strstr(ManCode, signatures[i]) != NULL) { + strcpy(name,ManCode); + status = 1; + } + } + + return status; /* return the device name string */ +} + +/* +** Perform IOCTL call functions here. Some are privileged operations and the +** effective uid is checked in those cases. +*/ +static int ewrk3_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct ewrk3_private *lp = (struct ewrk3_private *)dev->priv; + struct ewrk3_ioctl *ioc = (struct ewrk3_ioctl *) &rq->ifr_data; + u_long iobase = dev->base_addr; + int i, j, status = 0; + u_char csr; + union { + u_char addr[HASH_TABLE_LEN * ETH_ALEN]; + u_short val[(HASH_TABLE_LEN * ETH_ALEN) >> 1]; + } tmp; + + switch(ioc->cmd) { + case EWRK3_GET_HWADDR: /* Get the hardware address */ + for (i=0; i<ETH_ALEN; i++) { + tmp.addr[i] = dev->dev_addr[i]; + } + ioc->len = ETH_ALEN; + if (!(status = verify_area(VERIFY_WRITE, (void *)ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + } + + break; + case EWRK3_SET_HWADDR: /* Set the hardware address */ + if (suser()) { + if (!(status = verify_area(VERIFY_READ, (void *)ioc->data, ETH_ALEN))) { + csr = inb(EWRK3_CSR); + csr |= (CSR_TXD|CSR_RXD); + outb(csr, EWRK3_CSR); /* Disable the TX and RX */ + + memcpy_fromfs(tmp.addr,ioc->data,ETH_ALEN); + for (i=0; i<ETH_ALEN; i++) { + dev->dev_addr[i] = tmp.addr[i]; + outb(tmp.addr[i], EWRK3_PAR0 + i); + } + + csr &= ~(CSR_TXD|CSR_RXD); /* Enable the TX and RX */ + outb(csr, EWRK3_CSR); + } + } else { + status = -EPERM; + } + + break; + case EWRK3_SET_PROM: /* Set Promiscuous Mode */ + if (suser()) { + csr = inb(EWRK3_CSR); + csr |= CSR_PME; + csr &= ~CSR_MCE; + outb(csr, EWRK3_CSR); + } else { + status = -EPERM; + } + + break; + case EWRK3_CLR_PROM: /* Clear Promiscuous Mode */ + if (suser()) { + csr = inb(EWRK3_CSR); + csr &= ~CSR_PME; + outb(csr, EWRK3_CSR); + } else { + status = -EPERM; + } + + break; + case EWRK3_SAY_BOO: /* Say "Boo!" to the kernel log file */ + printk("%s: Boo!\n", dev->name); + + break; + case EWRK3_GET_MCA: /* Get the multicast address table */ + if (!(status = verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + while (set_bit(0, (void *)&lp->lock) != 0); /* Wait for lock to free */ + if (lp->shmem_length == IO_ONLY) { + outb(0, EWRK3_IOPR); + outw(PAGE0_HTE, EWRK3_PIR1); + for (i=0; i<(HASH_TABLE_LEN >> 3); i++) { + tmp.addr[i] = inb(EWRK3_DATA); + } + } else { + outb(0, EWRK3_MPR); + memcpy_fromio(tmp.addr, (char *)(lp->shmem_base + PAGE0_HTE), (HASH_TABLE_LEN >> 3)); + } + ioc->len = (HASH_TABLE_LEN >> 3); + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + } + lp->lock = 0; /* Unlock the page register */ + + break; + case EWRK3_SET_MCA: /* Set a multicast address */ + if (suser()) { + if (!(status=verify_area(VERIFY_READ, ioc->data, ETH_ALEN*ioc->len))) { + memcpy_fromfs(tmp.addr, ioc->data, ETH_ALEN * ioc->len); + set_multicast_list(dev); + } + } else { + status = -EPERM; + } + + break; + case EWRK3_CLR_MCA: /* Clear all multicast addresses */ + if (suser()) { + set_multicast_list(dev); + } else { + status = -EPERM; + } + + break; + case EWRK3_MCA_EN: /* Enable multicast addressing */ + if (suser()) { + csr = inb(EWRK3_CSR); + csr |= CSR_MCE; + csr &= ~CSR_PME; + outb(csr, EWRK3_CSR); + } else { + status = -EPERM; + } + + break; + case EWRK3_GET_STATS: /* Get the driver statistics */ + cli(); + ioc->len = sizeof(lp->pktStats); + if (!(status=verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, &lp->pktStats, ioc->len); + } + sti(); + + break; + case EWRK3_CLR_STATS: /* Zero out the driver statistics */ + if (suser()) { + cli(); + memset(&lp->pktStats, 0, sizeof(lp->pktStats)); + sti(); + } else { + status = -EPERM; + } + + break; + case EWRK3_GET_CSR: /* Get the CSR Register contents */ + tmp.addr[0] = inb(EWRK3_CSR); + ioc->len = 1; + if (!(status=verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + } + + break; + case EWRK3_SET_CSR: /* Set the CSR Register contents */ + if (suser()) { + if (!(status=verify_area(VERIFY_READ, ioc->data, 1))) { + memcpy_fromfs(tmp.addr, ioc->data, 1); + outb(tmp.addr[0], EWRK3_CSR); + } + } else { + status = -EPERM; + } + + break; + case EWRK3_GET_EEPROM: /* Get the EEPROM contents */ + if (suser()) { + for (i=0; i<(EEPROM_MAX>>1); i++) { + tmp.val[i] = (short)Read_EEPROM(iobase, i); + } + i = EEPROM_MAX; + tmp.addr[i++] = inb(EWRK3_CMR); /* Config/Management Reg. */ + for (j=0;j<ETH_ALEN;j++) { + tmp.addr[i++] = inb(EWRK3_PAR0 + j); + } + ioc->len = EEPROM_MAX + 1 + ETH_ALEN; + if (!(status=verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + } + } else { + status = -EPERM; + } + + break; + case EWRK3_SET_EEPROM: /* Set the EEPROM contents */ + if (suser()) { + if (!(status=verify_area(VERIFY_READ, ioc->data, EEPROM_MAX))) { + memcpy_fromfs(tmp.addr, ioc->data, EEPROM_MAX); + for (i=0; i<(EEPROM_MAX>>1); i++) { + Write_EEPROM(tmp.val[i], iobase, i); + } + } + } else { + status = -EPERM; + } + + break; + case EWRK3_GET_CMR: /* Get the CMR Register contents */ + tmp.addr[0] = inb(EWRK3_CMR); + ioc->len = 1; + if (!(status=verify_area(VERIFY_WRITE, ioc->data, ioc->len))) { + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + } + + break; + case EWRK3_SET_TX_CUT_THRU: /* Set TX cut through mode */ + if (suser()) { + lp->txc = 1; + } else { + status = -EPERM; + } + + break; + case EWRK3_CLR_TX_CUT_THRU: /* Clear TX cut through mode */ + if (suser()) { + lp->txc = 0; + } else { + status = -EPERM; + } + + break; + default: + status = -EOPNOTSUPP; + } + + return status; +} + +#ifdef MODULE +static char devicename[9] = { 0, }; +static struct device thisEthwrk = { + devicename, /* device name is inserted by /linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0x300, 5, /* I/O address, IRQ */ + 0, 0, 0, NULL, ewrk3_probe }; + +static int io=0x300; /* <--- EDIT THESE LINES FOR YOUR CONFIGURATION */ +static int irq=5; /* or use the insmod io= irq= options */ + +int +init_module(void) +{ + thisEthwrk.base_addr=io; + thisEthwrk.irq=irq; + if (register_netdev(&thisEthwrk) != 0) + return -EIO; + return 0; +} + +void +cleanup_module(void) +{ + if (thisEthwrk.priv) { + kfree(thisEthwrk.priv); + thisEthwrk.priv = NULL; + } + thisEthwrk.irq = 0; + + unregister_netdev(&thisEthwrk); + release_region(thisEthwrk.base_addr, EWRK3_TOTAL_SIZE); +} +#endif /* MODULE */ + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/linux/include -Wall -Wstrict-prototypes -fomit-frame-pointer -fno-strength-reduce -malign-loops=2 -malign-jumps=2 -malign-functions=2 -O2 -m486 -c ewrk3.c" + * + * compile-command: "gcc -D__KERNEL__ -DMODULE -I/linux/include -Wall -Wstrict-prototypes -fomit-frame-pointer -fno-strength-reduce -malign-loops=2 -malign-jumps=2 -malign-functions=2 -O2 -m486 -c ewrk3.c" + * End: + */ + diff --git a/linux/src/drivers/net/ewrk3.h b/linux/src/drivers/net/ewrk3.h new file mode 100644 index 00000000..554a18ae --- /dev/null +++ b/linux/src/drivers/net/ewrk3.h @@ -0,0 +1,322 @@ +/* + Written 1994 by David C. Davies. + + Copyright 1994 Digital Equipment Corporation. + + This software may be used and distributed according to the terms of the + GNU Public License, incorporated herein by reference. + + The author may be reached as davies@wanton.lkg.dec.com or Digital + Equipment Corporation, 550 King Street, Littleton MA 01460. + + ========================================================================= +*/ + +/* +** I/O Address Register Map +*/ +#define EWRK3_CSR iobase+0x00 /* Control and Status Register */ +#define EWRK3_CR iobase+0x01 /* Control Register */ +#define EWRK3_ICR iobase+0x02 /* Interrupt Control Register */ +#define EWRK3_TSR iobase+0x03 /* Transmit Status Register */ +#define EWRK3_RSVD1 iobase+0x04 /* RESERVED */ +#define EWRK3_RSVD2 iobase+0x05 /* RESERVED */ +#define EWRK3_FMQ iobase+0x06 /* Free Memory Queue */ +#define EWRK3_FMQC iobase+0x07 /* Free Memory Queue Counter */ +#define EWRK3_RQ iobase+0x08 /* Receive Queue */ +#define EWRK3_RQC iobase+0x09 /* Receive Queue Counter */ +#define EWRK3_TQ iobase+0x0a /* Transmit Queue */ +#define EWRK3_TQC iobase+0x0b /* Transmit Queue Counter */ +#define EWRK3_TDQ iobase+0x0c /* Transmit Done Queue */ +#define EWRK3_TDQC iobase+0x0d /* Transmit Done Queue Counter */ +#define EWRK3_PIR1 iobase+0x0e /* Page Index Register 1 */ +#define EWRK3_PIR2 iobase+0x0f /* Page Index Register 2 */ +#define EWRK3_DATA iobase+0x10 /* Data Register */ +#define EWRK3_IOPR iobase+0x11 /* I/O Page Register */ +#define EWRK3_IOBR iobase+0x12 /* I/O Base Register */ +#define EWRK3_MPR iobase+0x13 /* Memory Page Register */ +#define EWRK3_MBR iobase+0x14 /* Memory Base Register */ +#define EWRK3_APROM iobase+0x15 /* Address PROM */ +#define EWRK3_EPROM1 iobase+0x16 /* EEPROM Data Register 1 */ +#define EWRK3_EPROM2 iobase+0x17 /* EEPROM Data Register 2 */ +#define EWRK3_PAR0 iobase+0x18 /* Physical Address Register 0 */ +#define EWRK3_PAR1 iobase+0x19 /* Physical Address Register 1 */ +#define EWRK3_PAR2 iobase+0x1a /* Physical Address Register 2 */ +#define EWRK3_PAR3 iobase+0x1b /* Physical Address Register 3 */ +#define EWRK3_PAR4 iobase+0x1c /* Physical Address Register 4 */ +#define EWRK3_PAR5 iobase+0x1d /* Physical Address Register 5 */ +#define EWRK3_CMR iobase+0x1e /* Configuration/Management Register */ + +/* +** Control Page Map +*/ +#define PAGE0_FMQ 0x000 /* Free Memory Queue */ +#define PAGE0_RQ 0x080 /* Receive Queue */ +#define PAGE0_TQ 0x100 /* Transmit Queue */ +#define PAGE0_TDQ 0x180 /* Transmit Done Queue */ +#define PAGE0_HTE 0x200 /* Hash Table Entries */ +#define PAGE0_RSVD 0x240 /* RESERVED */ +#define PAGE0_USRD 0x600 /* User Data */ + +/* +** Control and Status Register bit definitions (EWRK3_CSR) +*/ +#define CSR_RA 0x80 /* Runt Accept */ +#define CSR_PME 0x40 /* Promiscuous Mode Enable */ +#define CSR_MCE 0x20 /* Multicast Enable */ +#define CSR_TNE 0x08 /* TX Done Queue Not Empty */ +#define CSR_RNE 0x04 /* RX Queue Not Empty */ +#define CSR_TXD 0x02 /* TX Disable */ +#define CSR_RXD 0x01 /* RX Disable */ + +/* +** Control Register bit definitions (EWRK3_CR) +*/ +#define CR_APD 0x80 /* Auto Port Disable */ +#define CR_PSEL 0x40 /* Port Select (0->TP port) */ +#define CR_LBCK 0x20 /* LoopBaCK enable */ +#define CR_FDUP 0x10 /* Full DUPlex enable */ +#define CR_FBUS 0x08 /* Fast BUS enable (ISA clk > 8.33MHz) */ +#define CR_EN_16 0x04 /* ENable 16 bit memory accesses */ +#define CR_LED 0x02 /* LED (1-> turn on) */ + +/* +** Interrupt Control Register bit definitions (EWRK3_ICR) +*/ +#define ICR_IE 0x80 /* Interrupt Enable */ +#define ICR_IS 0x60 /* Interrupt Selected */ +#define ICR_TNEM 0x08 /* TNE Mask (0->mask) */ +#define ICR_RNEM 0x04 /* RNE Mask (0->mask) */ +#define ICR_TXDM 0x02 /* TXD Mask (0->mask) */ +#define ICR_RXDM 0x01 /* RXD Mask (0->mask) */ + +/* +** Transmit Status Register bit definitions (EWRK3_TSR) +*/ +#define TSR_NCL 0x80 /* No Carrier Loopback */ +#define TSR_ID 0x40 /* Initially Deferred */ +#define TSR_LCL 0x20 /* Late CoLlision */ +#define TSR_ECL 0x10 /* Excessive CoLlisions */ +#define TSR_RCNTR 0x0f /* Retries CouNTeR */ + +/* +** I/O Page Register bit definitions (EWRK3_IOPR) +*/ +#define EEPROM_INIT 0xc0 /* EEPROM INIT command */ +#define EEPROM_WR_EN 0xc8 /* EEPROM WRITE ENABLE command */ +#define EEPROM_WR 0xd0 /* EEPROM WRITE command */ +#define EEPROM_WR_DIS 0xd8 /* EEPROM WRITE DISABLE command */ +#define EEPROM_RD 0xe0 /* EEPROM READ command */ + +/* +** I/O Base Register bit definitions (EWRK3_IOBR) +*/ +#define EISA_REGS_EN 0x20 /* Enable EISA ID and Control Registers */ +#define EISA_IOB 0x1f /* Compare bits for I/O Base Address */ + +/* +** I/O Configuration/Management Register bit definitions (EWRK3_CMR) +*/ +#define CMR_RA 0x80 /* Read Ahead */ +#define CMR_WB 0x40 /* Write Behind */ +#define CMR_LINK 0x20 /* 0->TP */ +#define CMR_POLARITY 0x10 /* Informational */ +#define CMR_NO_EEPROM 0x0c /* NO_EEPROM<1:0> pin status */ +#define CMR_HS 0x08 /* Hard Strapped pin status (LeMAC2) */ +#define CMR_PNP 0x04 /* Plug 'n Play */ +#define CMR_DRAM 0x02 /* 0-> 1DRAM, 1-> 2 DRAM on board */ +#define CMR_0WS 0x01 /* Zero Wait State */ + +/* +** MAC Receive Status Register bit definitions +*/ + +#define R_ROK 0x80 /* Receive OK summary */ +#define R_IAM 0x10 /* Individual Address Match */ +#define R_MCM 0x08 /* MultiCast Match */ +#define R_DBE 0x04 /* Dribble Bit Error */ +#define R_CRC 0x02 /* CRC error */ +#define R_PLL 0x01 /* Phase Lock Lost */ + +/* +** MAC Transmit Control Register bit definitions +*/ + +#define TCR_SQEE 0x40 /* SQE Enable - look for heartbeat */ +#define TCR_SED 0x20 /* Stop when Error Detected */ +#define TCR_QMODE 0x10 /* Q_MODE */ +#define TCR_LAB 0x08 /* Less Aggressive Backoff */ +#define TCR_PAD 0x04 /* PAD Runt Packets */ +#define TCR_IFC 0x02 /* Insert Frame Check */ +#define TCR_ISA 0x01 /* Insert Source Address */ + +/* +** MAC Transmit Status Register bit definitions +*/ + +#define T_VSTS 0x80 /* Valid STatuS */ +#define T_CTU 0x40 /* Cut Through Used */ +#define T_SQE 0x20 /* Signal Quality Error */ +#define T_NCL 0x10 /* No Carrier Loopback */ +#define T_LCL 0x08 /* Late Collision */ +#define T_ID 0x04 /* Initially Deferred */ +#define T_COLL 0x03 /* COLLision status */ +#define T_XCOLL 0x03 /* Excessive Collisions */ +#define T_MCOLL 0x02 /* Multiple Collisions */ +#define T_OCOLL 0x01 /* One Collision */ +#define T_NOCOLL 0x00 /* No Collisions */ +#define T_XUR 0x03 /* Excessive Underruns */ +#define T_TXE 0x7f /* TX Errors */ + +/* +** EISA Configuration Register bit definitions +*/ + +#define EISA_ID iobase + 0x0c80 /* EISA ID Registers */ +#define EISA_ID0 iobase + 0x0c80 /* EISA ID Register 0 */ +#define EISA_ID1 iobase + 0x0c81 /* EISA ID Register 1 */ +#define EISA_ID2 iobase + 0x0c82 /* EISA ID Register 2 */ +#define EISA_ID3 iobase + 0x0c83 /* EISA ID Register 3 */ +#define EISA_CR iobase + 0x0c84 /* EISA Control Register */ + +/* +** EEPROM BYTES +*/ +#define EEPROM_MEMB 0x00 +#define EEPROM_IOB 0x01 +#define EEPROM_EISA_ID0 0x02 +#define EEPROM_EISA_ID1 0x03 +#define EEPROM_EISA_ID2 0x04 +#define EEPROM_EISA_ID3 0x05 +#define EEPROM_MISC0 0x06 +#define EEPROM_MISC1 0x07 +#define EEPROM_PNAME7 0x08 +#define EEPROM_PNAME6 0x09 +#define EEPROM_PNAME5 0x0a +#define EEPROM_PNAME4 0x0b +#define EEPROM_PNAME3 0x0c +#define EEPROM_PNAME2 0x0d +#define EEPROM_PNAME1 0x0e +#define EEPROM_PNAME0 0x0f +#define EEPROM_SWFLAGS 0x10 +#define EEPROM_HWCAT 0x11 +#define EEPROM_NETMAN2 0x12 +#define EEPROM_REVLVL 0x13 +#define EEPROM_NETMAN0 0x14 +#define EEPROM_NETMAN1 0x15 +#define EEPROM_CHIPVER 0x16 +#define EEPROM_SETUP 0x17 +#define EEPROM_PADDR0 0x18 +#define EEPROM_PADDR1 0x19 +#define EEPROM_PADDR2 0x1a +#define EEPROM_PADDR3 0x1b +#define EEPROM_PADDR4 0x1c +#define EEPROM_PADDR5 0x1d +#define EEPROM_PA_CRC 0x1e +#define EEPROM_CHKSUM 0x1f + +/* +** EEPROM bytes for checksumming +*/ +#define EEPROM_MAX 32 /* bytes */ + +/* +** EEPROM MISCELLANEOUS FLAGS +*/ +#define RBE_SHADOW 0x0100 /* Remote Boot Enable Shadow */ +#define READ_AHEAD 0x0080 /* Read Ahead feature */ +#define IRQ_SEL2 0x0070 /* IRQ line selection (LeMAC2) */ +#define IRQ_SEL 0x0060 /* IRQ line selection */ +#define FAST_BUS 0x0008 /* ISA Bus speeds > 8.33MHz */ +#define ENA_16 0x0004 /* Enables 16 bit memory transfers */ +#define WRITE_BEHIND 0x0002 /* Write Behind feature */ +#define _0WS_ENA 0x0001 /* Zero Wait State Enable */ + +/* +** EEPROM NETWORK MANAGEMENT FLAGS +*/ +#define NETMAN_POL 0x04 /* Polarity defeat */ +#define NETMAN_LINK 0x02 /* Link defeat */ +#define NETMAN_CCE 0x01 /* Custom Counters Enable */ + +/* +** EEPROM SW FLAGS +*/ +#define SW_SQE 0x10 /* Signal Quality Error */ +#define SW_LAB 0x08 /* Less Aggressive Backoff */ +#define SW_INIT 0x04 /* Initialized */ +#define SW_TIMEOUT 0x02 /* 0:2.5 mins, 1: 30 secs */ +#define SW_REMOTE 0x01 /* Remote Boot Enable -> 1 */ + +/* +** EEPROM SETUP FLAGS +*/ +#define SETUP_APD 0x80 /* AutoPort Disable */ +#define SETUP_PS 0x40 /* Port Select */ +#define SETUP_MP 0x20 /* MultiPort */ +#define SETUP_1TP 0x10 /* 1 port, TP */ +#define SETUP_1COAX 0x00 /* 1 port, Coax */ +#define SETUP_DRAM 0x02 /* Number of DRAMS on board */ + +/* +** EEPROM MANAGEMENT FLAGS +*/ +#define MGMT_CCE 0x01 /* Custom Counters Enable */ + +/* +** EEPROM VERSIONS +*/ +#define LeMAC 0x11 +#define LeMAC2 0x12 + +/* +** Miscellaneous +*/ + +#define EEPROM_WAIT_TIME 1000 /* Number of microseconds */ +#define EISA_EN 0x0001 /* Enable EISA bus buffers */ + +#define HASH_TABLE_LEN 512 /* Bits */ + +#define XCT 0x80 /* Transmit Cut Through */ +#define PRELOAD 16 /* 4 long words */ + +#define MASK_INTERRUPTS 1 +#define UNMASK_INTERRUPTS 0 + +#define EEPROM_OFFSET(a) ((u_short)((u_long)(a))) + +/* +** Include the IOCTL stuff +*/ +#include <linux/sockios.h> + +#define EWRK3IOCTL SIOCDEVPRIVATE + +struct ewrk3_ioctl { + unsigned short cmd; /* Command to run */ + unsigned short len; /* Length of the data buffer */ + unsigned char *data; /* Pointer to the data buffer */ +}; + +/* +** Recognised commands for the driver +*/ +#define EWRK3_GET_HWADDR 0x01 /* Get the hardware address */ +#define EWRK3_SET_HWADDR 0x02 /* Get the hardware address */ +#define EWRK3_SET_PROM 0x03 /* Set Promiscuous Mode */ +#define EWRK3_CLR_PROM 0x04 /* Clear Promiscuous Mode */ +#define EWRK3_SAY_BOO 0x05 /* Say "Boo!" to the kernel log file */ +#define EWRK3_GET_MCA 0x06 /* Get a multicast address */ +#define EWRK3_SET_MCA 0x07 /* Set a multicast address */ +#define EWRK3_CLR_MCA 0x08 /* Clear a multicast address */ +#define EWRK3_MCA_EN 0x09 /* Enable a multicast address group */ +#define EWRK3_GET_STATS 0x0a /* Get the driver statistics */ +#define EWRK3_CLR_STATS 0x0b /* Zero out the driver statistics */ +#define EWRK3_GET_CSR 0x0c /* Get the CSR Register contents */ +#define EWRK3_SET_CSR 0x0d /* Set the CSR Register contents */ +#define EWRK3_GET_EEPROM 0x0e /* Get the EEPROM contents */ +#define EWRK3_SET_EEPROM 0x0f /* Set the EEPROM contents */ +#define EWRK3_GET_CMR 0x10 /* Get the CMR Register contents */ +#define EWRK3_CLR_TX_CUT_THRU 0x11 /* Clear the TX cut through mode */ +#define EWRK3_SET_TX_CUT_THRU 0x12 /* Set the TX cut through mode */ diff --git a/linux/src/drivers/net/fmv18x.c b/linux/src/drivers/net/fmv18x.c new file mode 100644 index 00000000..121dd0bb --- /dev/null +++ b/linux/src/drivers/net/fmv18x.c @@ -0,0 +1,664 @@ +/* fmv18x.c: A network device driver for the Fujitsu FMV-181/182/183/184. + + Original: at1700.c (1993-94 by Donald Becker). + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Modified by Yutaka TAMIYA (tamy@flab.fujitsu.co.jp) + Copyright 1994 Fujitsu Laboratories Ltd. + Special thanks to: + Masayoshi UTAKA (utaka@ace.yk.fujitsu.co.jp) + for testing this driver. + H. NEGISHI (agy, negishi@sun45.psd.cs.fujitsu.co.jp) + for suggestion of some program modification. + Masahiro SEKIGUCHI <seki@sysrap.cs.fujitsu.co.jp> + for suggestion of some program modification. + Kazutoshi MORIOKA (morioka@aurora.oaks.cs.fujitsu.co.jp) + for testing this driver. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + This is a device driver for the Fujitsu FMV-181/182/183/184, which + is a straight-forward Fujitsu MB86965 implementation. + + Sources: + at1700.c + The Fujitsu MB86965 datasheet. + The Fujitsu FMV-181/182 user's guide +*/ + +static const char *version = + "fmv18x.c:v1.3.71e 03/04/96 Yutaka TAMIYA (tamy@flab.fujitsu.co.jp)\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/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/delay.h> + +static int fmv18x_probe_list[] = +{0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x300, 0x340, 0}; + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 1 +#endif +static unsigned int net_debug = NET_DEBUG; + +typedef unsigned char uchar; + +/* Information that need to be kept for each board. */ +struct net_local { + struct enet_statistics stats; + long open_time; /* Useless example local info. */ + uint tx_started:1; /* Number of packet on the Tx queue. */ + uchar tx_queue; /* Number of packet on the Tx queue. */ + ushort tx_queue_len; /* Current length of the Tx queue. */ +}; + + +/* Offsets from the base address. */ +#define STATUS 0 +#define TX_STATUS 0 +#define RX_STATUS 1 +#define TX_INTR 2 /* Bit-mapped interrupt enable registers. */ +#define RX_INTR 3 +#define TX_MODE 4 +#define RX_MODE 5 +#define CONFIG_0 6 /* Misc. configuration settings. */ +#define CONFIG_1 7 +/* Run-time register bank 2 definitions. */ +#define DATAPORT 8 /* Word-wide DMA or programmed-I/O dataport. */ +#define TX_START 10 +#define COL16CNTL 11 +#define MODE13 13 +/* Fujitsu FMV-18x Card Configuration */ +#define FJ_STATUS0 0x10 +#define FJ_STATUS1 0x11 +#define FJ_CONFIG0 0x12 +#define FJ_CONFIG1 0x13 +#define FJ_MACADDR 0x14 /* 0x14 - 0x19 */ +#define FJ_BUFCNTL 0x1A +#define FJ_BUFDATA 0x1C +#define FMV18X_IO_EXTENT 32 + +/* Index to functions, as function prototypes. */ + +extern int fmv18x_probe(struct device *dev); + +static int fmv18x_probe1(struct device *dev, short ioaddr); +static int net_open(struct device *dev); +static int net_send_packet(struct sk_buff *skb, struct device *dev); +static void net_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void net_rx(struct device *dev); +static int net_close(struct device *dev); +static struct enet_statistics *net_get_stats(struct device *dev); +static void set_multicast_list(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, allocate space for the device and return success + (detachable devices only). + */ +#ifdef HAVE_DEVLIST +/* Support for a alternate probe manager, which will eliminate the + boilerplate below. */ +struct netdev_entry fmv18x_drv = +{"fmv18x", fmv18x_probe1, FMV18X_IO_EXTENT, fmv18x_probe_list}; +#else +int +fmv18x_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return fmv18x_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; fmv18x_probe_list[i]; i++) { + int ioaddr = fmv18x_probe_list[i]; + if (check_region(ioaddr, FMV18X_IO_EXTENT)) + continue; + if (fmv18x_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +/* The Fujitsu datasheet suggests that the NIC be probed for by checking its + "signature", the default bit pattern after a reset. This *doesn't* work -- + there is no way to reset the bus interface without a complete power-cycle! + + It turns out that ATI came to the same conclusion I did: the only thing + that can be done is checking a few bits and then diving right into MAC + address check. */ + +int fmv18x_probe1(struct device *dev, short ioaddr) +{ + char irqmap[4] = {3, 7, 10, 15}; + unsigned int i, irq; + + /* Resetting the chip doesn't reset the ISA interface, so don't bother. + That means we have to be careful with the register values we probe for. + */ + + /* Check I/O address configuration and Fujitsu vendor code */ + if (fmv18x_probe_list[inb(ioaddr + FJ_CONFIG0) & 0x07] != ioaddr + || inb(ioaddr+FJ_MACADDR ) != 0x00 + || inb(ioaddr+FJ_MACADDR+1) != 0x00 + || inb(ioaddr+FJ_MACADDR+2) != 0x0e) + return -ENODEV; + + irq = irqmap[(inb(ioaddr + FJ_CONFIG0)>>6) & 0x03]; + + /* Snarf the interrupt vector now. */ + if (request_irq(irq, &net_interrupt, 0, "fmv18x", NULL)) { + printk ("FMV-18x found at %#3x, but it's unusable due to a conflict on" + "IRQ %d.\n", ioaddr, irq); + return EAGAIN; + } + + /* Allocate a new 'dev' if needed. */ + if (dev == NULL) + dev = init_etherdev(0, sizeof(struct net_local)); + + /* Grab the region so that we can find another board if the IRQ request + fails. */ + request_region(ioaddr, FMV18X_IO_EXTENT, "fmv18x"); + + printk("%s: FMV-18x found at %#3x, IRQ %d, address ", dev->name, + ioaddr, irq); + + dev->base_addr = ioaddr; + dev->irq = irq; + irq2dev_map[irq] = dev; + + for(i = 0; i < 6; i++) { + unsigned char val = inb(ioaddr + FJ_MACADDR + i); + printk("%02x", val); + dev->dev_addr[i] = val; + } + + /* "FJ_STATUS0" 12 bit 0x0400 means use regular 100 ohm 10baseT signals, + rather than 150 ohm shielded twisted pair compensation. + 0x0000 == auto-sense the interface + 0x0800 == use TP interface + 0x1800 == use coax interface + */ + { + const char *porttype[] = {"auto-sense", "10baseT", "auto-sense", "10base2/5"}; + ushort setup_value = inb(ioaddr + FJ_STATUS0); + + switch( setup_value & 0x07 ){ + case 0x01 /* 10base5 */: + case 0x02 /* 10base2 */: dev->if_port = 0x18; break; + case 0x04 /* 10baseT */: dev->if_port = 0x08; break; + default /* auto-sense*/: dev->if_port = 0x00; break; + } + printk(" %s interface.\n", porttype[(dev->if_port>>3) & 3]); + } + + /* Initialize LAN Controller and LAN Card */ + outb(0xda, ioaddr + CONFIG_0); /* Initialize LAN Controller */ + outb(0x00, ioaddr + CONFIG_1); /* Stand by mode */ + outb(0x00, ioaddr + FJ_CONFIG1); /* Disable IRQ of LAN Card */ + outb(0x00, ioaddr + FJ_BUFCNTL); /* Reset ? I'm not sure (TAMIYA) */ + + /* wait for a while */ + udelay(200); + + /* Set the station address in bank zero. */ + outb(0x00, ioaddr + CONFIG_1); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + 8 + i); + + /* Switch to bank 1 and set the multicast table to accept none. */ + outb(0x04, ioaddr + CONFIG_1); + for (i = 0; i < 8; i++) + outb(0x00, ioaddr + 8 + i); + + /* Switch to bank 2 and lock our I/O address. */ + outb(0x08, ioaddr + CONFIG_1); + outb(dev->if_port, ioaddr + MODE13); + + if (net_debug) + printk(version); + + /* 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)); + + dev->open = net_open; + dev->stop = net_close; + dev->hard_start_xmit = net_send_packet; + dev->get_stats = net_get_stats; + dev->set_multicast_list = &set_multicast_list; + + /* Fill in the fields of 'dev' with ethernet-generic values. */ + + ether_setup(dev); + return 0; +} + + +static int net_open(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + + /* Set the configuration register 0 to 32K 100ns. byte-wide memory, + 16 bit bus access, and two 4K Tx, enable the Rx and Tx. */ + outb(0x5a, ioaddr + CONFIG_0); + + /* Powerup and switch to register bank 2 for the run-time registers. */ + outb(0xe8, ioaddr + CONFIG_1); + + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + + /* Clear Tx and Rx Status */ + outb(0xff, ioaddr + TX_STATUS); + outb(0xff, ioaddr + RX_STATUS); + lp->open_time = jiffies; + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* Enable the IRQ of the LAN Card */ + outb(0x80, ioaddr + FJ_CONFIG1); + + /* Enable both Tx and Rx interrupts */ + outw(0x8182, ioaddr+TX_INTR); + + MOD_INC_USE_COUNT; + + return 0; +} + +static int +net_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + + 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 < 10) + return 1; + printk("%s: transmit timed out with status %04x, %s?\n", dev->name, + htons(inw(ioaddr + TX_STATUS)), + inb(ioaddr + TX_STATUS) & 0x80 + ? "IRQ conflict" : "network cable problem"); + printk("%s: timeout registers: %04x %04x %04x %04x %04x %04x %04x %04x.\n", + dev->name, htons(inw(ioaddr + 0)), + htons(inw(ioaddr + 2)), htons(inw(ioaddr + 4)), + htons(inw(ioaddr + 6)), htons(inw(ioaddr + 8)), + htons(inw(ioaddr +10)), htons(inw(ioaddr +12)), + htons(inw(ioaddr +14))); + printk("eth card: %04x %04x\n", + htons(inw(ioaddr+FJ_STATUS0)), + htons(inw(ioaddr+FJ_CONFIG0))); + lp->stats.tx_errors++; + /* ToDo: We should try to restart the adaptor... */ + cli(); + + /* Initialize LAN Controller and LAN Card */ + outb(0xda, ioaddr + CONFIG_0); /* Initialize LAN Controller */ + outb(0x00, ioaddr + CONFIG_1); /* Stand by mode */ + outb(0x00, ioaddr + FJ_CONFIG1); /* Disable IRQ of LAN Card */ + outb(0x00, ioaddr + FJ_BUFCNTL); /* Reset ? I'm not sure */ + net_open(dev); + + sti(); + } + + /* 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) { + dev_tint(dev); + return 0; + } + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + 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; + + if (length > ETH_FRAME_LEN) { + if (net_debug) + printk("%s: Attempting to send a large packet (%d bytes).\n", + dev->name, length); + return 1; + } + + if (net_debug > 4) + printk("%s: Transmitting a packet of length %lu.\n", dev->name, + (unsigned long)skb->len); + + /* Disable both interrupts. */ + outw(0x0000, ioaddr + TX_INTR); + + outw(length, ioaddr + DATAPORT); + outsw(ioaddr + DATAPORT, buf, (length + 1) >> 1); + + lp->tx_queue++; + lp->tx_queue_len += length + 2; + + if (lp->tx_started == 0) { + /* If the Tx is idle, always trigger a transmit. */ + outb(0x80 | lp->tx_queue, ioaddr + TX_START); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + lp->tx_started = 1; + dev->tbusy = 0; + } else if (lp->tx_queue_len < 4096 - 1502) + /* Yes, there is room for one more packet. */ + dev->tbusy = 0; + + /* Re-enable interrupts */ + outw(0x8182, ioaddr + TX_INTR); + } + dev_kfree_skb (skb, FREE_WRITE); + + return 0; +} + +/* The typical workload of the driver: + Handle the network interface interrupts. */ +static void +net_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct net_local *lp; + int ioaddr, status; + + if (dev == NULL) { + printk ("fmv18x_interrupt(): irq %d for unknown device.\n", irq); + return; + } + dev->interrupt = 1; + + ioaddr = dev->base_addr; + lp = (struct net_local *)dev->priv; + + /* Avoid multiple interrupts. */ + outw(0x0000, ioaddr + TX_INTR); + + status = inw(ioaddr + TX_STATUS); + outw(status, ioaddr + TX_STATUS); + + if (net_debug > 4) + printk("%s: Interrupt with status %04x.\n", dev->name, status); + if (status & 0xff00 + || (inb(ioaddr + RX_MODE) & 0x40) == 0) { /* Got a packet(s). */ + net_rx(dev); + } + if (status & 0x00ff) { + if (status & 0x80) { + lp->stats.tx_packets++; + if (lp->tx_queue) { + outb(0x80 | lp->tx_queue, ioaddr + TX_START); + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } else { + lp->tx_started = 0; + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } + } + if (status & 0x02 ) { + if (net_debug > 4) + printk("%s: 16 Collision occur during Txing.\n", dev->name); + /* Retry to send the packet */ + outb(0x02, ioaddr + COL16CNTL); + } + } + + dev->interrupt = 0; + outw(0x8182, ioaddr + TX_INTR); + return; +} + +/* We have a good packet(s), get it/them out of the buffers. */ +static void +net_rx(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + int boguscount = 10; /* 5 -> 10: by agy 19940922 */ + + while ((inb(ioaddr + RX_MODE) & 0x40) == 0) { + /* Clear PKT_RDY bit: by agy 19940922 */ + /* outb(0x80, ioaddr + RX_STATUS); */ + ushort status = inw(ioaddr + DATAPORT); + + if (net_debug > 4) + printk("%s: Rxing packet mode %02x status %04x.\n", + dev->name, inb(ioaddr + RX_MODE), status); +#ifndef final_version + if (status == 0) { + outb(0x05, ioaddr + 14); + break; + } +#endif + + if ((status & 0xF0) != 0x20) { /* There was an error. */ + lp->stats.rx_errors++; + if (status & 0x08) lp->stats.rx_length_errors++; + if (status & 0x04) lp->stats.rx_frame_errors++; + if (status & 0x02) lp->stats.rx_crc_errors++; + if (status & 0x01) lp->stats.rx_over_errors++; + } else { + ushort pkt_len = inw(ioaddr + DATAPORT); + /* Malloc up new buffer. */ + struct sk_buff *skb; + + if (pkt_len > 1550) { + printk("%s: The FMV-18x claimed a very large packet, size %d.\n", + dev->name, pkt_len); + outb(0x05, ioaddr + 14); + lp->stats.rx_errors++; + break; + } + skb = dev_alloc_skb(pkt_len+3); + if (skb == NULL) { + printk("%s: Memory squeeze, dropping packet (len %d).\n", + dev->name, pkt_len); + outb(0x05, ioaddr + 14); + lp->stats.rx_dropped++; + break; + } + skb->dev = dev; + skb_reserve(skb,2); + + insw(ioaddr + DATAPORT, skb_put(skb,pkt_len), (pkt_len + 1) >> 1); + + if (net_debug > 5) { + int i; + printk("%s: Rxed packet of length %d: ", dev->name, pkt_len); + for (i = 0; i < 14; i++) + printk(" %02x", skb->data[i]); + printk(".\n"); + } + + skb->protocol=eth_type_trans(skb, dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + if (--boguscount <= 0) + break; + } + + /* If any worth-while packets have been received, dev_rint() + has done a mark_bh(NET_BH) for us and will work on them + when we get to the bottom-half routine. */ + { + int i; + for (i = 0; i < 20; i++) { + if ((inb(ioaddr + RX_MODE) & 0x40) == 0x40) + break; + (void)inw(ioaddr + DATAPORT); /* dummy status read */ + outb(0x05, ioaddr + 14); + } + + if (net_debug > 5 && i > 0) + printk("%s: Exint Rx packet with mode %02x after %d ticks.\n", + dev->name, inb(ioaddr + RX_MODE), i); + } + + return; +} + +/* The inverse routine to net_open(). */ +static int net_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + + ((struct net_local *)dev->priv)->open_time = 0; + + dev->tbusy = 1; + dev->start = 0; + + /* Set configuration register 0 to disable Tx and Rx. */ + outb(0xda, ioaddr + CONFIG_0); + + /* Update the statistics -- ToDo. */ + + /* Power-down the chip. Green, green, green! */ + outb(0x00, ioaddr + CONFIG_1); + + MOD_DEC_USE_COUNT; + + /* Set the ethernet adaptor disable IRQ */ + outb(0x00, ioaddr + FJ_CONFIG1); + + return 0; +} + +/* Get the current statistics. This may be called with the card open or + closed. */ +static struct enet_statistics * +net_get_stats(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + + cli(); + /* ToDo: Update the statistics from the device registers. */ + sti(); + + return &lp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + num_addrs == -1 Promiscuous mode, receive all packets + num_addrs == 0 Normal mode, clear multicast list + num_addrs > 0 Multicast mode, receive normal and MC packets, and do + best-effort filtering. + */ +static void +set_multicast_list(struct device *dev) +{ + short ioaddr = dev->base_addr; + if (dev->mc_count || dev->flags&(IFF_PROMISC|IFF_ALLMULTI)) + { + /* + * We must make the kernel realise we had to move + * into promisc mode or we start all out war on + * the cable. - AC + */ + dev->flags|=IFF_PROMISC; + + outb(3, ioaddr + RX_MODE); /* Enable promiscuous mode */ + } + else + outb(2, ioaddr + RX_MODE); /* Disable promiscuous, use normal mode */ +} + +#ifdef MODULE +static char devicename[9] = { 0, }; +static struct device dev_fmv18x = { + devicename, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, fmv18x_probe }; + +static int io = 0x220; +static int irq = 0; + +int init_module(void) +{ + if (io == 0) + printk("fmv18x: You should not use auto-probing with insmod!\n"); + dev_fmv18x.base_addr = io; + dev_fmv18x.irq = irq; + if (register_netdev(&dev_fmv18x) != 0) { + printk("fmv18x: register_netdev() returned non-zero.\n"); + return -EIO; + } + return 0; +} + +void +cleanup_module(void) +{ + unregister_netdev(&dev_fmv18x); + kfree(dev_fmv18x.priv); + dev_fmv18x.priv = NULL; + + /* If we don't do this, we can't re-insmod it later. */ + free_irq(dev_fmv18x.irq, NULL); + irq2dev_map[dev_fmv18x.irq] = NULL; + release_region(dev_fmv18x.base_addr, FMV18X_IO_EXTENT); +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c fmv18x.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * c-indent-level: 4 + * End: + */ diff --git a/linux/src/drivers/net/hp-plus.c b/linux/src/drivers/net/hp-plus.c new file mode 100644 index 00000000..c8e36111 --- /dev/null +++ b/linux/src/drivers/net/hp-plus.c @@ -0,0 +1,483 @@ +/* hp-plus.c: A HP PCLAN/plus ethernet driver for linux. */ +/* + Written 1994 by Donald Becker. + + This driver is for the Hewlett Packard PC LAN (27***) plus ethercards. + These cards are sold under several model numbers, usually 2724*. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + As is often the case, a great deal of credit is owed to Russ Nelson. + The Crynwr packet driver was my primary source of HP-specific + programming information. +*/ + +static const char *version = +"hp-plus.c:v1.10 9/24/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +#include <linux/module.h> + +#include <linux/string.h> /* Important -- this inlines word moves. */ +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include <asm/system.h> +#include <asm/io.h> + + +#include "8390.h" + +/* A zero-terminated list of I/O addresses to be probed. */ +static unsigned int hpplus_portlist[] = +{0x200, 0x240, 0x280, 0x2C0, 0x300, 0x320, 0x340, 0}; + +/* + The HP EtherTwist chip implementation is a fairly routine DP8390 + implementation. It allows both shared memory and programmed-I/O buffer + access, using a custom interface for both. The programmed-I/O mode is + entirely implemented in the HP EtherTwist chip, bypassing the problem + ridden built-in 8390 facilities used on NE2000 designs. The shared + memory mode is likewise special, with an offset register used to make + packets appear at the shared memory base. Both modes use a base and bounds + page register to hide the Rx ring buffer wrap -- a packet that spans the + end of physical buffer memory appears continuous to the driver. (c.f. the + 3c503 and Cabletron E2100) + + A special note: the internal buffer of the board is only 8 bits wide. + This lays several nasty traps for the unaware: + - the 8390 must be programmed for byte-wide operations + - all I/O and memory operations must work on whole words (the access + latches are serially preloaded and have no byte-swapping ability). + + This board is laid out in I/O space much like the earlier HP boards: + the first 16 locations are for the board registers, and the second 16 are + for the 8390. The board is easy to identify, with both a dedicated 16 bit + ID register and a constant 0x530* value in the upper bits of the paging + register. +*/ + +#define HP_ID 0x00 /* ID register, always 0x4850. */ +#define HP_PAGING 0x02 /* Registers visible @ 8-f, see PageName. */ +#define HPP_OPTION 0x04 /* Bitmapped options, see HP_Option. */ +#define HPP_OUT_ADDR 0x08 /* I/O output location in Perf_Page. */ +#define HPP_IN_ADDR 0x0A /* I/O input location in Perf_Page. */ +#define HP_DATAPORT 0x0c /* I/O data transfer in Perf_Page. */ +#define NIC_OFFSET 0x10 /* Offset to the 8390 registers. */ +#define HP_IO_EXTENT 32 + +#define HP_START_PG 0x00 /* First page of TX buffer */ +#define HP_STOP_PG 0x80 /* Last page +1 of RX ring */ + +/* The register set selected in HP_PAGING. */ +enum PageName { + Perf_Page = 0, /* Normal operation. */ + MAC_Page = 1, /* The ethernet address (+checksum). */ + HW_Page = 2, /* EEPROM-loaded hardware parameters. */ + LAN_Page = 4, /* Transceiver selection, testing, etc. */ + ID_Page = 6 }; + +/* The bit definitions for the HPP_OPTION register. */ +enum HP_Option { + NICReset = 1, ChipReset = 2, /* Active low, really UNreset. */ + EnableIRQ = 4, FakeIntr = 8, BootROMEnb = 0x10, IOEnb = 0x20, + MemEnable = 0x40, ZeroWait = 0x80, MemDisable = 0x1000, }; + +int hp_plus_probe(struct device *dev); +int hpp_probe1(struct device *dev, int ioaddr); + +static void hpp_reset_8390(struct device *dev); +static int hpp_open(struct device *dev); +static int hpp_close(struct device *dev); +static void hpp_mem_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void hpp_mem_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static void hpp_mem_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void hpp_io_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void hpp_io_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static void hpp_io_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); + + +/* Probe a list of addresses for an HP LAN+ adaptor. + This routine is almost boilerplate. */ +#ifdef HAVE_DEVLIST +/* Support for a alternate probe manager, which will eliminate the + boilerplate below. */ +struct netdev_entry hpplus_drv = +{"hpplus", hpp_probe1, HP_IO_EXTENT, hpplus_portlist}; +#else + +int hp_plus_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return hpp_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; hpplus_portlist[i]; i++) { + int ioaddr = hpplus_portlist[i]; + if (check_region(ioaddr, HP_IO_EXTENT)) + continue; + if (hpp_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +/* Do the interesting part of the probe at a single address. */ +int hpp_probe1(struct device *dev, int ioaddr) +{ + int i; + unsigned char checksum = 0; + const char *name = "HP-PC-LAN+"; + int mem_start; + static unsigned version_printed = 0; + + /* Check for the HP+ signature, 50 48 0x 53. */ + if (inw(ioaddr + HP_ID) != 0x4850 + || (inw(ioaddr + HP_PAGING) & 0xfff0) != 0x5300) + return ENODEV; + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk("hp-plus.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + if (ei_debug && version_printed++ == 0) + printk(version); + + printk("%s: %s at %#3x,", dev->name, name, ioaddr); + + /* Retrieve and checksum the station address. */ + outw(MAC_Page, ioaddr + HP_PAGING); + + for(i = 0; i < ETHER_ADDR_LEN; i++) { + unsigned char inval = inb(ioaddr + 8 + i); + dev->dev_addr[i] = inval; + checksum += inval; + printk(" %2.2x", inval); + } + checksum += inb(ioaddr + 14); + + if (checksum != 0xff) { + printk(" bad checksum %2.2x.\n", checksum); + return ENODEV; + } else { + /* Point at the Software Configuration Flags. */ + outw(ID_Page, ioaddr + HP_PAGING); + printk(" ID %4.4x", inw(ioaddr + 12)); + } + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk ("hp-plus.c: unable to allocate memory for dev->priv.\n"); + return -ENOMEM; + } + + /* Grab the region so we can find another board if something fails. */ + request_region(ioaddr, HP_IO_EXTENT,"hp-plus"); + + /* Read the IRQ line. */ + outw(HW_Page, ioaddr + HP_PAGING); + { + int irq = inb(ioaddr + 13) & 0x0f; + int option = inw(ioaddr + HPP_OPTION); + + dev->irq = irq; + if (option & MemEnable) { + mem_start = inw(ioaddr + 9) << 8; + printk(", IRQ %d, memory address %#x.\n", irq, mem_start); + } else { + mem_start = 0; + printk(", IRQ %d, programmed-I/O mode.\n", irq); + } + } + + /* Set the wrap registers for string I/O reads. */ + outw((HP_START_PG + TX_2X_PAGES) | ((HP_STOP_PG - 1) << 8), ioaddr + 14); + + /* Set the base address to point to the NIC, not the "real" base! */ + dev->base_addr = ioaddr + NIC_OFFSET; + + dev->open = &hpp_open; + dev->stop = &hpp_close; + + ei_status.name = name; + ei_status.word16 = 0; /* Agggghhhhh! Debug time: 2 days! */ + ei_status.tx_start_page = HP_START_PG; + ei_status.rx_start_page = HP_START_PG + TX_2X_PAGES; + ei_status.stop_page = HP_STOP_PG; + + ei_status.reset_8390 = &hpp_reset_8390; + ei_status.block_input = &hpp_io_block_input; + ei_status.block_output = &hpp_io_block_output; + ei_status.get_8390_hdr = &hpp_io_get_8390_hdr; + + /* Check if the memory_enable flag is set in the option register. */ + if (mem_start) { + ei_status.block_input = &hpp_mem_block_input; + ei_status.block_output = &hpp_mem_block_output; + ei_status.get_8390_hdr = &hpp_mem_get_8390_hdr; + dev->mem_start = mem_start; + dev->rmem_start = dev->mem_start + TX_2X_PAGES*256; + dev->mem_end = dev->rmem_end + = dev->mem_start + (HP_STOP_PG - HP_START_PG)*256; + } + + outw(Perf_Page, ioaddr + HP_PAGING); + NS8390_init(dev, 0); + /* Leave the 8390 and HP chip reset. */ + outw(inw(ioaddr + HPP_OPTION) & ~EnableIRQ, ioaddr + HPP_OPTION); + + return 0; +} + +static int +hpp_open(struct device *dev) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + int option_reg; + + if (request_irq(dev->irq, &ei_interrupt, 0, "hp-plus", NULL)) { + return -EAGAIN; + } + + /* Reset the 8390 and HP chip. */ + option_reg = inw(ioaddr + HPP_OPTION); + outw(option_reg & ~(NICReset + ChipReset), ioaddr + HPP_OPTION); + SLOW_DOWN_IO; SLOW_DOWN_IO; + /* Unreset the board and enable interrupts. */ + outw(option_reg | (EnableIRQ + NICReset + ChipReset), ioaddr + HPP_OPTION); + + /* Set the wrap registers for programmed-I/O operation. */ + outw(HW_Page, ioaddr + HP_PAGING); + outw((HP_START_PG + TX_2X_PAGES) | ((HP_STOP_PG - 1) << 8), ioaddr + 14); + + /* Select the operational page. */ + outw(Perf_Page, ioaddr + HP_PAGING); + + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static int +hpp_close(struct device *dev) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = NULL; + ei_close(dev); + outw((option_reg & ~EnableIRQ) | MemDisable | NICReset | ChipReset, + ioaddr + HPP_OPTION); + + MOD_DEC_USE_COUNT; + return 0; +} + +static void +hpp_reset_8390(struct device *dev) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + + if (ei_debug > 1) printk("resetting the 8390 time=%ld...", jiffies); + + outw(option_reg & ~(NICReset + ChipReset), ioaddr + HPP_OPTION); + /* Pause a few cycles for the hardware reset to take place. */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + ei_status.txing = 0; + outw(option_reg | (EnableIRQ + NICReset + ChipReset), ioaddr + HPP_OPTION); + + SLOW_DOWN_IO; SLOW_DOWN_IO; + + + if ((inb_p(ioaddr+NIC_OFFSET+EN0_ISR) & ENISR_RESET) == 0) + printk("%s: hp_reset_8390() did not complete.\n", dev->name); + + if (ei_debug > 1) printk("8390 reset done (%ld).", jiffies); + return; +} + +/* The programmed-I/O version of reading the 4 byte 8390 specific header. + Note that transfer with the EtherTwist+ must be on word boundaries. */ + +static void +hpp_io_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + + outw((ring_page<<8), ioaddr + HPP_IN_ADDR); + insw(ioaddr + HP_DATAPORT, hdr, sizeof(struct e8390_pkt_hdr)>>1); +} + +/* Block input and output, similar to the Crynwr packet driver. */ + +static void +hpp_io_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + char *buf = skb->data; + + outw(ring_offset, ioaddr + HPP_IN_ADDR); + insw(ioaddr + HP_DATAPORT, buf, count>>1); + if (count & 0x01) + buf[count-1] = inw(ioaddr + HP_DATAPORT); +} + +/* The corresponding shared memory versions of the above 2 functions. */ + +static void +hpp_mem_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + + outw((ring_page<<8), ioaddr + HPP_IN_ADDR); + outw(option_reg & ~(MemDisable + BootROMEnb), ioaddr + HPP_OPTION); + memcpy_fromio(hdr, dev->mem_start, sizeof(struct e8390_pkt_hdr)); + outw(option_reg, ioaddr + HPP_OPTION); + hdr->count = (hdr->count + 3) & ~3; /* Round up allocation. */ +} + +static void +hpp_mem_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + + outw(ring_offset, ioaddr + HPP_IN_ADDR); + + outw(option_reg & ~(MemDisable + BootROMEnb), ioaddr + HPP_OPTION); + + /* Caution: this relies on get_8390_hdr() rounding up count! + Also note that we *can't* use eth_io_copy_and_sum() because + it will not always copy "count" bytes (e.g. padded IP). */ + + memcpy_fromio(skb->data, dev->mem_start, count); + outw(option_reg, ioaddr + HPP_OPTION); +} + +/* A special note: we *must* always transfer >=16 bit words. + It's always safe to round up, so we do. */ +static void +hpp_io_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + outw(start_page << 8, ioaddr + HPP_OUT_ADDR); + outsl(ioaddr + HP_DATAPORT, buf, (count+3)>>2); + return; +} + +static void +hpp_mem_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page) +{ + int ioaddr = dev->base_addr - NIC_OFFSET; + int option_reg = inw(ioaddr + HPP_OPTION); + + outw(start_page << 8, ioaddr + HPP_OUT_ADDR); + outw(option_reg & ~(MemDisable + BootROMEnb), ioaddr + HPP_OPTION); + memcpy_toio(dev->mem_start, buf, (count + 3) & ~3); + outw(option_reg, ioaddr + HPP_OPTION); + + return; +} + + +#ifdef MODULE +#define MAX_HPP_CARDS 4 /* Max number of HPP cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_HPP_CARDS] = { 0, }; +static struct device dev_hpp[MAX_HPP_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_HPP_CARDS] = { 0, }; +static int irq[MAX_HPP_CARDS] = { 0, }; + +/* This is set up so that only a single autoprobe takes place per call. +ISA device autoprobes on a running machine are not recommended. */ +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_HPP_CARDS; this_dev++) { + struct device *dev = &dev_hpp[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->init = hp_plus_probe; + if (io[this_dev] == 0) { + if (this_dev != 0) break; /* only autoprobe 1st one */ + printk(KERN_NOTICE "hp-plus.c: Presently autoprobing (not recommended) for a single card.\n"); + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "hp-plus.c: No HP-Plus card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_HPP_CARDS; this_dev++) { + struct device *dev = &dev_hpp[this_dev]; + if (dev->priv != NULL) { + /* NB: hpp_close() handles free_irq + irq2dev map */ + int ioaddr = dev->base_addr - NIC_OFFSET; + kfree(dev->priv); + dev->priv = NULL; + release_region(ioaddr, HP_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c hp-plus.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * c-indent-level: 4 + * End: + */ diff --git a/linux/src/drivers/net/hp.c b/linux/src/drivers/net/hp.c new file mode 100644 index 00000000..741924f9 --- /dev/null +++ b/linux/src/drivers/net/hp.c @@ -0,0 +1,451 @@ +/* hp.c: A HP LAN ethernet driver for linux. */ +/* + Written 1993-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This is a driver for the HP PC-LAN adaptors. + + Sources: + The Crynwr packet driver. +*/ + +static const char *version = + "hp.c:v1.10 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include <asm/system.h> +#include <asm/io.h> + +#include "8390.h" + +/* A zero-terminated list of I/O addresses to be probed. */ +static unsigned int hppclan_portlist[] = +{ 0x300, 0x320, 0x340, 0x280, 0x2C0, 0x200, 0x240, 0}; + +#define HP_IO_EXTENT 32 + +#define HP_DATAPORT 0x0c /* "Remote DMA" data port. */ +#define HP_ID 0x07 +#define HP_CONFIGURE 0x08 /* Configuration register. */ +#define HP_RUN 0x01 /* 1 == Run, 0 == reset. */ +#define HP_IRQ 0x0E /* Mask for software-configured IRQ line. */ +#define HP_DATAON 0x10 /* Turn on dataport */ +#define NIC_OFFSET 0x10 /* Offset the 8390 registers. */ + +#define HP_START_PG 0x00 /* First page of TX buffer */ +#define HP_8BSTOP_PG 0x80 /* Last page +1 of RX ring */ +#define HP_16BSTOP_PG 0xFF /* Same, for 16 bit cards. */ + +int hp_probe(struct device *dev); +int hp_probe1(struct device *dev, int ioaddr); + +static int hp_open(struct device *dev); +static int hp_close(struct device *dev); +static void hp_reset_8390(struct device *dev); +static void hp_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void hp_block_input(struct device *dev, int count, + struct sk_buff *skb , int ring_offset); +static void hp_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); + +static void hp_init_card(struct device *dev); + +/* The map from IRQ number to HP_CONFIGURE register setting. */ +/* My default is IRQ5 0 1 2 3 4 5 6 7 8 9 10 11 */ +static char irqmap[16] = { 0, 0, 4, 6, 8,10, 0,14, 0, 4, 2,12,0,0,0,0}; + + +/* Probe for an HP LAN adaptor. + Also initialize the card and fill in STATION_ADDR with the station + address. */ +#ifdef HAVE_DEVLIST +struct netdev_entry netcard_drv = +{"hp", hp_probe1, HP_IO_EXTENT, hppclan_portlist}; +#else + +int hp_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return hp_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; hppclan_portlist[i]; i++) { + int ioaddr = hppclan_portlist[i]; + if (check_region(ioaddr, HP_IO_EXTENT)) + continue; + if (hp_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +int hp_probe1(struct device *dev, int ioaddr) +{ + int i, board_id, wordmode; + const char *name; + static unsigned version_printed = 0; + + /* Check for the HP physical address, 08 00 09 xx xx xx. */ + /* This really isn't good enough: we may pick up HP LANCE boards + also! Avoid the lance 0x5757 signature. */ + if (inb(ioaddr) != 0x08 + || inb(ioaddr+1) != 0x00 + || inb(ioaddr+2) != 0x09 + || inb(ioaddr+14) == 0x57) + return ENODEV; + + /* Set up the parameters based on the board ID. + If you have additional mappings, please mail them to me -djb. */ + if ((board_id = inb(ioaddr + HP_ID)) & 0x80) { + name = "HP27247"; + wordmode = 1; + } else { + name = "HP27250"; + wordmode = 0; + } + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk("hp.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + if (ei_debug && version_printed++ == 0) + printk(version); + + printk("%s: %s (ID %02x) at %#3x,", dev->name, name, board_id, ioaddr); + + for(i = 0; i < ETHER_ADDR_LEN; i++) + printk(" %2.2x", dev->dev_addr[i] = inb(ioaddr + i)); + + /* Snarf the interrupt now. Someday this could be moved to open(). */ + if (dev->irq < 2) { + int irq_16list[] = { 11, 10, 5, 3, 4, 7, 9, 0}; + int irq_8list[] = { 7, 5, 3, 4, 9, 0}; + int *irqp = wordmode ? irq_16list : irq_8list; + do { + int irq = *irqp; + if (request_irq (irq, NULL, 0, "bogus", NULL) != -EBUSY) { + autoirq_setup(0); + /* Twinkle the interrupt, and check if it's seen. */ + outb_p(irqmap[irq] | HP_RUN, ioaddr + HP_CONFIGURE); + outb_p( 0x00 | HP_RUN, ioaddr + HP_CONFIGURE); + if (irq == autoirq_report(0) /* It's a good IRQ line! */ + && request_irq (irq, &ei_interrupt, 0, "hp", NULL) == 0) { + printk(" selecting IRQ %d.\n", irq); + dev->irq = *irqp; + break; + } + } + } while (*++irqp); + if (*irqp == 0) { + printk(" no free IRQ lines.\n"); + return EBUSY; + } + } else { + if (dev->irq == 2) + dev->irq = 9; + if (request_irq(dev->irq, ei_interrupt, 0, "hp", NULL)) { + printk (" unable to get IRQ %d.\n", dev->irq); + return EBUSY; + } + } + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk (" unable to get memory for dev->priv.\n"); + free_irq(dev->irq, NULL); + return -ENOMEM; + } + + /* Grab the region so we can find another board if something fails. */ + request_region(ioaddr, HP_IO_EXTENT,"hp"); + + /* Set the base address to point to the NIC, not the "real" base! */ + dev->base_addr = ioaddr + NIC_OFFSET; + dev->open = &hp_open; + dev->stop = &hp_close; + + ei_status.name = name; + ei_status.word16 = wordmode; + ei_status.tx_start_page = HP_START_PG; + ei_status.rx_start_page = HP_START_PG + TX_PAGES; + ei_status.stop_page = wordmode ? HP_16BSTOP_PG : HP_8BSTOP_PG; + + ei_status.reset_8390 = &hp_reset_8390; + ei_status.get_8390_hdr = &hp_get_8390_hdr; + ei_status.block_input = &hp_block_input; + ei_status.block_output = &hp_block_output; + hp_init_card(dev); + + return 0; +} + +static int +hp_open(struct device *dev) +{ + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static int +hp_close(struct device *dev) +{ + ei_close(dev); + MOD_DEC_USE_COUNT; + return 0; +} + +static void +hp_reset_8390(struct device *dev) +{ + int hp_base = dev->base_addr - NIC_OFFSET; + int saved_config = inb_p(hp_base + HP_CONFIGURE); + + if (ei_debug > 1) printk("resetting the 8390 time=%ld...", jiffies); + outb_p(0x00, hp_base + HP_CONFIGURE); + ei_status.txing = 0; + /* Pause just a few cycles for the hardware reset to take place. */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + + outb_p(saved_config, hp_base + HP_CONFIGURE); + SLOW_DOWN_IO; SLOW_DOWN_IO; + + if ((inb_p(hp_base+NIC_OFFSET+EN0_ISR) & ENISR_RESET) == 0) + printk("%s: hp_reset_8390() did not complete.\n", dev->name); + + if (ei_debug > 1) printk("8390 reset done (%ld).", jiffies); + return; +} + +static void +hp_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + int nic_base = dev->base_addr; + int saved_config = inb_p(nic_base - NIC_OFFSET + HP_CONFIGURE); + + outb_p(saved_config | HP_DATAON, nic_base - NIC_OFFSET + HP_CONFIGURE); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base); + outb_p(sizeof(struct e8390_pkt_hdr), nic_base + EN0_RCNTLO); + outb_p(0, nic_base + EN0_RCNTHI); + outb_p(0, nic_base + EN0_RSARLO); /* On page boundary */ + outb_p(ring_page, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base); + + if (ei_status.word16) + insw(nic_base - NIC_OFFSET + HP_DATAPORT, hdr, sizeof(struct e8390_pkt_hdr)>>1); + else + insb(nic_base - NIC_OFFSET + HP_DATAPORT, hdr, sizeof(struct e8390_pkt_hdr)); + + outb_p(saved_config & (~HP_DATAON), nic_base - NIC_OFFSET + HP_CONFIGURE); +} + +/* Block input and output, similar to the Crynwr packet driver. If you are + porting to a new ethercard look at the packet driver source for hints. + The HP LAN doesn't use shared memory -- we put the packet + out through the "remote DMA" dataport. */ + +static void +hp_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + int nic_base = dev->base_addr; + int saved_config = inb_p(nic_base - NIC_OFFSET + HP_CONFIGURE); + int xfer_count = count; + char *buf = skb->data; + + outb_p(saved_config | HP_DATAON, nic_base - NIC_OFFSET + HP_CONFIGURE); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base); + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(ring_offset & 0xff, nic_base + EN0_RSARLO); + outb_p(ring_offset >> 8, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base); + if (ei_status.word16) { + insw(nic_base - NIC_OFFSET + HP_DATAPORT,buf,count>>1); + if (count & 0x01) + buf[count-1] = inb(nic_base - NIC_OFFSET + HP_DATAPORT), xfer_count++; + } else { + insb(nic_base - NIC_OFFSET + HP_DATAPORT, buf, count); + } + /* This is for the ALPHA version only, remove for later releases. */ + if (ei_debug > 0) { /* DMA termination address check... */ + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + int addr = (high << 8) + low; + /* Check only the lower 8 bits so we can ignore ring wrap. */ + if (((ring_offset + xfer_count) & 0xff) != (addr & 0xff)) + printk("%s: RX transfer address mismatch, %#4.4x vs. %#4.4x (actual).\n", + dev->name, ring_offset + xfer_count, addr); + } + outb_p(saved_config & (~HP_DATAON), nic_base - NIC_OFFSET + HP_CONFIGURE); +} + +static void +hp_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page) +{ + int nic_base = dev->base_addr; + int saved_config = inb_p(nic_base - NIC_OFFSET + HP_CONFIGURE); + + outb_p(saved_config | HP_DATAON, nic_base - NIC_OFFSET + HP_CONFIGURE); + /* Round the count up for word writes. Do we need to do this? + What effect will an odd byte count have on the 8390? + I should check someday. */ + if (ei_status.word16 && (count & 0x01)) + count++; + /* We should already be in page 0, but to be safe... */ + outb_p(E8390_PAGE0+E8390_START+E8390_NODMA, nic_base); + +#ifdef NE8390_RW_BUGFIX + /* Handle the read-before-write bug the same way as the + Crynwr packet driver -- the NatSemi method doesn't work. */ + outb_p(0x42, nic_base + EN0_RCNTLO); + outb_p(0, nic_base + EN0_RCNTHI); + outb_p(0xff, nic_base + EN0_RSARLO); + outb_p(0x00, nic_base + EN0_RSARHI); +#define NE_CMD 0x00 + outb_p(E8390_RREAD+E8390_START, nic_base + NE_CMD); + /* Make certain that the dummy read has occurred. */ + inb_p(0x61); + inb_p(0x61); +#endif + + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(0x00, nic_base + EN0_RSARLO); + outb_p(start_page, nic_base + EN0_RSARHI); + + outb_p(E8390_RWRITE+E8390_START, nic_base); + if (ei_status.word16) { + /* Use the 'rep' sequence for 16 bit boards. */ + outsw(nic_base - NIC_OFFSET + HP_DATAPORT, buf, count>>1); + } else { + outsb(nic_base - NIC_OFFSET + HP_DATAPORT, buf, count); + } + + /* DON'T check for 'inb_p(EN0_ISR) & ENISR_RDC' here -- it's broken! */ + + /* This is for the ALPHA version only, remove for later releases. */ + if (ei_debug > 0) { /* DMA termination address check... */ + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + int addr = (high << 8) + low; + if ((start_page << 8) + count != addr) + printk("%s: TX Transfer address mismatch, %#4.4x vs. %#4.4x.\n", + dev->name, (start_page << 8) + count, addr); + } + outb_p(saved_config & (~HP_DATAON), nic_base - NIC_OFFSET + HP_CONFIGURE); + return; +} + +/* This function resets the ethercard if something screws up. */ +static void +hp_init_card(struct device *dev) +{ + int irq = dev->irq; + NS8390_init(dev, 0); + outb_p(irqmap[irq&0x0f] | HP_RUN, + dev->base_addr - NIC_OFFSET + HP_CONFIGURE); + return; +} + +#ifdef MODULE +#define MAX_HP_CARDS 4 /* Max number of HP cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_HP_CARDS] = { 0, }; +static struct device dev_hp[MAX_HP_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_HP_CARDS] = { 0, }; +static int irq[MAX_HP_CARDS] = { 0, }; + +/* This is set up so that only a single autoprobe takes place per call. +ISA device autoprobes on a running machine are not recommended. */ +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_HP_CARDS; this_dev++) { + struct device *dev = &dev_hp[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->init = hp_probe; + if (io[this_dev] == 0) { + if (this_dev != 0) break; /* only autoprobe 1st one */ + printk(KERN_NOTICE "hp.c: Presently autoprobing (not recommended) for a single card.\n"); + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "hp.c: No HP card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_HP_CARDS; this_dev++) { + struct device *dev = &dev_hp[this_dev]; + if (dev->priv != NULL) { + int ioaddr = dev->base_addr - NIC_OFFSET; + kfree(dev->priv); + dev->priv = NULL; + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = NULL; + release_region(ioaddr, HP_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c hp.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * c-indent-level: 4 + * End: + */ diff --git a/linux/src/drivers/net/hp100.c b/linux/src/drivers/net/hp100.c new file mode 100644 index 00000000..2e4f8049 --- /dev/null +++ b/linux/src/drivers/net/hp100.c @@ -0,0 +1,3122 @@ +/* +** hp100.c +** HP CASCADE Architecture Driver for 100VG-AnyLan Network Adapters +** +** $Id: hp100.c,v 1.1 1999/04/26 05:52:18 tb Exp $ +** +** Based on the HP100 driver written by Jaroslav Kysela <perex@jcu.cz> +** Extended for new busmaster capable chipsets by +** Siegfried "Frieder" Loeffler (dg1sek) <floeff@mathematik.uni-stuttgart.de> +** +** Maintained by: Jaroslav Kysela <perex@jcu.cz> +** +** This driver has only been tested with +** -- HP J2585B 10/100 Mbit/s PCI Busmaster +** -- HP J2585A 10/100 Mbit/s PCI +** -- HP J2970 10 Mbit/s PCI Combo 10base-T/BNC +** -- HP J2973 10 Mbit/s PCI 10base-T +** -- HP J2573 10/100 ISA +** -- Compex ReadyLink ENET100-VG4 10/100 Mbit/s PCI / EISA +** -- Compex FreedomLine 100/VG 10/100 Mbit/s ISA / EISA / PCI +** +** but it should also work with the other CASCADE based adapters. +** +** TODO: +** - J2573 seems to hang sometimes when in shared memory mode. +** - Mode for Priority TX +** - Check PCI registers, performance might be improved? +** - To reduce interrupt load in busmaster, one could switch off +** the interrupts that are used to refill the queues whenever the +** queues are filled up to more than a certain threshold. +** - some updates for EISA version of card +** +** +** This source/code is public free; you can distribute it and/or modify +** it under terms of the GNU General Public License (published by the +** Free Software Foundation) either version two of this License, or any +** later version. +** +** 1.55 -> 1.56 +** - removed printk in misc. interrupt and update statistics to allow +** monitoring of card status +** - timing changes in xmit routines, relogin to 100VG hub added when +** driver does reset +** - included fix for Compex FreedomLine PCI adapter +** +** 1.54 -> 1.55 +** - fixed bad initialization in init_module +** - added Compex FreedomLine adapter +** - some fixes in card initialization +** +** 1.53 -> 1.54 +** - added hardware multicast filter support (doesn't work) +** - little changes in hp100_sense_lan routine +** - added support for Coax and AUI (J2970) +** - fix for multiple cards and hp100_mode parameter (insmod) +** - fix for shared IRQ +** +** 1.52 -> 1.53 +** - fixed bug in multicast support +** +*/ + +#define HP100_DEFAULT_PRIORITY_TX 0 + +#undef HP100_DEBUG +#undef HP100_DEBUG_B /* Trace */ +#undef HP100_DEBUG_BM /* Debug busmaster code (PDL stuff) */ + +#undef HP100_DEBUG_TRAINING /* Debug login-to-hub procedure */ +#undef HP100_DEBUG_TX +#undef HP100_DEBUG_IRQ +#undef HP100_DEBUG_RX + +#undef HP100_MULTICAST_FILTER /* Need to be debugged... */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/bitops.h> +#include <asm/io.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <linux/types.h> +#include <linux/config.h> /* for CONFIG_PCI */ +#include <linux/delay.h> + +#if LINUX_VERSION_CODE < 0x020100 +#define ioremap vremap +#define iounmap vfree +typedef struct enet_statistics hp100_stats_t; +#else +#define LINUX_2_1 +typedef struct net_device_stats hp100_stats_t; +#endif + +#ifndef __initfunc +#define __initfunc(__initarg) __initarg +#else +#include <linux/init.h> +#endif + +#include "hp100.h" + +/* + * defines + */ + +#define HP100_BUS_ISA 0 +#define HP100_BUS_EISA 1 +#define HP100_BUS_PCI 2 + +#ifndef PCI_DEVICE_ID_HP_J2585B +#define PCI_DEVICE_ID_HP_J2585B 0x1031 +#endif +#ifndef PCI_VENDOR_ID_COMPEX +#define PCI_VENDOR_ID_COMPEX 0x11f6 +#endif +#ifndef PCI_DEVICE_ID_COMPEX_ENET100VG4 +#define PCI_DEVICE_ID_COMPEX_ENET100VG4 0x0112 +#endif +#ifndef PCI_VENDOR_ID_COMPEX2 +#define PCI_VENDOR_ID_COMPEX2 0x101a +#endif +#ifndef PCI_DEVICE_ID_COMPEX2_100VG +#define PCI_DEVICE_ID_COMPEX2_100VG 0x0005 +#endif + +#define HP100_REGION_SIZE 0x20 /* for ioports */ + +#define HP100_MAX_PACKET_SIZE (1536+4) +#define HP100_MIN_PACKET_SIZE 60 + +#ifndef HP100_DEFAULT_RX_RATIO +/* default - 75% onboard memory on the card are used for RX packets */ +#define HP100_DEFAULT_RX_RATIO 75 +#endif + +#ifndef HP100_DEFAULT_PRIORITY_TX +/* default - don't enable transmit outgoing packets as priority */ +#define HP100_DEFAULT_PRIORITY_TX 0 +#endif + +/* + * structures + */ + +struct hp100_eisa_id { + u_int id; + const char *name; + u_char bus; +}; + +struct hp100_pci_id { + u_short vendor; + u_short device; +}; + +struct hp100_private { + struct hp100_eisa_id *id; + u_short chip; + u_short soft_model; + u_int memory_size; + u_int virt_memory_size; + u_short rx_ratio; /* 1 - 99 */ + u_short priority_tx; /* != 0 - priority tx */ + u_short mode; /* PIO, Shared Mem or Busmaster */ + u_char bus; + u_char pci_bus; + u_char pci_device_fn; + short mem_mapped; /* memory mapped access */ + u_int *mem_ptr_virt; /* virtual memory mapped area, maybe NULL */ + u_int *mem_ptr_phys; /* physical memory mapped area */ + short lan_type; /* 10Mb/s, 100Mb/s or -1 (error) */ + int hub_status; /* was login to hub successful? */ + u_char mac1_mode; + u_char mac2_mode; + u_char hash_bytes[ 8 ]; + hp100_stats_t stats; + + /* Rings for busmaster mode: */ + hp100_ring_t *rxrhead; /* Head (oldest) index into rxring */ + hp100_ring_t *rxrtail; /* Tail (newest) index into rxring */ + hp100_ring_t *txrhead; /* Head (oldest) index into txring */ + hp100_ring_t *txrtail; /* Tail (newest) index into txring */ + + hp100_ring_t rxring[ MAX_RX_PDL ]; + hp100_ring_t txring[ MAX_TX_PDL ]; + + u_int *page_vaddr; /* Virtual address of allocated page */ + u_int *page_vaddr_algn; /* Aligned virtual address of allocated page */ + int rxrcommit; /* # Rx PDLs commited to adapter */ + int txrcommit; /* # Tx PDLs commited to adapter */ +}; + +/* + * variables + */ + +static struct hp100_eisa_id hp100_eisa_ids[] = { + + /* 10/100 EISA card with revision A Cascade chip */ + { 0x80F1F022, "HP J2577 rev A", HP100_BUS_EISA }, + + /* 10/100 ISA card with revision A Cascade chip */ + { 0x50F1F022, "HP J2573 rev A", HP100_BUS_ISA }, + + /* 10 only EISA card with Cascade chip */ + { 0x2019F022, "HP 27248B", HP100_BUS_EISA }, + + /* 10/100 EISA card with Cascade chip */ + { 0x4019F022, "HP J2577", HP100_BUS_EISA }, + + /* 10/100 ISA card with Cascade chip */ + { 0x5019F022, "HP J2573", HP100_BUS_ISA }, + + /* 10/100 PCI card - old J2585A */ + { 0x1030103c, "HP J2585A", HP100_BUS_PCI }, + + /* 10/100 PCI card - new J2585B - master capable */ + { 0x1041103c, "HP J2585B", HP100_BUS_PCI }, + + /* 10 Mbit Combo Adapter */ + { 0x1042103c, "HP J2970", HP100_BUS_PCI }, + + /* 10 Mbit 10baseT Adapter */ + { 0x1040103c, "HP J2973", HP100_BUS_PCI }, + + /* 10/100 EISA card from Compex */ + { 0x0103180e, "ReadyLink ENET100-VG4", HP100_BUS_EISA }, + + /* 10/100 EISA card from Compex - FreedomLine (sq5bpf) */ + /* Note: plhbrod@mbox.vol.cz reported that same ID have ISA */ + /* version of adapter, too... */ + { 0x0104180e, "FreedomLine 100/VG", HP100_BUS_EISA }, + + /* 10/100 PCI card from Compex - FreedomLine + * + * I think this card doesn't like aic7178 scsi controller, but + * I haven't tested this much. It works fine on diskless machines. + * Jacek Lipkowski <sq5bpf@acid.ch.pw.edu.pl> + */ + { 0x021211f6, "FreedomLine 100/VG", HP100_BUS_PCI }, + + /* 10/100 PCI card from Compex (J2585A compatible) */ + { 0x011211f6, "ReadyLink ENET100-VG4", HP100_BUS_PCI } + +}; + +#define HP100_EISA_IDS_SIZE (sizeof(hp100_eisa_ids)/sizeof(struct hp100_eisa_id)) + +static struct hp100_pci_id hp100_pci_ids[] = { + { PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_J2585A }, + { PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_J2585B }, + { PCI_VENDOR_ID_COMPEX, PCI_DEVICE_ID_COMPEX_ENET100VG4 }, + { PCI_VENDOR_ID_COMPEX2, PCI_DEVICE_ID_COMPEX2_100VG } +}; + +#define HP100_PCI_IDS_SIZE (sizeof(hp100_pci_ids)/sizeof(struct hp100_pci_id)) + +static int hp100_rx_ratio = HP100_DEFAULT_RX_RATIO; +static int hp100_priority_tx = HP100_DEFAULT_PRIORITY_TX; +static int hp100_mode = 1; + +#ifdef LINUX_2_1 +MODULE_PARM( hp100_rx_ratio, "1i" ); +MODULE_PARM( hp100_priority_tx, "1i" ); +MODULE_PARM( hp100_mode, "1i" ); +#endif + +/* + * prototypes + */ + +static int hp100_probe1( struct device *dev, int ioaddr, u_char bus, u_char pci_bus, u_char pci_device_fn ); +static int hp100_open( struct device *dev ); +static int hp100_close( struct device *dev ); +static int hp100_start_xmit( struct sk_buff *skb, struct device *dev ); +static int hp100_start_xmit_bm (struct sk_buff *skb, struct device *dev ); +static void hp100_rx( struct device *dev ); +static hp100_stats_t *hp100_get_stats( struct device *dev ); +static void hp100_misc_interrupt( struct device *dev ); +static void hp100_update_stats( struct device *dev ); +static void hp100_clear_stats( int ioaddr ); +static void hp100_set_multicast_list( struct device *dev); +static void hp100_interrupt( int irq, void *dev_id, struct pt_regs *regs ); +static void hp100_start_interface( struct device *dev ); +static void hp100_stop_interface( struct device *dev ); +static void hp100_load_eeprom( struct device *dev, u_short ioaddr ); +static int hp100_sense_lan( struct device *dev ); +static int hp100_login_to_vg_hub( struct device *dev, u_short force_relogin ); +static int hp100_down_vg_link( struct device *dev ); +static void hp100_cascade_reset( struct device *dev, u_short enable ); +static void hp100_BM_shutdown( struct device *dev ); +static void hp100_mmuinit( struct device *dev ); +static void hp100_init_pdls( struct device *dev ); +static int hp100_init_rxpdl( struct device *dev, register hp100_ring_t *ringptr, register u_int *pdlptr); +static int hp100_init_txpdl( struct device *dev, register hp100_ring_t *ringptr, register u_int *pdlptr); +static void hp100_rxfill( struct device *dev ); +static void hp100_hwinit( struct device *dev ); +static void hp100_clean_txring( struct device *dev ); +#ifdef HP100_DEBUG +static void hp100_RegisterDump( struct device *dev ); +#endif + +/* TODO: This function should not really be needed in a good design... */ +static void wait( void ) +{ + udelay( 1000 ); +} + +/* + * probe functions + * These functions should - if possible - avoid doing write operations + * since this could cause problems when the card is not installed. + */ + +__initfunc(int hp100_probe( struct device *dev )) +{ + int base_addr = dev ? dev -> base_addr : 0; + int ioaddr = 0; +#ifdef CONFIG_PCI + int pci_start_index = 0; +#endif + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4200, TRACE ); + printk( "hp100: %s: probe\n", dev->name ); +#endif + + if ( base_addr > 0xff ) /* Check a single specified location. */ + { + if ( check_region( base_addr, HP100_REGION_SIZE ) ) return -EINVAL; + if ( base_addr < 0x400 ) + return hp100_probe1( dev, base_addr, HP100_BUS_ISA, 0, 0 ); + if ( EISA_bus && base_addr >= 0x1c38 && ( (base_addr - 0x1c38) & 0x3ff ) == 0 ) + return hp100_probe1( dev, base_addr, HP100_BUS_EISA, 0, 0 ); +#ifdef CONFIG_PCI + printk( "hp100: %s: You may specify card # in i/o address parameter for PCI bus...", dev->name ); + return hp100_probe1( dev, base_addr, HP100_BUS_PCI, 0, 0 ); +#else + return -ENODEV; +#endif + } + else +#ifdef CONFIG_PCI + if ( base_addr > 0 && base_addr < 8 + 1 ) + pci_start_index = 0x100 | ( base_addr - 1 ); + else +#endif + if ( base_addr != 0 ) return -ENXIO; + + /* at first - scan PCI bus(es) */ + +#ifdef CONFIG_PCI + if ( pcibios_present() ) + { + int pci_index; + +#ifdef HP100_DEBUG_PCI + printk( "hp100: %s: PCI BIOS is present, checking for devices..\n", dev->name ); +#endif + for ( pci_index = pci_start_index & 7; pci_index < 8; pci_index++ ) + { + u_char pci_bus, pci_device_fn; + u_short pci_command; + int pci_id_index; + + for ( pci_id_index = 0; pci_id_index < HP100_PCI_IDS_SIZE; pci_id_index++ ) + if ( pcibios_find_device( hp100_pci_ids[ pci_id_index ].vendor, + hp100_pci_ids[ pci_id_index ].device, + pci_index, &pci_bus, + &pci_device_fn ) == 0 ) goto __pci_found; + break; + + __pci_found: + pcibios_read_config_dword( pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &ioaddr ); + + ioaddr &= ~3; /* remove I/O space marker in bit 0. */ + + if ( check_region( ioaddr, HP100_REGION_SIZE ) ) continue; + + pcibios_read_config_word( pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command ); + if ( !( pci_command & PCI_COMMAND_IO ) ) + { +#ifdef HP100_DEBUG + printk( "hp100: %s: PCI I/O Bit has not been set. Setting...\n", dev->name ); +#endif + pci_command |= PCI_COMMAND_IO; + pcibios_write_config_word( pci_bus, pci_device_fn, + PCI_COMMAND, pci_command ); + } + if ( !( pci_command & PCI_COMMAND_MASTER ) ) + { +#ifdef HP100_DEBUG + printk( "hp100: %s: PCI Master Bit has not been set. Setting...\n", dev->name ); +#endif + pci_command |= PCI_COMMAND_MASTER; + pcibios_write_config_word( pci_bus, pci_device_fn, + PCI_COMMAND, pci_command ); + } +#ifdef HP100_DEBUG + printk( "hp100: %s: PCI adapter found at 0x%x\n", dev->name, ioaddr ); +#endif + if ( hp100_probe1( dev, ioaddr, HP100_BUS_PCI, pci_bus, pci_device_fn ) == 0 ) + return 0; + } + } + if ( pci_start_index > 0 ) return -ENODEV; +#endif /* CONFIG_PCI */ + + /* Second: Probe all EISA possible port regions (if EISA bus present) */ + for ( ioaddr = 0x1c38; EISA_bus && ioaddr < 0x10000; ioaddr += 0x400 ) + { + if ( check_region( ioaddr, HP100_REGION_SIZE ) ) continue; + if ( hp100_probe1( dev, ioaddr, HP100_BUS_EISA, 0, 0 ) == 0 ) return 0; + } + + /* Third Probe all ISA possible port regions */ + for ( ioaddr = 0x100; ioaddr < 0x400; ioaddr += 0x20 ) + { + if ( check_region( ioaddr, HP100_REGION_SIZE ) ) continue; + if ( hp100_probe1( dev, ioaddr, HP100_BUS_ISA, 0, 0 ) == 0 ) return 0; + } + + return -ENODEV; +} + + +__initfunc(static int hp100_probe1( struct device *dev, int ioaddr, u_char bus, u_char pci_bus, u_char pci_device_fn )) +{ + int i; + + u_char uc, uc_1; + u_int eisa_id; + u_int chip; + u_int memory_size = 0, virt_memory_size = 0; + u_short local_mode, lsw; + short mem_mapped; + u_int *mem_ptr_phys, *mem_ptr_virt; + struct hp100_private *lp; + struct hp100_eisa_id *eid; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4201, TRACE ); + printk("hp100: %s: probe1\n",dev->name); +#endif + + if ( dev == NULL ) + { +#ifdef HP100_DEBUG + printk( "hp100_probe1: %s: dev == NULL ?\n", dev->name ); +#endif + return EIO; + } + + if ( hp100_inw( HW_ID ) != HP100_HW_ID_CASCADE ) + { + return -ENODEV; + } + else + { + chip = hp100_inw( PAGING ) & HP100_CHIPID_MASK; +#ifdef HP100_DEBUG + if ( chip == HP100_CHIPID_SHASTA ) + printk("hp100: %s: Shasta Chip detected. (This is a pre 802.12 chip)\n", dev->name); + else if ( chip == HP100_CHIPID_RAINIER ) + printk("hp100: %s: Rainier Chip detected. (This is a pre 802.12 chip)\n", dev->name); + else if ( chip == HP100_CHIPID_LASSEN ) + printk("hp100: %s: Lassen Chip detected.\n", dev->name); + else + printk("hp100: %s: Warning: Unknown CASCADE chip (id=0x%.4x).\n",dev->name,chip); +#endif + } + + dev->base_addr = ioaddr; + + hp100_page( ID_MAC_ADDR ); + for ( i = uc = eisa_id = 0; i < 4; i++ ) + { + eisa_id >>= 8; + uc_1 = hp100_inb( BOARD_ID + i ); + eisa_id |= uc_1 << 24; + uc += uc_1; + } + uc += hp100_inb( BOARD_ID + 4 ); + + if ( uc != 0xff ) /* bad checksum? */ + { + printk("hp100_probe: %s: bad EISA ID checksum at base port 0x%x\n", dev->name, ioaddr ); + return -ENODEV; + } + + for ( i=0; i < HP100_EISA_IDS_SIZE; i++) + if ( hp100_eisa_ids[ i ].id == eisa_id ) + break; + if ( i >= HP100_EISA_IDS_SIZE ) { + for ( i = 0; i < HP100_EISA_IDS_SIZE; i++) + if ( ( hp100_eisa_ids[ i ].id & 0xf0ffffff ) == ( eisa_id & 0xf0ffffff ) ) + break; + if ( i >= HP100_EISA_IDS_SIZE ) { + printk( "hp100_probe: %s: card at port 0x%x isn't known (id = 0x%x)\n", dev -> name, ioaddr, eisa_id ); + return -ENODEV; + } + } + eid = &hp100_eisa_ids[ i ]; + if ( ( eid->id & 0x0f000000 ) < ( eisa_id & 0x0f000000 ) ) + { + printk( "hp100_probe: %s: newer version of card %s at port 0x%x - unsupported\n", + dev->name, eid->name, ioaddr ); + return -ENODEV; + } + + for ( i = uc = 0; i < 7; i++ ) + uc += hp100_inb( LAN_ADDR + i ); + if ( uc != 0xff ) + { + printk("hp100_probe: %s: bad lan address checksum (card %s at port 0x%x)\n", + dev->name, eid->name, ioaddr ); + return -EIO; + } + + /* Make sure, that all registers are correctly updated... */ + + hp100_load_eeprom( dev, ioaddr ); + wait(); + + /* + * Determine driver operation mode + * + * Use the variable "hp100_mode" upon insmod or as kernel parameter to + * force driver modes: + * hp100_mode=1 -> default, use busmaster mode if configured. + * hp100_mode=2 -> enable shared memory mode + * hp100_mode=3 -> force use of i/o mapped mode. + * hp100_mode=4 -> same as 1, but re-set the enable bit on the card. + */ + + /* + * LSW values: + * 0x2278 -> J2585B, PnP shared memory mode + * 0x2270 -> J2585B, shared memory mode, 0xdc000 + * 0xa23c -> J2585B, I/O mapped mode + * 0x2240 -> EISA COMPEX, BusMaster (Shasta Chip) + * 0x2220 -> EISA HP, I/O (Shasta Chip) + * 0x2260 -> EISA HP, BusMaster (Shasta Chip) + */ + +#if 0 + local_mode = 0x2270; + hp100_outw(0xfefe,OPTION_LSW); + hp100_outw(local_mode|HP100_SET_LB|HP100_SET_HB,OPTION_LSW); +#endif + + /* hp100_mode value maybe used in future by another card */ + local_mode=hp100_mode; + if ( local_mode < 1 || local_mode > 4 ) + local_mode = 1; /* default */ +#ifdef HP100_DEBUG + printk( "hp100: %s: original LSW = 0x%x\n", dev->name, hp100_inw(OPTION_LSW) ); +#endif + + if(local_mode==3) + { + hp100_outw(HP100_MEM_EN|HP100_RESET_LB, OPTION_LSW); + hp100_outw(HP100_IO_EN|HP100_SET_LB, OPTION_LSW); + hp100_outw(HP100_BM_WRITE|HP100_BM_READ|HP100_RESET_HB, OPTION_LSW); + printk("hp100: %s: IO mapped mode forced.\n", dev->name); + } + else if(local_mode==2) + { + hp100_outw(HP100_MEM_EN|HP100_SET_LB, OPTION_LSW); + hp100_outw(HP100_IO_EN |HP100_SET_LB, OPTION_LSW); + hp100_outw(HP100_BM_WRITE|HP100_BM_READ|HP100_RESET_HB, OPTION_LSW); + printk("hp100: %s: Shared memory mode requested.\n", dev->name); + } + else if(local_mode==4) + { + if(chip==HP100_CHIPID_LASSEN) + { + hp100_outw(HP100_BM_WRITE| + HP100_BM_READ | HP100_SET_HB, OPTION_LSW); + hp100_outw(HP100_IO_EN | + HP100_MEM_EN | HP100_RESET_LB, OPTION_LSW); + printk("hp100: %s: Busmaster mode requested.\n",dev->name); + } + local_mode=1; + } + + if(local_mode==1) /* default behaviour */ + { + lsw = hp100_inw(OPTION_LSW); + + if ( (lsw & HP100_IO_EN) && + (~lsw & HP100_MEM_EN) && + (~lsw & (HP100_BM_WRITE|HP100_BM_READ)) ) + { +#ifdef HP100_DEBUG + printk("hp100: %s: IO_EN bit is set on card.\n",dev->name); +#endif + local_mode=3; + } + else if ( chip == HP100_CHIPID_LASSEN && + ( lsw & (HP100_BM_WRITE|HP100_BM_READ) ) == + (HP100_BM_WRITE|HP100_BM_READ) ) + { + printk("hp100: %s: Busmaster mode enabled.\n",dev->name); + hp100_outw(HP100_MEM_EN|HP100_IO_EN|HP100_RESET_LB, OPTION_LSW); + } + else + { +#ifdef HP100_DEBUG + printk("hp100: %s: Card not configured for BM or BM not supported with this card.\n", dev->name ); + printk("hp100: %s: Trying shared memory mode.\n", dev->name); +#endif + /* In this case, try shared memory mode */ + local_mode=2; + hp100_outw(HP100_MEM_EN|HP100_SET_LB, OPTION_LSW); + /* hp100_outw(HP100_IO_EN|HP100_RESET_LB, OPTION_LSW); */ + } + } + +#ifdef HP100_DEBUG + printk( "hp100: %s: new LSW = 0x%x\n", dev->name, hp100_inw(OPTION_LSW) ); +#endif + + /* Check for shared memory on the card, eventually remap it */ + hp100_page( HW_MAP ); + mem_mapped = (( hp100_inw( OPTION_LSW ) & ( HP100_MEM_EN ) ) != 0); + mem_ptr_phys = mem_ptr_virt = NULL; + memory_size = (8192<<( (hp100_inb(SRAM)>>5)&0x07)); + virt_memory_size = 0; + + /* For memory mapped or busmaster mode, we want the memory address */ + if ( mem_mapped || (local_mode==1)) + { + mem_ptr_phys = (u_int *)( hp100_inw( MEM_MAP_LSW ) | + ( hp100_inw( MEM_MAP_MSW ) << 16 ) ); + (u_int)mem_ptr_phys &= ~0x1fff; /* 8k alignment */ + + if ( bus == HP100_BUS_ISA && ( (u_long)mem_ptr_phys & ~0xfffff ) != 0 ) + { + printk("hp100: %s: Can only use programmed i/o mode.\n", dev->name); + mem_ptr_phys = NULL; + mem_mapped = 0; + local_mode=3; /* Use programmed i/o */ + } + + /* We do not need access to shared memory in busmaster mode */ + /* However in slave mode we need to remap high (>1GB) card memory */ + if(local_mode!=1) /* = not busmaster */ + { + if ( bus == HP100_BUS_PCI && mem_ptr_phys >= (u_int *)0x100000 ) + { + /* We try with smaller memory sizes, if ioremap fails */ + for(virt_memory_size = memory_size; virt_memory_size>16383; virt_memory_size>>=1) + { + if((mem_ptr_virt=ioremap((u_long)mem_ptr_phys,virt_memory_size))==NULL) + { +#ifdef HP100_DEBUG + printk( "hp100: %s: ioremap for 0x%x bytes high PCI memory at 0x%lx failed\n", dev->name, virt_memory_size, (u_long)mem_ptr_phys ); +#endif + } + else + { +#ifdef HP100_DEBUG + printk( "hp100: %s: remapped 0x%x bytes high PCI memory at 0x%lx to 0x%lx.\n", dev->name, virt_memory_size, (u_long)mem_ptr_phys, (u_long)mem_ptr_virt); +#endif + break; + } + } + + if(mem_ptr_virt==NULL) /* all ioremap tries failed */ + { + printk("hp100: %s: Failed to ioremap the PCI card memory. Will have to use i/o mapped mode.\n", dev->name); + local_mode=3; + virt_memory_size = 0; + } + } + } + + } + + if(local_mode==3) /* io mapped forced */ + { + mem_mapped = 0; + mem_ptr_phys = mem_ptr_virt = NULL; + printk("hp100: %s: Using (slow) programmed i/o mode.\n", dev->name); + } + + /* Initialise the "private" data structure for this card. */ + if ( (dev->priv=kmalloc(sizeof(struct hp100_private), GFP_KERNEL)) == NULL) + return -ENOMEM; + memset( dev->priv, 0, sizeof(struct hp100_private) ); + + lp = (struct hp100_private *)dev->priv; + lp->id = eid; + lp->chip = chip; + lp->mode = local_mode; + lp->pci_bus = pci_bus; + lp->bus = bus; + lp->pci_device_fn = pci_device_fn; + lp->priority_tx = hp100_priority_tx; + lp->rx_ratio = hp100_rx_ratio; + lp->mem_ptr_phys = mem_ptr_phys; + lp->mem_ptr_virt = mem_ptr_virt; + hp100_page( ID_MAC_ADDR ); + lp->soft_model = hp100_inb( SOFT_MODEL ); + lp->mac1_mode = HP100_MAC1MODE3; + lp->mac2_mode = HP100_MAC2MODE3; + memset( &lp->hash_bytes, 0x00, 8 ); + + dev->base_addr = ioaddr; + + lp->memory_size = memory_size; + lp->virt_memory_size = virt_memory_size; + lp->rx_ratio = hp100_rx_ratio; /* can be conf'd with insmod */ + + /* memory region for programmed i/o */ + request_region( dev->base_addr, HP100_REGION_SIZE, eid->name ); + + dev->open = hp100_open; + dev->stop = hp100_close; + + if (lp->mode==1) /* busmaster */ + dev->hard_start_xmit = hp100_start_xmit_bm; + else + dev->hard_start_xmit = hp100_start_xmit; + + dev->get_stats = hp100_get_stats; + dev->set_multicast_list = &hp100_set_multicast_list; + + /* Ask the card for which IRQ line it is configured */ + hp100_page( HW_MAP ); + dev->irq = hp100_inb( IRQ_CHANNEL ) & HP100_IRQMASK; + if ( dev->irq == 2 ) + dev->irq = 9; + + if(lp->mode==1) /* busmaster */ + dev->dma=4; + + /* Ask the card for its MAC address and store it for later use. */ + hp100_page( ID_MAC_ADDR ); + for ( i = uc = 0; i < 6; i++ ) + dev->dev_addr[ i ] = hp100_inb( LAN_ADDR + i ); + + /* Reset statistics (counters) */ + hp100_clear_stats( ioaddr ); + + ether_setup( dev ); + + /* If busmaster mode is wanted, a dma-capable memory area is needed for + * the rx and tx PDLs + * PCI cards can access the whole PC memory. Therefore GFP_DMA is not + * needed for the allocation of the memory area. + */ + + /* TODO: We do not need this with old cards, where PDLs are stored + * in the cards shared memory area. But currently, busmaster has been + * implemented/tested only with the lassen chip anyway... */ + if(lp->mode==1) /* busmaster */ + { + /* Get physically continous memory for TX & RX PDLs */ + if ( (lp->page_vaddr=kmalloc(MAX_RINGSIZE+0x0f,GFP_KERNEL) ) == NULL) + return -ENOMEM; + lp->page_vaddr_algn=((u_int *) ( ((u_int)(lp->page_vaddr)+0x0f) &~0x0f)); + memset(lp->page_vaddr, 0, MAX_RINGSIZE+0x0f); + +#ifdef HP100_DEBUG_BM + printk("hp100: %s: Reserved DMA memory from 0x%x to 0x%x\n", + dev->name, + (u_int)lp->page_vaddr_algn, + (u_int)lp->page_vaddr_algn+MAX_RINGSIZE); +#endif + lp->rxrcommit = lp->txrcommit = 0; + lp->rxrhead = lp->rxrtail = &(lp->rxring[0]); + lp->txrhead = lp->txrtail = &(lp->txring[0]); + } + + /* Initialise the card. */ + /* (I'm not really sure if it's a good idea to do this during probing, but + * like this it's assured that the lan connection type can be sensed + * correctly) + */ + hp100_hwinit( dev ); + + /* Try to find out which kind of LAN the card is connected to. */ + lp->lan_type = hp100_sense_lan( dev ); + + /* Print out a message what about what we think we have probed. */ + printk( "hp100: %s: %s at 0x%x, IRQ %d, ", + dev->name, lp->id->name, ioaddr, dev->irq ); + switch ( bus ) { + case HP100_BUS_EISA: printk( "EISA" ); break; + case HP100_BUS_PCI: printk( "PCI" ); break; + default: printk( "ISA" ); break; + } + printk( " bus, %dk SRAM (rx/tx %d%%).\n", + lp->memory_size >> 10, lp->rx_ratio ); + + if ( lp->mode==2 ) /* memory mapped */ + { + printk( "hp100: %s: Memory area at 0x%lx-0x%lx", + dev->name,(u_long)mem_ptr_phys, + ((u_long)mem_ptr_phys+(mem_ptr_phys>(u_int *)0x100000?(u_long)lp->memory_size:16*1024))-1 ); + if ( mem_ptr_virt ) + printk( " (virtual base 0x%lx)", (u_long)mem_ptr_virt ); + printk( ".\n" ); + + /* Set for info when doing ifconfig */ + dev->mem_start = (u_long)mem_ptr_phys; + dev->mem_end = (u_long)mem_ptr_phys+(u_long)lp->memory_size; + } + printk( "hp100: %s: ", dev->name ); + if ( lp->lan_type != HP100_LAN_ERR ) + printk( "Adapter is attached to " ); + switch ( lp->lan_type ) { + case HP100_LAN_100: + printk( "100Mb/s Voice Grade AnyLAN network.\n" ); + break; + case HP100_LAN_10: + printk( "10Mb/s network.\n" ); + break; + default: + printk( "Warning! Link down.\n" ); + } + + return 0; +} + + +/* This procedure puts the card into a stable init state */ +static void hp100_hwinit( struct device *dev ) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4202, TRACE ); + printk("hp100: %s: hwinit\n", dev->name); +#endif + + /* Initialise the card. -------------------------------------------- */ + + /* Clear all pending Ints and disable Ints */ + hp100_page( PERFORMANCE ); + hp100_outw( 0xfefe, IRQ_MASK ); /* mask off all ints */ + hp100_outw( 0xffff, IRQ_STATUS ); /* clear all pending ints */ + + hp100_outw( HP100_INT_EN | HP100_RESET_LB, OPTION_LSW ); + hp100_outw( HP100_TRI_INT | HP100_SET_HB, OPTION_LSW ); + + if(lp->mode==1) + { + hp100_BM_shutdown( dev ); /* disables BM, puts cascade in reset */ + wait(); + } + else + { + hp100_outw( HP100_INT_EN | HP100_RESET_LB, OPTION_LSW ); + hp100_cascade_reset( dev, TRUE ); + hp100_page( MAC_CTRL ); + hp100_andb( ~(HP100_RX_EN|HP100_TX_EN), MAC_CFG_1); + } + + /* Initiate EEPROM reload */ + hp100_load_eeprom( dev, 0 ); + + wait(); + + /* Go into reset again. */ + hp100_cascade_reset( dev, TRUE ); + + /* Set Option Registers to a safe state */ + hp100_outw( HP100_DEBUG_EN | + HP100_RX_HDR | + HP100_EE_EN | + HP100_BM_WRITE | + HP100_BM_READ | HP100_RESET_HB | + HP100_FAKE_INT | + HP100_INT_EN | + HP100_MEM_EN | + HP100_IO_EN | HP100_RESET_LB, OPTION_LSW); + + hp100_outw( HP100_TRI_INT | + HP100_MMAP_DIS | HP100_SET_HB, OPTION_LSW ); + + hp100_outb( HP100_PRIORITY_TX | + HP100_ADV_NXT_PKT | + HP100_TX_CMD | HP100_RESET_LB, OPTION_MSW ); + + /* TODO: Configure MMU for Ram Test. */ + /* TODO: Ram Test. */ + + /* Re-check if adapter is still at same i/o location */ + /* (If the base i/o in eeprom has been changed but the */ + /* registers had not been changed, a reload of the eeprom */ + /* would move the adapter to the address stored in eeprom */ + + /* TODO: Code to implement. */ + + /* Until here it was code from HWdiscover procedure. */ + /* Next comes code from mmuinit procedure of SCO BM driver which is + * called from HWconfigure in the SCO driver. */ + + /* Initialise MMU, eventually switch on Busmaster Mode, initialise + * multicast filter... + */ + hp100_mmuinit( dev ); + + /* We don't turn the interrupts on here - this is done by start_interface. */ + wait(); /* TODO: Do we really need this? */ + + /* Enable Hardware (e.g. unreset) */ + hp100_cascade_reset( dev, FALSE ); + + /* ------- initialisation complete ----------- */ + + /* Finally try to log in the Hub if there may be a VG connection. */ + if( lp->lan_type != HP100_LAN_10 ) + hp100_login_to_vg_hub( dev, FALSE ); /* relogin */ +} + + +/* + * mmuinit - Reinitialise Cascade MMU and MAC settings. + * Note: Must already be in reset and leaves card in reset. + */ +static void hp100_mmuinit( struct device *dev ) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + int i; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4203, TRACE ); + printk("hp100: %s: mmuinit\n",dev->name); +#endif + +#ifdef HP100_DEBUG + if( 0!=(hp100_inw(OPTION_LSW)&HP100_HW_RST) ) + { + printk("hp100: %s: Not in reset when entering mmuinit. Fix me.\n",dev->name); + return; + } +#endif + + /* Make sure IRQs are masked off and ack'ed. */ + hp100_page( PERFORMANCE ); + hp100_outw( 0xfefe, IRQ_MASK ); /* mask off all ints */ + hp100_outw( 0xffff, IRQ_STATUS ); /* ack IRQ */ + + /* + * Enable Hardware + * - Clear Debug En, Rx Hdr Pipe, EE En, I/O En, Fake Int and Intr En + * - Set Tri-State Int, Bus Master Rd/Wr, and Mem Map Disable + * - Clear Priority, Advance Pkt and Xmit Cmd + */ + + hp100_outw( HP100_DEBUG_EN | + HP100_RX_HDR | + HP100_EE_EN | HP100_RESET_HB | + HP100_IO_EN | + HP100_FAKE_INT | + HP100_INT_EN | HP100_RESET_LB, OPTION_LSW ); + + hp100_outw( HP100_TRI_INT | HP100_SET_HB, OPTION_LSW); + + if(lp->mode==1) /* busmaster */ + { + hp100_outw( HP100_BM_WRITE | + HP100_BM_READ | + HP100_MMAP_DIS | HP100_SET_HB, OPTION_LSW ); + } + else if(lp->mode==2) /* memory mapped */ + { + hp100_outw( HP100_BM_WRITE | + HP100_BM_READ | HP100_RESET_HB, OPTION_LSW ); + hp100_outw( HP100_MMAP_DIS | HP100_RESET_HB, OPTION_LSW ); + hp100_outw( HP100_MEM_EN | HP100_SET_LB, OPTION_LSW ); + hp100_outw( HP100_IO_EN | HP100_SET_LB, OPTION_LSW ); + } + else if( lp->mode==3 ) /* i/o mapped mode */ + { + hp100_outw( HP100_MMAP_DIS | HP100_SET_HB | + HP100_IO_EN | HP100_SET_LB, OPTION_LSW ); + } + + hp100_page( HW_MAP ); + hp100_outb( 0, EARLYRXCFG ); + hp100_outw( 0, EARLYTXCFG ); + + /* + * Enable Bus Master mode + */ + if(lp->mode==1) /* busmaster */ + { + /* Experimental: Set some PCI configuration bits */ + hp100_page( HW_MAP ); + hp100_andb( ~HP100_PDL_USE3, MODECTRL1 ); /* BM engine read maximum */ + hp100_andb( ~HP100_TX_DUALQ, MODECTRL1 ); /* No Queue for Priority TX */ + + /* PCI Bus failures should result in a Misc. Interrupt */ + hp100_orb( HP100_EN_BUS_FAIL, MODECTRL2); + + hp100_outw( HP100_BM_READ | HP100_BM_WRITE | HP100_SET_HB, OPTION_LSW ); + hp100_page( HW_MAP ); + /* Use Burst Mode and switch on PAGE_CK */ + hp100_orb( HP100_BM_BURST_RD | + HP100_BM_BURST_WR, BM); + if((lp->chip==HP100_CHIPID_RAINIER)||(lp->chip==HP100_CHIPID_SHASTA)) + hp100_orb( HP100_BM_PAGE_CK, BM ); + hp100_orb( HP100_BM_MASTER, BM ); + } + else /* not busmaster */ + { + hp100_page(HW_MAP); + hp100_andb(~HP100_BM_MASTER, BM ); + } + + /* + * Divide card memory into regions for Rx, Tx and, if non-ETR chip, PDLs + */ + hp100_page( MMU_CFG ); + if(lp->mode==1) /* only needed for Busmaster */ + { + int xmit_stop, recv_stop; + + if((lp->chip==HP100_CHIPID_RAINIER)||(lp->chip==HP100_CHIPID_SHASTA)) + { + int pdl_stop; + + /* + * Each pdl is 508 bytes long. (63 frags * 4 bytes for address and + * 4 bytes for header). We will leave NUM_RXPDLS * 508 (rounded + * to the next higher 1k boundary) bytes for the rx-pdl's + * Note: For non-etr chips the transmit stop register must be + * programmed on a 1k boundary, i.e. bits 9:0 must be zero. + */ + pdl_stop = lp->memory_size; + xmit_stop = ( pdl_stop-508*(MAX_RX_PDL)-16 )& ~(0x03ff); + recv_stop = ( xmit_stop * (lp->rx_ratio)/100 ) &~(0x03ff); + hp100_outw( (pdl_stop>>4)-1, PDL_MEM_STOP ); +#ifdef HP100_DEBUG_BM + printk("hp100: %s: PDL_STOP = 0x%x\n", dev->name, pdl_stop); +#endif + } + else /* ETR chip (Lassen) in busmaster mode */ + { + xmit_stop = ( lp->memory_size ) - 1; + recv_stop = ( ( lp->memory_size * lp->rx_ratio ) / 100 ) & ~(0x03ff); + } + + hp100_outw( xmit_stop>>4 , TX_MEM_STOP ); + hp100_outw( recv_stop>>4 , RX_MEM_STOP ); +#ifdef HP100_DEBUG_BM + printk("hp100: %s: TX_STOP = 0x%x\n",dev->name,xmit_stop>>4); + printk("hp100: %s: RX_STOP = 0x%x\n",dev->name,recv_stop>>4); +#endif + } + else /* Slave modes (memory mapped and programmed io) */ + { + hp100_outw( (((lp->memory_size*lp->rx_ratio)/100)>>4), RX_MEM_STOP ); + hp100_outw( ((lp->memory_size - 1 )>>4), TX_MEM_STOP ); +#ifdef HP100_DEBUG + printk("hp100: %s: TX_MEM_STOP: 0x%x\n", dev->name,hp100_inw(TX_MEM_STOP)); + printk("hp100: %s: RX_MEM_STOP: 0x%x\n", dev->name,hp100_inw(RX_MEM_STOP)); +#endif + } + + /* Write MAC address into page 1 */ + hp100_page( MAC_ADDRESS ); + for ( i = 0; i < 6; i++ ) + hp100_outb( dev->dev_addr[ i ], MAC_ADDR + i ); + + /* Zero the multicast hash registers */ + for ( i = 0; i < 8; i++ ) + hp100_outb( 0x0, HASH_BYTE0 + i ); + + /* Set up MAC defaults */ + hp100_page( MAC_CTRL ); + + /* Go to LAN Page and zero all filter bits */ + /* Zero accept error, accept multicast, accept broadcast and accept */ + /* all directed packet bits */ + hp100_andb( ~(HP100_RX_EN| + HP100_TX_EN| + HP100_ACC_ERRORED| + HP100_ACC_MC| + HP100_ACC_BC| + HP100_ACC_PHY), MAC_CFG_1 ); + + hp100_outb( 0x00, MAC_CFG_2 ); + + /* Zero the frame format bit. This works around a training bug in the */ + /* new hubs. */ + hp100_outb( 0x00, VG_LAN_CFG_2); /* (use 802.3) */ + + if(lp->priority_tx) + hp100_outb( HP100_PRIORITY_TX | HP100_SET_LB, OPTION_MSW ); + else + hp100_outb( HP100_PRIORITY_TX | HP100_RESET_LB, OPTION_MSW ); + + hp100_outb( HP100_ADV_NXT_PKT | + HP100_TX_CMD | HP100_RESET_LB, OPTION_MSW ); + + /* If busmaster, initialize the PDLs */ + if(lp->mode==1) + hp100_init_pdls( dev ); + + /* Go to performance page and initalize isr and imr registers */ + hp100_page( PERFORMANCE ); + hp100_outw( 0xfefe, IRQ_MASK ); /* mask off all ints */ + hp100_outw( 0xffff, IRQ_STATUS ); /* ack IRQ */ +} + + +/* + * open/close functions + */ + +static int hp100_open( struct device *dev ) +{ + struct hp100_private *lp = (struct hp100_private *)dev->priv; +#ifdef HP100_DEBUG_B + int ioaddr=dev->base_addr; +#endif + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4204, TRACE ); + printk("hp100: %s: open\n",dev->name); +#endif + + /* New: if bus is PCI or EISA, interrupts might be shared interrupts */ + if ( request_irq(dev->irq, hp100_interrupt, + lp->bus==HP100_BUS_PCI||lp->bus==HP100_BUS_EISA?SA_SHIRQ:SA_INTERRUPT, + lp->id->name, dev)) + { + printk( "hp100: %s: unable to get IRQ %d\n", dev->name, dev->irq ); + return -EAGAIN; + } + + MOD_INC_USE_COUNT; + + dev->tbusy = 0; + dev->trans_start = jiffies; + dev->interrupt = 0; + dev->start = 1; + + lp->lan_type = hp100_sense_lan( dev ); + lp->mac1_mode = HP100_MAC1MODE3; + lp->mac2_mode = HP100_MAC2MODE3; + memset( &lp->hash_bytes, 0x00, 8 ); + + hp100_stop_interface( dev ); + + hp100_hwinit( dev ); + + hp100_start_interface( dev ); /* sets mac modes, enables interrupts */ + + return 0; +} + + +/* The close function is called when the interface is to be brought down */ +static int hp100_close( struct device *dev ) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4205, TRACE ); + printk("hp100: %s: close\n", dev->name); +#endif + + hp100_page( PERFORMANCE ); + hp100_outw( 0xfefe, IRQ_MASK ); /* mask off all IRQs */ + + hp100_stop_interface( dev ); + + if ( lp->lan_type == HP100_LAN_100 ) + lp->hub_status=hp100_login_to_vg_hub( dev, FALSE ); + + dev->tbusy = 1; + dev->start = 0; + + free_irq( dev->irq, dev ); + +#ifdef HP100_DEBUG + printk( "hp100: %s: close LSW = 0x%x\n", dev->name, hp100_inw(OPTION_LSW) ); +#endif + + MOD_DEC_USE_COUNT; + return 0; +} + + +/* + * Configure the PDL Rx rings and LAN + */ +static void hp100_init_pdls( struct device *dev ) +{ + struct hp100_private *lp = (struct hp100_private *)dev->priv; + hp100_ring_t *ringptr; + u_int *pageptr; + int i; + +#ifdef HP100_DEBUG_B + int ioaddr = dev->base_addr; +#endif + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4206, TRACE ); + printk("hp100: %s: init pdls\n", dev->name); +#endif + + if(0==lp->page_vaddr_algn) + printk("hp100: %s: Warning: lp->page_vaddr_algn not initialised!\n",dev->name); + else + { + /* pageptr shall point into the DMA accessible memory region */ + /* we use this pointer to status the upper limit of allocated */ + /* memory in the allocated page. */ + /* note: align the pointers to the pci cache line size */ + memset(lp->page_vaddr_algn, 0, MAX_RINGSIZE); /* Zero Rx/Tx ring page */ + pageptr=lp->page_vaddr_algn; + + lp->rxrcommit =0; + ringptr = lp->rxrhead = lp-> rxrtail = &(lp->rxring[0]); + + /* Initialise Rx Ring */ + for (i=MAX_RX_PDL-1; i>=0; i--) + { + lp->rxring[i].next = ringptr; + ringptr=&(lp->rxring[i]); + pageptr+=hp100_init_rxpdl(dev, ringptr, pageptr); + } + + /* Initialise Tx Ring */ + lp->txrcommit = 0; + ringptr = lp->txrhead = lp->txrtail = &(lp->txring[0]); + for (i=MAX_TX_PDL-1; i>=0; i--) + { + lp->txring[i].next = ringptr; + ringptr=&(lp->txring[i]); + pageptr+=hp100_init_txpdl(dev, ringptr, pageptr); + } + } +} + + +/* These functions "format" the entries in the pdl structure */ +/* They return how much memory the fragments need. */ +static int hp100_init_rxpdl( struct device *dev, register hp100_ring_t *ringptr, register u32 *pdlptr ) +{ + /* pdlptr is starting adress for this pdl */ + + if( 0!=( ((unsigned)pdlptr) & 0xf) ) + printk("hp100: %s: Init rxpdl: Unaligned pdlptr 0x%x.\n",dev->name,(unsigned)pdlptr); + + ringptr->pdl = pdlptr+1; + ringptr->pdl_paddr = virt_to_bus(pdlptr+1); + ringptr->skb = (void *) NULL; + + /* + * Write address and length of first PDL Fragment (which is used for + * storing the RX-Header + * We use the 4 bytes _before_ the PDH in the pdl memory area to + * store this information. (PDH is at offset 0x04) + */ + /* Note that pdlptr+1 and not pdlptr is the pointer to the PDH */ + + *(pdlptr+2) =(u_int) virt_to_bus(pdlptr); /* Address Frag 1 */ + *(pdlptr+3) = 4; /* Length Frag 1 */ + + return( ( ((MAX_RX_FRAG*2+2)+3) /4)*4 ); +} + + +static int hp100_init_txpdl( struct device *dev, register hp100_ring_t *ringptr, register u32 *pdlptr ) +{ + if( 0!=( ((unsigned)pdlptr) & 0xf) ) + printk("hp100: %s: Init txpdl: Unaligned pdlptr 0x%x.\n",dev->name,(unsigned) pdlptr); + + ringptr->pdl = pdlptr; /* +1; */ + ringptr->pdl_paddr = virt_to_bus(pdlptr); /* +1 */ + ringptr->skb = (void *) NULL; + + return((((MAX_TX_FRAG*2+2)+3)/4)*4); +} + + +/* + * hp100_build_rx_pdl allocates an skb_buff of maximum size plus two bytes + * for possible odd word alignment rounding up to next dword and set PDL + * address for fragment#2 + * Returns: 0 if unable to allocate skb_buff + * 1 if successful + */ +int hp100_build_rx_pdl( hp100_ring_t *ringptr, struct device *dev ) +{ +#ifdef HP100_DEBUG_B + int ioaddr = dev->base_addr; +#endif +#ifdef HP100_DEBUG_BM + u_int *p; +#endif + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4207, TRACE ); + printk("hp100: %s: build rx pdl\n", dev->name); +#endif + + /* Allocate skb buffer of maximum size */ + /* Note: This depends on the alloc_skb functions allocating more + * space than requested, i.e. aligning to 16bytes */ + + ringptr->skb = dev_alloc_skb( ((MAX_ETHER_SIZE+2+3)/4)*4 ); + + if(NULL!=ringptr->skb) + { + /* + * Reserve 2 bytes at the head of the buffer to land the IP header + * on a long word boundary (According to the Network Driver section + * in the Linux KHG, this should help to increase performance.) + */ + skb_reserve(ringptr->skb, 2); + + ringptr->skb->dev=dev; + ringptr->skb->data=(u_char *)skb_put(ringptr->skb, MAX_ETHER_SIZE ); + + /* ringptr->pdl points to the beginning of the PDL, i.e. the PDH */ + /* Note: 1st Fragment is used for the 4 byte packet status + * (receive header). Its PDL entries are set up by init_rxpdl. So + * here we only have to set up the PDL fragment entries for the data + * part. Those 4 bytes will be stored in the DMA memory region + * directly before the PDL. + */ +#ifdef HP100_DEBUG_BM + printk("hp100: %s: build_rx_pdl: PDH@0x%x, skb->data (len %d) at 0x%x\n", + dev->name, + (u_int) ringptr->pdl, + ((MAX_ETHER_SIZE+2+3)/4)*4, + (unsigned int) ringptr->skb->data); +#endif + + ringptr->pdl[0] = 0x00020000; /* Write PDH */ + ringptr->pdl[3] = ((u_int)virt_to_bus(ringptr->skb->data)); + ringptr->pdl[4] = MAX_ETHER_SIZE; /* Length of Data */ + +#ifdef HP100_DEBUG_BM + for(p=(ringptr->pdl); p<(ringptr->pdl+5); p++) + printk("hp100: %s: Adr 0x%.8x = 0x%.8x\n",dev->name,(u_int) p,(u_int) *p ); +#endif + return(1); + } + /* else: */ + /* alloc_skb failed (no memory) -> still can receive the header + * fragment into PDL memory. make PDL safe by clearing msgptr and + * making the PDL only 1 fragment (i.e. the 4 byte packet status) + */ +#ifdef HP100_DEBUG_BM + printk("hp100: %s: build_rx_pdl: PDH@0x%x, No space for skb.\n", + dev->name, + (u_int) ringptr->pdl); +#endif + + ringptr->pdl[0]=0x00010000; /* PDH: Count=1 Fragment */ + + return(0); +} + + +/* + * hp100_rxfill - attempt to fill the Rx Ring will empty skb's + * + * Makes assumption that skb's are always contiguous memory areas and + * therefore PDLs contain only 2 physical fragments. + * - While the number of Rx PDLs with buffers is less than maximum + * a. Get a maximum packet size skb + * b. Put the physical address of the buffer into the PDL. + * c. Output physical address of PDL to adapter. + */ +static void hp100_rxfill( struct device *dev ) +{ + int ioaddr=dev->base_addr; + + struct hp100_private *lp = (struct hp100_private *)dev->priv; + hp100_ring_t *ringptr; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4208, TRACE ); + printk("hp100: %s: rxfill\n",dev->name); +#endif + + hp100_page( PERFORMANCE ); + + while (lp->rxrcommit < MAX_RX_PDL) + { + /* + ** Attempt to get a buffer and build a Rx PDL. + */ + ringptr = lp->rxrtail; + if (0 == hp100_build_rx_pdl( ringptr, dev )) + { + return; /* None available, return */ + } + + /* Hand this PDL over to the card */ + /* Note: This needs performance page selected! */ +#ifdef HP100_DEBUG_BM + printk("hp100: %s: rxfill: Hand to card: pdl #%d @0x%x phys:0x%x, buffer: 0x%x\n", + dev->name, + lp->rxrcommit, + (u_int)ringptr->pdl, + (u_int)ringptr->pdl_paddr, + (u_int)ringptr->pdl[3]); +#endif + + hp100_outl( (u32)ringptr->pdl_paddr, RX_PDA); + + lp->rxrcommit += 1; + lp->rxrtail = ringptr->next; + } +} + + +/* + * BM_shutdown - shutdown bus mastering and leave chip in reset state + */ + +static void hp100_BM_shutdown( struct device *dev ) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + unsigned long time; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4209, TRACE ); + printk("hp100: %s: bm shutdown\n",dev->name); +#endif + + hp100_page( PERFORMANCE ); + hp100_outw( 0xfefe, IRQ_MASK ); /* mask off all ints */ + hp100_outw( 0xffff, IRQ_STATUS ); /* Ack all ints */ + + /* Ensure Interrupts are off */ + hp100_outw( HP100_INT_EN | HP100_RESET_LB , OPTION_LSW ); + + /* Disable all MAC activity */ + hp100_page( MAC_CTRL ); + hp100_andb( ~(HP100_RX_EN | HP100_TX_EN), MAC_CFG_1 ); /* stop rx/tx */ + + /* If cascade MMU is not already in reset */ + if (0 != (hp100_inw(OPTION_LSW)&HP100_HW_RST) ) + { + /* Wait 1.3ms (10Mb max packet time) to ensure MAC is idle so + * MMU pointers will not be reset out from underneath + */ + hp100_page( MAC_CTRL ); + for(time=0; time<5000; time++) + { + if( (hp100_inb(MAC_CFG_1)&(HP100_TX_IDLE|HP100_RX_IDLE))== + (HP100_TX_IDLE|HP100_RX_IDLE) ) break; + } + + /* Shutdown algorithm depends on the generation of Cascade */ + if( lp->chip==HP100_CHIPID_LASSEN ) + { /* ETR shutdown/reset */ + /* Disable Busmaster mode and wait for bit to go to zero. */ + hp100_page(HW_MAP); + hp100_andb( ~HP100_BM_MASTER, BM ); + /* 100 ms timeout */ + for(time=0; time<32000; time++) + { + if ( 0 == (hp100_inb( BM ) & HP100_BM_MASTER) ) break; + } + } + else + { /* Shasta or Rainier Shutdown/Reset */ + /* To ensure all bus master inloading activity has ceased, + * wait for no Rx PDAs or no Rx packets on card. + */ + hp100_page( PERFORMANCE ); + /* 100 ms timeout */ + for(time=0; time<10000; time++) + { + /* RX_PDL: PDLs not executed. */ + /* RX_PKT_CNT: RX'd packets on card. */ + if ( (hp100_inb( RX_PDL ) == 0) && + (hp100_inb( RX_PKT_CNT ) == 0) ) break; + } + + if(time>=10000) + printk("hp100: %s: BM shutdown error.\n", dev->name); + + /* To ensure all bus master outloading activity has ceased, + * wait until the Tx PDA count goes to zero or no more Tx space + * available in the Tx region of the card. + */ + /* 100 ms timeout */ + for(time=0; time<10000; time++) { + if ( (0 == hp100_inb( TX_PKT_CNT )) && + (0 != (hp100_inb( TX_MEM_FREE )&HP100_AUTO_COMPARE))) break; + } + + /* Disable Busmaster mode */ + hp100_page(HW_MAP); + hp100_andb( ~HP100_BM_MASTER, BM ); + } /* end of shutdown procedure for non-etr parts */ + + hp100_cascade_reset( dev, TRUE ); + } + hp100_page( PERFORMANCE ); + /* hp100_outw( HP100_BM_READ | HP100_BM_WRITE | HP100_RESET_HB, OPTION_LSW ); */ + /* Busmaster mode should be shut down now. */ +} + + + +/* + * transmit functions + */ + +/* tx function for busmaster mode */ +static int hp100_start_xmit_bm( struct sk_buff *skb, struct device *dev ) +{ + unsigned long flags; + int i, ok_flag; + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + hp100_ring_t *ringptr; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4210, TRACE ); + printk("hp100: %s: start_xmit_bm\n",dev->name); +#endif + + if ( skb==NULL ) + { +#ifndef LINUX_2_1 + dev_tint( dev ); +#endif + return 0; + } + + if ( skb->len <= 0 ) return 0; + + /* Get Tx ring tail pointer */ + if( lp->txrtail->next==lp->txrhead ) + { + /* No memory. */ +#ifdef HP100_DEBUG + printk("hp100: %s: start_xmit_bm: No TX PDL available.\n", dev->name); +#endif + /* not waited long enough since last tx? */ + if ( jiffies - dev->trans_start < HZ ) return -EAGAIN; + + if ( lp->lan_type < 0 ) /* no LAN type detected yet? */ + { + hp100_stop_interface( dev ); + if ( ( lp->lan_type = hp100_sense_lan( dev ) ) < 0 ) + { + printk( "hp100: %s: no connection found - check wire\n", dev->name ); + hp100_start_interface( dev ); /* 10Mb/s RX pkts maybe handled */ + return -EIO; + } + if ( lp->lan_type == HP100_LAN_100 ) + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); /* relogin */ + hp100_start_interface( dev ); + } + + if ( lp->lan_type == HP100_LAN_100 && lp->hub_status < 0 ) + /* we have a 100Mb/s adapter but it isn't connected to hub */ + { + printk( "hp100: %s: login to 100Mb/s hub retry\n", dev->name ); + hp100_stop_interface( dev ); + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); + hp100_start_interface( dev ); + } + else + { + hp100_ints_off(); + i = hp100_sense_lan( dev ); + hp100_ints_on(); + if ( i == HP100_LAN_ERR ) + printk( "hp100: %s: link down detected\n", dev->name ); + else + if ( lp->lan_type != i ) /* cable change! */ + { + /* it's very hard - all network setting must be changed!!! */ + printk( "hp100: %s: cable change 10Mb/s <-> 100Mb/s detected\n", dev->name ); + lp->lan_type = i; + hp100_stop_interface( dev ); + if ( lp->lan_type == HP100_LAN_100 ) + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); + hp100_start_interface( dev ); + } + else + { + printk( "hp100: %s: interface reset\n", dev->name ); + hp100_stop_interface( dev ); + if ( lp->lan_type == HP100_LAN_100 ) + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); + hp100_start_interface( dev ); + } + } + + dev->trans_start = jiffies; + return -EAGAIN; + } + + /* + * we have to turn int's off before modifying this, otherwise + * a tx_pdl_cleanup could occur at the same time + */ + save_flags( flags ); + cli(); + ringptr=lp->txrtail; + lp->txrtail=ringptr->next; + + /* Check whether packet has minimal packet size */ + ok_flag = skb->len >= HP100_MIN_PACKET_SIZE; + i = ok_flag ? skb->len : HP100_MIN_PACKET_SIZE; + + ringptr->skb=skb; + ringptr->pdl[0]=((1<<16) | i); /* PDH: 1 Fragment & length */ + ringptr->pdl[1]=(u32)virt_to_bus(skb->data); /* 1st Frag: Adr. of data */ + if(lp->chip==HP100_CHIPID_SHASTA) + { + /* TODO:Could someone who has the EISA card please check if this works? */ + ringptr->pdl[2]=i; + } + else /* Lassen */ + { + /* In the PDL, don't use the padded size but the real packet size: */ + ringptr->pdl[2]=skb->len; /* 1st Frag: Length of frag */ + } + + /* Hand this PDL to the card. */ + hp100_outl( ringptr->pdl_paddr, TX_PDA_L ); /* Low Prio. Queue */ + + lp->txrcommit++; + restore_flags( flags ); + + /* Update statistics */ + lp->stats.tx_packets++; +#ifdef LINUX_2_1 + lp->stats.tx_bytes += skb->len; +#endif + dev->trans_start = jiffies; + + return 0; +} + + +/* clean_txring checks if packets have been sent by the card by reading + * the TX_PDL register from the performance page and comparing it to the + * number of commited packets. It then frees the skb's of the packets that + * obviously have been sent to the network. + * + * Needs the PERFORMANCE page selected. + */ +static void hp100_clean_txring( struct device *dev ) +{ + struct hp100_private *lp = (struct hp100_private *)dev->priv; + int ioaddr = dev->base_addr; + int donecount; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4211, TRACE ); + printk("hp100: %s: clean txring\n", dev->name); +#endif + + /* How many PDLs have been transmitted? */ + donecount=(lp->txrcommit)-hp100_inb(TX_PDL); + +#ifdef HP100_DEBUG + if(donecount>MAX_TX_PDL) + printk("hp100: %s: Warning: More PDLs transmitted than commited to card???\n",dev->name); +#endif + + for( ; 0!=donecount; donecount-- ) + { +#ifdef HP100_DEBUG_BM + printk("hp100: %s: Free skb: data @0x%.8x txrcommit=0x%x TXPDL=0x%x, done=0x%x\n", + dev->name, + (u_int) lp->txrhead->skb->data, + lp->txrcommit, + hp100_inb(TX_PDL), + donecount); +#endif +#ifdef LINUX_2_1 + dev_kfree_skb( lp->txrhead->skb ); +#else + dev_kfree_skb( lp->txrhead->skb, FREE_WRITE ); +#endif + lp->txrhead->skb=(void *)NULL; + lp->txrhead=lp->txrhead->next; + lp->txrcommit--; + } +} + + +/* tx function for slave modes */ +static int hp100_start_xmit( struct sk_buff *skb, struct device *dev ) +{ + int i, ok_flag; + int ioaddr = dev->base_addr; + u_short val; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4212, TRACE ); + printk("hp100: %s: start_xmit\n", dev->name); +#endif + + if ( skb==NULL ) + { +#ifndef LINUX_2_1 + dev_tint( dev ); +#endif + return 0; + } + + if ( skb->len <= 0 ) return 0; + + if ( lp->lan_type < 0 ) /* no LAN type detected yet? */ + { + hp100_stop_interface( dev ); + if ( ( lp->lan_type = hp100_sense_lan( dev ) ) < 0 ) + { + printk( "hp100: %s: no connection found - check wire\n", dev->name ); + hp100_start_interface( dev ); /* 10Mb/s RX packets maybe handled */ + return -EIO; + } + if ( lp->lan_type == HP100_LAN_100 ) + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); /* relogin */ + hp100_start_interface( dev ); + } + + /* If there is not enough free memory on the card... */ + i=hp100_inl(TX_MEM_FREE)&0x7fffffff; + if ( !(((i/2)-539)>(skb->len+16) && (hp100_inb(TX_PKT_CNT)<255)) ) + { +#ifdef HP100_DEBUG + printk( "hp100: %s: start_xmit: tx free mem = 0x%x\n", dev->name, i ); +#endif + /* not waited long enough since last failed tx try? */ + if ( jiffies - dev->trans_start < HZ ) + { +#ifdef HP100_DEBUG + printk("hp100: %s: trans_start timing problem\n", dev->name); +#endif + return -EAGAIN; + } + if ( lp->lan_type == HP100_LAN_100 && lp->hub_status < 0 ) + /* we have a 100Mb/s adapter but it isn't connected to hub */ + { + printk( "hp100: %s: login to 100Mb/s hub retry\n", dev->name ); + hp100_stop_interface( dev ); + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); + hp100_start_interface( dev ); + } + else + { + hp100_ints_off(); + i = hp100_sense_lan( dev ); + hp100_ints_on(); + if ( i == HP100_LAN_ERR ) + printk( "hp100: %s: link down detected\n", dev->name ); + else + if ( lp->lan_type != i ) /* cable change! */ + { + /* it's very hard - all network setting must be changed!!! */ + printk( "hp100: %s: cable change 10Mb/s <-> 100Mb/s detected\n", dev->name ); + lp->lan_type = i; + hp100_stop_interface( dev ); + if ( lp->lan_type == HP100_LAN_100 ) + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); + hp100_start_interface( dev ); + } + else + { + printk( "hp100: %s: interface reset\n", dev->name ); + hp100_stop_interface( dev ); + if ( lp->lan_type == HP100_LAN_100 ) + lp->hub_status = hp100_login_to_vg_hub( dev, FALSE ); + hp100_start_interface( dev ); + udelay(1000); + } + } + dev->trans_start = jiffies; + return -EAGAIN; + } + + for ( i=0; i<6000 && ( hp100_inb( OPTION_MSW ) & HP100_TX_CMD ); i++ ) + { +#ifdef HP100_DEBUG_TX + printk( "hp100: %s: start_xmit: busy\n", dev->name ); +#endif + } + + hp100_ints_off(); + val = hp100_inw( IRQ_STATUS ); + /* Ack / clear the interrupt TX_COMPLETE interrupt - this interrupt is set + * when the current packet being transmitted on the wire is completed. */ + hp100_outw( HP100_TX_COMPLETE, IRQ_STATUS ); +#ifdef HP100_DEBUG_TX + printk("hp100: %s: start_xmit: irq_status=0x%.4x, irqmask=0x%.4x, len=%d\n",dev->name,val,hp100_inw(IRQ_MASK),(int)skb->len ); +#endif + + ok_flag = skb->len >= HP100_MIN_PACKET_SIZE; + i = ok_flag ? skb->len : HP100_MIN_PACKET_SIZE; + + hp100_outw( i, DATA32 ); /* tell card the total packet length */ + hp100_outw( i, FRAGMENT_LEN ); /* and first/only fragment length */ + + if ( lp->mode==2 ) /* memory mapped */ + { + if ( lp->mem_ptr_virt ) /* high pci memory was remapped */ + { + /* Note: The J2585B needs alignment to 32bits here! */ + memcpy( lp->mem_ptr_virt, skb->data, ( skb->len + 3 ) & ~3 ); + if ( !ok_flag ) + memset( lp->mem_ptr_virt, 0, HP100_MIN_PACKET_SIZE - skb->len ); + } + else + { + /* Note: The J2585B needs alignment to 32bits here! */ + memcpy_toio( lp->mem_ptr_phys, skb->data, (skb->len + 3) & ~3 ); + if ( !ok_flag ) + memset_io( lp->mem_ptr_phys, 0, HP100_MIN_PACKET_SIZE - skb->len ); + } + } + else /* programmed i/o */ + { + outsl( ioaddr + HP100_REG_DATA32, skb->data, ( skb->len + 3 ) >> 2 ); + if ( !ok_flag ) + for ( i = ( skb->len + 3 ) & ~3; i < HP100_MIN_PACKET_SIZE; i += 4 ) + hp100_outl( 0, DATA32 ); + } + + hp100_outb( HP100_TX_CMD | HP100_SET_LB, OPTION_MSW ); /* send packet */ + + lp->stats.tx_packets++; +#ifdef LINUX_2_1 + lp->stats.tx_bytes += skb->len; +#endif + dev->trans_start=jiffies; + hp100_ints_on(); + +#ifdef LINUX_2_1 + dev_kfree_skb( skb ); +#else + dev_kfree_skb( skb, FREE_WRITE ); +#endif + +#ifdef HP100_DEBUG_TX + printk( "hp100: %s: start_xmit: end\n", dev->name ); +#endif + + return 0; +} + + +/* + * Receive Function (Non-Busmaster mode) + * Called when an "Receive Packet" interrupt occurs, i.e. the receive + * packet counter is non-zero. + * For non-busmaster, this function does the whole work of transfering + * the packet to the host memory and then up to higher layers via skb + * and netif_rx. + */ + +static void hp100_rx( struct device *dev ) +{ + int packets, pkt_len; + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + u_int header; + struct sk_buff *skb; + +#ifdef DEBUG_B + hp100_outw( 0x4213, TRACE ); + printk("hp100: %s: rx\n", dev->name); +#endif + + /* First get indication of received lan packet */ + /* RX_PKT_CND indicates the number of packets which have been fully */ + /* received onto the card but have not been fully transfered of the card */ + packets = hp100_inb( RX_PKT_CNT ); +#ifdef HP100_DEBUG_RX + if ( packets > 1 ) + printk( "hp100: %s: rx: waiting packets = %d\n", dev->name,packets ); +#endif + + while ( packets-- > 0 ) + { + /* If ADV_NXT_PKT is still set, we have to wait until the card has */ + /* really advanced to the next packet. */ + for (pkt_len=0; pkt_len<6000 &&(hp100_inb(OPTION_MSW)&HP100_ADV_NXT_PKT); + pkt_len++ ) + { +#ifdef HP100_DEBUG_RX + printk( "hp100: %s: rx: busy, remaining packets = %d\n", dev->name, packets ); +#endif + } + + /* First we get the header, which contains information about the */ + /* actual length of the received packet. */ + if( lp->mode==2 ) /* memory mapped mode */ + { + if ( lp->mem_ptr_virt ) /* if memory was remapped */ + header = *(__u32 *)lp->mem_ptr_virt; + else + header = readl( lp->mem_ptr_phys ); + } + else /* programmed i/o */ + header = hp100_inl( DATA32 ); + + pkt_len = ((header & HP100_PKT_LEN_MASK) + 3) & ~3; + +#ifdef HP100_DEBUG_RX + printk( "hp100: %s: rx: new packet - length=%d, errors=0x%x, dest=0x%x\n", + dev->name, + header & HP100_PKT_LEN_MASK, (header>>16)&0xfff8, + (header>>16)&7); +#endif + + /* Now we allocate the skb and transfer the data into it. */ + skb = dev_alloc_skb( pkt_len ); + if ( skb == NULL ) /* Not enough memory->drop packet */ + { +#ifdef HP100_DEBUG + printk( "hp100: %s: rx: couldn't allocate a sk_buff of size %d\n", dev->name, pkt_len ); +#endif + lp->stats.rx_dropped++; + } + else /* skb successfully allocated */ + { + u_char *ptr; + + skb->dev = dev; + + /* ptr to start of the sk_buff data area */ + ptr = (u_char *)skb_put( skb, pkt_len ); + + /* Now transfer the data from the card into that area */ + if ( lp->mode==2 ) + { + if ( lp->mem_ptr_virt ) + memcpy( ptr, lp->mem_ptr_virt, pkt_len ); + /* Note alignment to 32bit transfers */ + else + memcpy_fromio( ptr, lp->mem_ptr_phys, pkt_len ); + } + else /* io mapped */ + insl( ioaddr + HP100_REG_DATA32, ptr, pkt_len >> 2 ); + + skb->protocol = eth_type_trans( skb, dev ); + + netif_rx( skb ); + lp->stats.rx_packets++; +#ifdef LINUX_2_1 + lp->stats.rx_bytes += skb->len; +#endif + +#ifdef HP100_DEBUG_RX + printk( "hp100: %s: rx: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + dev->name, + ptr[ 0 ], ptr[ 1 ], ptr[ 2 ], ptr[ 3 ], ptr[ 4 ], ptr[ 5 ], + ptr[ 6 ], ptr[ 7 ], ptr[ 8 ], ptr[ 9 ], ptr[ 10 ], ptr[ 11 ] ); +#endif + } + + /* Indicate the card that we have got the packet */ + hp100_outb( HP100_ADV_NXT_PKT | HP100_SET_LB, OPTION_MSW ); + + switch ( header & 0x00070000 ) { + case (HP100_MULTI_ADDR_HASH<<16): + case (HP100_MULTI_ADDR_NO_HASH<<16): + lp->stats.multicast++; break; + } + } /* end of while(there are packets) loop */ +#ifdef HP100_DEBUG_RX + printk( "hp100_rx: %s: end\n", dev->name ); +#endif +} + + +/* + * Receive Function for Busmaster Mode + */ +static void hp100_rx_bm( struct device *dev ) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + hp100_ring_t *ptr; + u_int header; + int pkt_len; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4214, TRACE ); + printk("hp100: %s: rx_bm\n", dev->name); +#endif + +#ifdef HP100_DEBUG + if(0==lp->rxrcommit) + { + printk("hp100: %s: rx_bm called although no PDLs were committed to adapter?\n", dev->name); + return; + } + else + + /* RX_PKT_CNT states how many PDLs are currently formatted and available to + * the cards BM engine */ + if( (hp100_inw(RX_PKT_CNT)&0x00ff) >= lp->rxrcommit) + { + printk("hp100: %s: More packets received than commited? RX_PKT_CNT=0x%x, commit=0x%x\n", dev->name, hp100_inw(RX_PKT_CNT)&0x00ff, lp->rxrcommit); + return; + } +#endif + + while( (lp->rxrcommit > hp100_inb(RX_PDL)) ) + { + /* + * The packet was received into the pdl pointed to by lp->rxrhead ( + * the oldest pdl in the ring + */ + + /* First we get the header, which contains information about the */ + /* actual length of the received packet. */ + + ptr=lp->rxrhead; + + header = *(ptr->pdl-1); + pkt_len = (header & HP100_PKT_LEN_MASK); + +#ifdef HP100_DEBUG_BM + printk( "hp100: %s: rx_bm: header@0x%x=0x%x length=%d, errors=0x%x, dest=0x%x\n", + dev->name, + (u_int) (ptr->pdl-1),(u_int) header, + pkt_len, + (header>>16)&0xfff8, + (header>>16)&7); + printk( "hp100: %s: RX_PDL_COUNT:0x%x TX_PDL_COUNT:0x%x, RX_PKT_CNT=0x%x PDH=0x%x, Data@0x%x len=0x%x\n", + dev->name, + hp100_inb( RX_PDL ), + hp100_inb( TX_PDL ), + hp100_inb( RX_PKT_CNT ), + (u_int) *(ptr->pdl), + (u_int) *(ptr->pdl+3), + (u_int) *(ptr->pdl+4)); +#endif + + if( (pkt_len>=MIN_ETHER_SIZE) && + (pkt_len<=MAX_ETHER_SIZE) ) + { + if(ptr->skb==NULL) + { + printk("hp100: %s: rx_bm: skb null\n", dev->name); + /* can happen if we only allocated room for the pdh due to memory shortage. */ + lp->stats.rx_dropped++; + } + else + { + skb_trim( ptr->skb, pkt_len ); /* Shorten it */ + ptr->skb->protocol = eth_type_trans( ptr->skb, dev ); + + netif_rx( ptr->skb ); /* Up and away... */ + + lp->stats.rx_packets++; +#ifdef LINUX_2_1 + lp->stats.rx_bytes += ptr->skb->len; +#endif + } + + switch ( header & 0x00070000 ) { + case (HP100_MULTI_ADDR_HASH<<16): + case (HP100_MULTI_ADDR_NO_HASH<<16): + lp->stats.multicast++; break; + } + } + else + { +#ifdef HP100_DEBUG + printk("hp100: %s: rx_bm: Received bad packet (length=%d)\n",dev->name,pkt_len); +#endif + if(ptr->skb!=NULL) +#ifdef LINUX_2_1 + dev_kfree_skb( ptr->skb ); +#else + dev_kfree_skb( ptr->skb, FREE_READ ); +#endif + lp->stats.rx_errors++; + } + + lp->rxrhead=lp->rxrhead->next; + + /* Allocate a new rx PDL (so lp->rxrcommit stays the same) */ + if (0 == hp100_build_rx_pdl( lp->rxrtail, dev )) + { + /* No space for skb, header can still be received. */ +#ifdef HP100_DEBUG + printk("hp100: %s: rx_bm: No space for new PDL.\n", dev->name); +#endif + return; + } + else + { /* successfully allocated new PDL - put it in ringlist at tail. */ + hp100_outl((u32)lp->rxrtail->pdl_paddr, RX_PDA); + lp->rxrtail=lp->rxrtail->next; + } + + } +} + + + +/* + * statistics + */ +static hp100_stats_t *hp100_get_stats( struct device *dev ) +{ + int ioaddr = dev->base_addr; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4215, TRACE ); +#endif + + hp100_ints_off(); + hp100_update_stats( dev ); + hp100_ints_on(); + return &((struct hp100_private *)dev->priv)->stats; +} + +static void hp100_update_stats( struct device *dev ) +{ + int ioaddr = dev->base_addr; + u_short val; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4216, TRACE ); + printk("hp100: %s: update-stats\n", dev->name); +#endif + + /* Note: Statistics counters clear when read. */ + hp100_page( MAC_CTRL ); + val = hp100_inw( DROPPED ) & 0x0fff; + lp->stats.rx_errors += val; + lp->stats.rx_over_errors += val; + val = hp100_inb( CRC ); + lp->stats.rx_errors += val; + lp->stats.rx_crc_errors += val; + val = hp100_inb( ABORT ); + lp->stats.tx_errors += val; + lp->stats.tx_aborted_errors += val; + hp100_page( PERFORMANCE ); +} + +static void hp100_misc_interrupt( struct device *dev ) +{ + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4216, TRACE ); + printk("hp100: %s: misc_interrupt\n", dev->name); +#endif + + /* Note: Statistics counters clear when read. */ + lp->stats.rx_errors++; + lp->stats.tx_errors++; +} + +static void hp100_clear_stats( int ioaddr ) +{ + unsigned long flags; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4217, TRACE ); + printk("hp100: %s: clear_stats\n", dev->name); +#endif + + save_flags( flags ); + cli(); + hp100_page( MAC_CTRL ); /* get all statistics bytes */ + hp100_inw( DROPPED ); + hp100_inb( CRC ); + hp100_inb( ABORT ); + hp100_page( PERFORMANCE ); + restore_flags( flags ); +} + + +/* + * multicast setup + */ + +/* + * Set or clear the multicast filter for this adapter. + */ + +static void hp100_set_multicast_list( struct device *dev ) +{ + unsigned long flags; + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4218, TRACE ); + printk("hp100: %s: set_mc_list\n", dev->name); +#endif + + save_flags( flags ); + cli(); + hp100_ints_off(); + hp100_page( MAC_CTRL ); + hp100_andb( ~(HP100_RX_EN | HP100_TX_EN), MAC_CFG_1 ); /* stop rx/tx */ + + if ( dev->flags & IFF_PROMISC ) + { + lp->mac2_mode = HP100_MAC2MODE6; /* promiscuous mode = get all good */ + lp->mac1_mode = HP100_MAC1MODE6; /* packets on the net */ + memset( &lp->hash_bytes, 0xff, 8 ); + } + else if ( dev->mc_count || (dev->flags&IFF_ALLMULTI) ) + { + lp->mac2_mode = HP100_MAC2MODE5; /* multicast mode = get packets for */ + lp->mac1_mode = HP100_MAC1MODE5; /* me, broadcasts and all multicasts */ +#ifdef HP100_MULTICAST_FILTER /* doesn't work!!! */ + if ( dev -> flags & IFF_ALLMULTI ) + { + /* set hash filter to receive all multicast packets */ + memset( &lp->hash_bytes, 0xff, 8 ); + } + else + { + int i, j, idx; + u_char *addrs; + struct dev_mc_list *dmi; + + memset( &lp->hash_bytes, 0x00, 8 ); +#ifdef HP100_DEBUG + printk("hp100: %s: computing hash filter - mc_count = %i\n", dev -> name, dev -> mc_count ); +#endif + for ( i = 0, dmi = dev -> mc_list; i < dev -> mc_count; i++, dmi = dmi -> next ) + { + addrs = dmi -> dmi_addr; + if ( ( *addrs & 0x01 ) == 0x01 ) /* multicast address? */ + { +#ifdef HP100_DEBUG + printk("hp100: %s: multicast = %02x:%02x:%02x:%02x:%02x:%02x, ", + dev -> name, + addrs[ 0 ], addrs[ 1 ], addrs[ 2 ], + addrs[ 3 ], addrs[ 4 ], addrs[ 5 ] ); +#endif + for ( j = idx = 0; j < 6; j++ ) + { + idx ^= *addrs++ & 0x3f; + printk( ":%02x:", idx ); + } +#ifdef HP100_DEBUG + printk("idx = %i\n", idx ); +#endif + lp->hash_bytes[ idx >> 3 ] |= ( 1 << ( idx & 7 ) ); + } + } + } +#else + memset( &lp->hash_bytes, 0xff, 8 ); +#endif + } + else + { + lp->mac2_mode = HP100_MAC2MODE3; /* normal mode = get packets for me */ + lp->mac1_mode = HP100_MAC1MODE3; /* and broadcasts */ + memset( &lp->hash_bytes, 0x00, 8 ); + } + + if ( ( (hp100_inb(MAC_CFG_1) & 0x0f)!=lp->mac1_mode ) || + ( hp100_inb(MAC_CFG_2)!=lp->mac2_mode ) ) + { + int i; + + hp100_outb( lp->mac2_mode, MAC_CFG_2 ); + hp100_andb( HP100_MAC1MODEMASK, MAC_CFG_1 ); /* clear mac1 mode bits */ + hp100_orb( lp->mac1_mode, MAC_CFG_1 ); /* and set the new mode */ + + hp100_page( MAC_ADDRESS ); + for ( i = 0; i < 8; i++ ) + hp100_outb( lp->hash_bytes[ i ], HASH_BYTE0 + i ); +#ifdef HP100_DEBUG + printk("hp100: %s: mac1 = 0x%x, mac2 = 0x%x, multicast hash = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, lp->mac1_mode, lp->mac2_mode, + lp->hash_bytes[ 0 ], lp->hash_bytes[ 1 ], + lp->hash_bytes[ 2 ], lp->hash_bytes[ 3 ], + lp->hash_bytes[ 4 ], lp->hash_bytes[ 5 ], + lp->hash_bytes[ 6 ], lp->hash_bytes[ 7 ] + ); +#endif + + if(lp->lan_type==HP100_LAN_100) + { +#ifdef HP100_DEBUG + printk("hp100: %s: 100VG MAC settings have changed - relogin.\n", dev->name); +#endif + lp->hub_status=hp100_login_to_vg_hub( dev, TRUE ); /* force a relogin to the hub */ + } + } + else + { + int i; + u_char old_hash_bytes[ 8 ]; + + hp100_page( MAC_ADDRESS ); + for ( i = 0; i < 8; i++ ) + old_hash_bytes[ i ] = hp100_inb( HASH_BYTE0 + i ); + if ( memcmp( old_hash_bytes, &lp->hash_bytes, 8 ) ) + { + for ( i = 0; i < 8; i++ ) + hp100_outb( lp->hash_bytes[ i ], HASH_BYTE0 + i ); +#ifdef HP100_DEBUG + printk("hp100: %s: multicast hash = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, + lp->hash_bytes[ 0 ], lp->hash_bytes[ 1 ], + lp->hash_bytes[ 2 ], lp->hash_bytes[ 3 ], + lp->hash_bytes[ 4 ], lp->hash_bytes[ 5 ], + lp->hash_bytes[ 6 ], lp->hash_bytes[ 7 ] + ); +#endif + + if(lp->lan_type==HP100_LAN_100) + { +#ifdef HP100_DEBUG + printk("hp100: %s: 100VG MAC settings have changed - relogin.\n", dev->name); +#endif + lp->hub_status=hp100_login_to_vg_hub( dev, TRUE ); /* force a relogin to the hub */ + } + } + } + + hp100_page( MAC_CTRL ); + hp100_orb( HP100_RX_EN | HP100_RX_IDLE | /* enable rx */ + HP100_TX_EN | HP100_TX_IDLE, MAC_CFG_1 ); /* enable tx */ + + hp100_page( PERFORMANCE ); + hp100_ints_on(); + restore_flags( flags ); +} + + +/* + * hardware interrupt handling + */ + +static void hp100_interrupt( int irq, void *dev_id, struct pt_regs *regs ) +{ + struct device *dev = (struct device *)dev_id; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + + int ioaddr; + u_int val; + + if ( dev == NULL ) return; + ioaddr = dev->base_addr; + + if ( dev->interrupt ) + printk( "hp100: %s: re-entering the interrupt handler\n", dev->name ); + hp100_ints_off(); + dev->interrupt = 1; /* mark that we are inside the handler */ + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4219, TRACE ); +#endif + + /* hp100_page( PERFORMANCE ); */ + val = hp100_inw( IRQ_STATUS ); +#ifdef HP100_DEBUG_IRQ + printk( "hp100: %s: mode=%x,IRQ_STAT=0x%.4x,RXPKTCNT=0x%.2x RXPDL=0x%.2x TXPKTCNT=0x%.2x TXPDL=0x%.2x\n", + dev->name, + lp->mode, + (u_int)val, + hp100_inb( RX_PKT_CNT ), + hp100_inb( RX_PDL ), + hp100_inb( TX_PKT_CNT ), + hp100_inb( TX_PDL ) + ); +#endif + + if(val==0) /* might be a shared interrupt */ + { + dev->interrupt=0; + hp100_ints_on(); + return; + } + /* We're only interested in those interrupts we really enabled. */ + /* val &= hp100_inw( IRQ_MASK ); */ + + /* + * RX_PDL_FILL_COMPL is set whenever a RX_PDL has been executed. A RX_PDL + * is considered executed whenever the RX_PDL data structure is no longer + * needed. + */ + if ( val & HP100_RX_PDL_FILL_COMPL ) + { + if(lp->mode==1) + hp100_rx_bm( dev ); + else + { + printk("hp100: %s: rx_pdl_fill_compl interrupt although not busmaster?\n", dev->name); + } + } + + /* + * The RX_PACKET interrupt is set, when the receive packet counter is + * non zero. We use this interrupt for receiving in slave mode. In + * busmaster mode, we use it to make sure we did not miss any rx_pdl_fill + * interrupts. If rx_pdl_fill_compl is not set and rx_packet is set, then + * we somehow have missed a rx_pdl_fill_compl interrupt. + */ + + if ( val & HP100_RX_PACKET ) /* Receive Packet Counter is non zero */ + { + if(lp->mode!=1) /* non busmaster */ + hp100_rx( dev ); + else if ( !(val & HP100_RX_PDL_FILL_COMPL )) + { + /* Shouldnt happen - maybe we missed a RX_PDL_FILL Interrupt? */ + hp100_rx_bm( dev ); + } + } + + /* + * Ack. that we have noticed the interrupt and thereby allow next one. + * Note that this is now done after the slave rx function, since first + * acknowledging and then setting ADV_NXT_PKT caused an extra interrupt + * on the J2573. + */ + hp100_outw( val, IRQ_STATUS ); + + /* + * RX_ERROR is set when a packet is dropped due to no memory resources on + * the card or when a RCV_ERR occurs. + * TX_ERROR is set when a TX_ABORT condition occurs in the MAC->exists + * only in the 802.3 MAC and happens when 16 collisions occur during a TX + */ + if ( val & ( HP100_TX_ERROR | HP100_RX_ERROR ) ) + { +#ifdef HP100_DEBUG_IRQ + printk("hp100: %s: TX/RX Error IRQ\n", dev->name); +#endif + hp100_update_stats( dev ); + if(lp->mode==1) + { + hp100_rxfill( dev ); + hp100_clean_txring( dev ); + } + } + + /* + * RX_PDA_ZERO is set when the PDA count goes from non-zero to zero. + */ + if ( (lp->mode==1)&&(val &(HP100_RX_PDA_ZERO)) ) + hp100_rxfill( dev ); + + /* + * HP100_TX_COMPLETE interrupt occurs when packet transmitted on wire + * is completed + */ + if ( (lp->mode==1) && ( val & ( HP100_TX_COMPLETE )) ) + hp100_clean_txring( dev ); + + /* + * MISC_ERROR is set when either the LAN link goes down or a detected + * bus error occurs. + */ + if ( val & HP100_MISC_ERROR ) /* New for J2585B */ + { +#ifdef HP100_DEBUG_IRQ + printk("hp100: %s: Misc. Error Interrupt - Check cabling.\n", dev->name); +#endif + if(lp->mode==1) + { + hp100_clean_txring( dev ); + hp100_rxfill( dev ); + } + hp100_misc_interrupt( dev ); + } + + dev->interrupt = 0; + hp100_ints_on(); +} + + +/* + * some misc functions + */ + +static void hp100_start_interface( struct device *dev ) +{ + unsigned long flags; + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4220, TRACE ); + printk("hp100: %s: hp100_start_interface\n",dev->name); +#endif + + save_flags( flags ); + cli(); + + /* Ensure the adapter does not want to request an interrupt when */ + /* enabling the IRQ line to be active on the bus (i.e. not tri-stated) */ + hp100_page( PERFORMANCE ); + hp100_outw( 0xfefe, IRQ_MASK ); /* mask off all ints */ + hp100_outw( 0xffff, IRQ_STATUS ); /* ack all IRQs */ + hp100_outw( HP100_FAKE_INT|HP100_INT_EN|HP100_RESET_LB, OPTION_LSW); + /* Un Tri-state int. TODO: Check if shared interrupts can be realised? */ + hp100_outw( HP100_TRI_INT | HP100_RESET_HB, OPTION_LSW ); + + if(lp->mode==1) + { + /* Make sure BM bit is set... */ + hp100_page(HW_MAP); + hp100_orb( HP100_BM_MASTER, BM ); + hp100_rxfill( dev ); + } + else if(lp->mode==2) + { + /* Enable memory mapping. Note: Don't do this when busmaster. */ + hp100_outw( HP100_MMAP_DIS | HP100_RESET_HB, OPTION_LSW ); + } + + hp100_page(PERFORMANCE); + hp100_outw( 0xfefe, IRQ_MASK ); /* mask off all ints */ + hp100_outw( 0xffff, IRQ_STATUS ); /* ack IRQ */ + + /* enable a few interrupts: */ + if(lp->mode==1) /* busmaster mode */ + { + hp100_outw( HP100_RX_PDL_FILL_COMPL | + HP100_RX_PDA_ZERO | + HP100_RX_ERROR | + /* HP100_RX_PACKET | */ + /* HP100_RX_EARLY_INT | */ HP100_SET_HB | + /* HP100_TX_PDA_ZERO | */ + HP100_TX_COMPLETE | + /* HP100_MISC_ERROR | */ + HP100_TX_ERROR | HP100_SET_LB, IRQ_MASK ); + } + else + { + hp100_outw( HP100_RX_PACKET | + HP100_RX_ERROR | HP100_SET_HB | + HP100_TX_ERROR | HP100_SET_LB , IRQ_MASK ); + } + + /* Enable MAC Tx and RX, set MAC modes, ... */ + hp100_set_multicast_list( dev ); + + restore_flags( flags ); +} + + +static void hp100_stop_interface( struct device *dev ) +{ + struct hp100_private *lp = (struct hp100_private *)dev->priv; + int ioaddr = dev->base_addr; + u_int val; + +#ifdef HP100_DEBUG_B + printk("hp100: %s: hp100_stop_interface\n",dev->name); + hp100_outw( 0x4221, TRACE ); +#endif + + if (lp->mode==1) + hp100_BM_shutdown( dev ); + else + { + /* Note: MMAP_DIS will be reenabled by start_interface */ + hp100_outw( HP100_INT_EN | HP100_RESET_LB | + HP100_TRI_INT | HP100_MMAP_DIS | HP100_SET_HB, OPTION_LSW ); + val = hp100_inw( OPTION_LSW ); + + hp100_page( MAC_CTRL ); + hp100_andb( ~(HP100_RX_EN | HP100_TX_EN), MAC_CFG_1 ); + + if ( !(val & HP100_HW_RST) ) return; /* If reset, imm. return ... */ + /* ... else: busy wait until idle */ + for ( val = 0; val < 6000; val++ ) + if ( ( hp100_inb( MAC_CFG_1 ) & (HP100_TX_IDLE | HP100_RX_IDLE) ) == + (HP100_TX_IDLE | HP100_RX_IDLE) ) + { + hp100_page(PERFORMANCE); + return; + } + printk( "hp100: %s: hp100_stop_interface - timeout\n", dev->name ); + hp100_page(PERFORMANCE); + } +} + + +static void hp100_load_eeprom( struct device *dev, u_short probe_ioaddr ) +{ + int i; + int ioaddr = probe_ioaddr > 0 ? probe_ioaddr : dev->base_addr; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4222, TRACE ); +#endif + + hp100_page( EEPROM_CTRL ); + hp100_andw( ~HP100_EEPROM_LOAD, EEPROM_CTRL ); + hp100_orw( HP100_EEPROM_LOAD, EEPROM_CTRL ); + for ( i = 0; i < 10000; i++ ) + if ( !( hp100_inb( OPTION_MSW ) & HP100_EE_LOAD ) ) return; + printk( "hp100: %s: hp100_load_eeprom - timeout\n", dev->name ); +} + + +/* Sense connection status. + * return values: LAN_10 - Connected to 10Mbit/s network + * LAN_100 - Connected to 100Mbit/s network + * LAN_ERR - not connected or 100Mbit/s Hub down + */ +static int hp100_sense_lan( struct device *dev ) +{ + int ioaddr = dev->base_addr; + u_short val_VG, val_10; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4223, TRACE ); +#endif + + hp100_page( MAC_CTRL ); + val_10 = hp100_inb( 10_LAN_CFG_1 ); + val_VG = hp100_inb( VG_LAN_CFG_1 ); + hp100_page( PERFORMANCE ); +#ifdef HP100_DEBUG + printk( "hp100: %s: sense_lan: val_VG = 0x%04x, val_10 = 0x%04x\n", dev->name, val_VG, val_10 ); +#endif + + if ( val_10 & HP100_LINK_BEAT_ST ) /* 10Mb connection is active */ + return HP100_LAN_10; + + if ( val_10 & HP100_AUI_ST ) /* have we BNC or AUI onboard? */ + { + val_10 |= HP100_AUI_SEL | HP100_LOW_TH; + hp100_page( MAC_CTRL ); + hp100_outb( val_10, 10_LAN_CFG_1 ); + hp100_page( PERFORMANCE ); + return HP100_LAN_10; + } + + if ( (lp->id->id == 0x02019F022) || + (lp->id->id == 0x01042103c) || + (lp->id->id == 0x01040103c) ) + return HP100_LAN_ERR; /* Those cards don't have a 100 Mbit connector */ + + if ( val_VG & HP100_LINK_CABLE_ST ) /* Can hear the HUBs tone. */ + return HP100_LAN_100; + return HP100_LAN_ERR; +} + + + +static int hp100_down_vg_link( struct device *dev ) +{ + struct hp100_private *lp = (struct hp100_private *)dev->priv; + int ioaddr = dev->base_addr; + unsigned long time; + long savelan, newlan; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4224, TRACE ); + printk("hp100: %s: down_vg_link\n", dev->name); +#endif + + hp100_page( MAC_CTRL ); + time=jiffies+(HZ/4); + do{ + if ( hp100_inb( VG_LAN_CFG_1 ) & HP100_LINK_CABLE_ST ) break; + } while (time>jiffies); + + if ( jiffies >= time ) /* no signal->no logout */ + return 0; + + /* Drop the VG Link by clearing the link up cmd and load addr.*/ + + hp100_andb( ~( HP100_LOAD_ADDR| HP100_LINK_CMD), VG_LAN_CFG_1); + hp100_orb( HP100_VG_SEL, VG_LAN_CFG_1); + + /* Conditionally stall for >250ms on Link-Up Status (to go down) */ + time=jiffies+(HZ/2); + do{ + if ( !(hp100_inb( VG_LAN_CFG_1) & HP100_LINK_UP_ST) ) break; + } while(time>jiffies); + +#ifdef HP100_DEBUG + if (jiffies>=time) + printk("hp100: %s: down_vg_link: Link does not go down?\n", dev->name); +#endif + + /* To prevent condition where Rev 1 VG MAC and old hubs do not complete */ + /* logout under traffic (even though all the status bits are cleared), */ + /* do this workaround to get the Rev 1 MAC in its idle state */ + if ( lp->chip==HP100_CHIPID_LASSEN ) + { + /* Reset VG MAC to insure it leaves the logoff state even if */ + /* the Hub is still emitting tones */ + hp100_andb(~HP100_VG_RESET, VG_LAN_CFG_1); + udelay(1500); /* wait for >1ms */ + hp100_orb(HP100_VG_RESET, VG_LAN_CFG_1); /* Release Reset */ + udelay(1500); + } + + /* New: For lassen, switch to 10 Mbps mac briefly to clear training ACK */ + /* to get the VG mac to full reset. This is not req.d with later chips */ + /* Note: It will take the between 1 and 2 seconds for the VG mac to be */ + /* selected again! This will be left to the connect hub function to */ + /* perform if desired. */ + if (lp->chip==HP100_CHIPID_LASSEN) + { + /* Have to write to 10 and 100VG control registers simultaneously */ + savelan=newlan=hp100_inl(10_LAN_CFG_1); /* read 10+100 LAN_CFG regs */ + newlan &= ~(HP100_VG_SEL<<16); + newlan |= (HP100_DOT3_MAC)<<8; + hp100_andb( ~HP100_AUTO_MODE, MAC_CFG_3); /* Autosel off */ + hp100_outl(newlan, 10_LAN_CFG_1); + + /* Conditionally stall for 5sec on VG selected. */ + time=jiffies+(HZ*5); + do{ + if( !(hp100_inb(MAC_CFG_4) & HP100_MAC_SEL_ST) ) break; + } while(time>jiffies); + + hp100_orb( HP100_AUTO_MODE, MAC_CFG_3); /* Autosel back on */ + hp100_outl(savelan, 10_LAN_CFG_1); + } + + time=jiffies+(3*HZ); /* Timeout 3s */ + do { + if ( (hp100_inb( VG_LAN_CFG_1 )&HP100_LINK_CABLE_ST) == 0) break; + } while (time>jiffies); + + if(time<=jiffies) + { +#ifdef HP100_DEBUG + printk( "hp100: %s: down_vg_link: timeout\n", dev->name ); +#endif + return -EIO; + } + + time=jiffies+(2*HZ); /* This seems to take a while.... */ + do {} while (time>jiffies); + + return 0; +} + + +static int hp100_login_to_vg_hub( struct device *dev, u_short force_relogin ) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + u_short val=0; + unsigned long time; + int startst; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4225, TRACE ); + printk("hp100: %s: login_to_vg_hub\n", dev->name); +#endif + + /* Initiate a login sequence iff VG MAC is enabled and either Load Address + * bit is zero or the force relogin flag is set (e.g. due to MAC address or + * promiscuous mode change) + */ + hp100_page( MAC_CTRL ); + startst=hp100_inb( VG_LAN_CFG_1 ); + if((force_relogin==TRUE)||(hp100_inb( MAC_CFG_4 )&HP100_MAC_SEL_ST)) + { +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: Start training\n", dev->name); +#endif + + /* Ensure VG Reset bit is 1 (i.e., do not reset)*/ + hp100_orb( HP100_VG_RESET , VG_LAN_CFG_1 ); + + /* If Lassen AND auto-select-mode AND VG tones were sensed on */ + /* entry then temporarily put them into force 100Mbit mode */ + if((lp->chip==HP100_CHIPID_LASSEN)&&( startst & HP100_LINK_CABLE_ST ) ) + hp100_andb( ~HP100_DOT3_MAC, 10_LAN_CFG_2 ); + + /* Drop the VG link by zeroing Link Up Command and Load Address */ + hp100_andb( ~(HP100_LINK_CMD/* |HP100_LOAD_ADDR */), VG_LAN_CFG_1); + +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: Bring down the link\n", dev->name); +#endif + + /* Wait for link to drop */ + time = jiffies + (HZ/10); + do { + if (~(hp100_inb( VG_LAN_CFG_1 )& HP100_LINK_UP_ST) ) break; + } while (time>jiffies); + + /* Start an addressed training and optionally request promiscuous port */ + if ( (dev->flags) & IFF_PROMISC ) + { + hp100_orb( HP100_PROM_MODE, VG_LAN_CFG_2); + if(lp->chip==HP100_CHIPID_LASSEN) + hp100_orw( HP100_MACRQ_PROMSC, TRAIN_REQUEST ); + } + else + { + hp100_andb( ~HP100_PROM_MODE, VG_LAN_CFG_2); + /* For ETR parts we need to reset the prom. bit in the training + * register, otherwise promiscious mode won't be disabled. + */ + if(lp->chip==HP100_CHIPID_LASSEN) + { + hp100_andw( ~HP100_MACRQ_PROMSC, TRAIN_REQUEST ); + } + } + + /* With ETR parts, frame format request bits can be set. */ + if(lp->chip==HP100_CHIPID_LASSEN) + hp100_orb( HP100_MACRQ_FRAMEFMT_EITHER, TRAIN_REQUEST); + + hp100_orb( HP100_LINK_CMD|HP100_LOAD_ADDR|HP100_VG_RESET, VG_LAN_CFG_1); + + /* Note: Next wait could be omitted for Hood and earlier chips under */ + /* certain circumstances */ + /* TODO: check if hood/earlier and skip wait. */ + + /* Wait for either short timeout for VG tones or long for login */ + /* Wait for the card hardware to signalise link cable status ok... */ + hp100_page( MAC_CTRL ); + time = jiffies + ( 1*HZ ); /* 1 sec timeout for cable st */ + do { + if ( hp100_inb( VG_LAN_CFG_1 ) & HP100_LINK_CABLE_ST ) break; + } while ( jiffies < time ); + + if ( jiffies >= time ) + { +#ifdef HP100_DEBUG_TRAINING + printk( "hp100: %s: Link cable status not ok? Training aborted.\n", dev->name ); +#endif + } + else + { +#ifdef HP100_DEBUG_TRAINING + printk( "hp100: %s: HUB tones detected. Trying to train.\n", dev->name); +#endif + + time = jiffies + ( 2*HZ ); /* again a timeout */ + do { + val = hp100_inb( VG_LAN_CFG_1 ); + if ( (val & ( HP100_LINK_UP_ST )) ) + { +#ifdef HP100_DEBUG_TRAINING + printk( "hp100: %s: Passed training.\n", dev->name); +#endif + break; + } + } while ( time > jiffies ); + } + + /* If LINK_UP_ST is set, then we are logged into the hub. */ + if ( (jiffies<=time) && (val & HP100_LINK_UP_ST) ) + { +#ifdef HP100_DEBUG_TRAINING + printk( "hp100: %s: Successfully logged into the HUB.\n", dev->name); + if(lp->chip==HP100_CHIPID_LASSEN) + { + val = hp100_inw(TRAIN_ALLOW); + printk( "hp100: %s: Card supports 100VG MAC Version \"%s\" ", + dev->name,(hp100_inw(TRAIN_REQUEST)&HP100_CARD_MACVER) ? "802.12" : "Pre"); + printk( "Driver will use MAC Version \"%s\"\n", + ( val & HP100_HUB_MACVER) ? "802.12" : "Pre" ); + printk( "hp100: %s: Frame format is %s.\n",dev->name,(val&HP100_MALLOW_FRAMEFMT)?"802.5":"802.3"); + } +#endif + } + else + { + /* If LINK_UP_ST is not set, login was not successful */ + printk("hp100: %s: Problem logging into the HUB.\n",dev->name); + if(lp->chip==HP100_CHIPID_LASSEN) + { + /* Check allowed Register to find out why there is a problem. */ + val = hp100_inw( TRAIN_ALLOW ); /* wont work on non-ETR card */ +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: MAC Configuration requested: 0x%04x, HUB allowed: 0x%04x\n", dev->name, hp100_inw(TRAIN_REQUEST), val); +#endif + if ( val & HP100_MALLOW_ACCDENIED ) + printk("hp100: %s: HUB access denied.\n", dev->name); + if ( val & HP100_MALLOW_CONFIGURE ) + printk("hp100: %s: MAC Configuration is incompatible with the Network.\n", dev->name); + if ( val & HP100_MALLOW_DUPADDR ) + printk("hp100: %s: Duplicate MAC Address on the Network.\n", dev->name); + } + } + + /* If we have put the chip into forced 100 Mbit mode earlier, go back */ + /* to auto-select mode */ + + if( (lp->chip==HP100_CHIPID_LASSEN)&&(startst & HP100_LINK_CABLE_ST) ) + { + hp100_page( MAC_CTRL ); + hp100_orb( HP100_DOT3_MAC, 10_LAN_CFG_2 ); + } + + val=hp100_inb(VG_LAN_CFG_1); + + /* Clear the MISC_ERROR Interrupt, which might be generated when doing the relogin */ + hp100_page(PERFORMANCE); + hp100_outw( HP100_MISC_ERROR, IRQ_STATUS); + + if (val&HP100_LINK_UP_ST) + return(0); /* login was ok */ + else + { + printk("hp100: %s: Training failed.\n", dev->name); + hp100_down_vg_link( dev ); + return -EIO; + } + } + /* no forced relogin & already link there->no training. */ + return -EIO; +} + + +static void hp100_cascade_reset( struct device *dev, u_short enable ) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = (struct hp100_private *)dev->priv; + int i; + +#ifdef HP100_DEBUG_B + hp100_outw( 0x4226, TRACE ); + printk("hp100: %s: cascade_reset\n", dev->name); +#endif + + if (enable==TRUE) + { + hp100_outw( HP100_HW_RST | HP100_RESET_LB, OPTION_LSW ); + if(lp->chip==HP100_CHIPID_LASSEN) + { + /* Lassen requires a PCI transmit fifo reset */ + hp100_page( HW_MAP ); + hp100_andb( ~HP100_PCI_RESET, PCICTRL2 ); + hp100_orb( HP100_PCI_RESET, PCICTRL2 ); + /* Wait for min. 300 ns */ + /* we cant use jiffies here, because it may be */ + /* that we have disabled the timer... */ + for (i=0; i<0xffff; i++); + hp100_andb( ~HP100_PCI_RESET, PCICTRL2 ); + hp100_page( PERFORMANCE ); + } + } + else + { /* bring out of reset */ + hp100_outw(HP100_HW_RST|HP100_SET_LB, OPTION_LSW); + for (i=0; i<0xffff; i++ ); + hp100_page(PERFORMANCE); + } +} + +#ifdef HP100_DEBUG +void hp100_RegisterDump( struct device *dev ) +{ + int ioaddr=dev->base_addr; + int Page; + int Register; + + /* Dump common registers */ + printk("hp100: %s: Cascade Register Dump\n", dev->name); + printk("hardware id #1: 0x%.2x\n",hp100_inb(HW_ID)); + printk("hardware id #2/paging: 0x%.2x\n",hp100_inb(PAGING)); + printk("option #1: 0x%.4x\n",hp100_inw(OPTION_LSW)); + printk("option #2: 0x%.4x\n",hp100_inw(OPTION_MSW)); + + /* Dump paged registers */ + for (Page = 0; Page < 8; Page++) + { + /* Dump registers */ + printk("page: 0x%.2x\n",Page); + outw( Page, ioaddr+0x02); + for (Register = 0x8; Register < 0x22; Register += 2) + { + /* Display Register contents except data port */ + if (((Register != 0x10) && (Register != 0x12)) || (Page > 0)) + { + printk("0x%.2x = 0x%.4x\n",Register,inw(ioaddr+Register)); + } + } + } + hp100_page(PERFORMANCE); +} +#endif + + + +/* + * module section + */ + +#ifdef MODULE + +/* Parameters set by insmod */ +int hp100_port[5] = { 0, -1, -1, -1, -1 }; +#ifdef LINUX_2_1 +MODULE_PARM(hp100_port, "1-5i"); +#endif + +#ifdef LINUX_2_1 +char hp100_name[5][IFNAMSIZ] = { "", "", "", "", "" }; +MODULE_PARM(hp100_name, "1-5c" __MODULE_STRING(IFNAMSIZ)); +#else +static char devname[5][IFNAMSIZ] = { "", "", "", "", "" }; +static char *hp100_name[5] = { devname[0], devname[1], + devname[2], devname[3], + devname[4] }; +#endif + +/* List of devices */ +static struct device *hp100_devlist[5] = { NULL, NULL, NULL, NULL, NULL }; + +/* + * Note: if you have more than five 100vg cards in your pc, feel free to + * increase this value + */ + +/* + * Note: to register three eisa or pci devices, use: + * option hp100 hp100_port=0,0,0 + * to register one card at io 0x280 as eth239, use: + * option hp100 hp100_port=0x280 hp100_name=eth239 + */ + +int init_module( void ) +{ + int i, cards; + + if (hp100_port == 0 && !EISA_bus && !pcibios_present()) + printk("hp100: You should not use auto-probing with insmod!\n"); + + /* Loop on all possible base addresses */ + i = -1; cards = 0; + while((hp100_port[++i] != -1) && (i < 5)) + { + /* Create device and set basics args */ + hp100_devlist[i] = kmalloc(sizeof(struct device), GFP_KERNEL); + memset(hp100_devlist[i], 0x00, sizeof(struct device)); + hp100_devlist[i]->name = hp100_name[i]; + hp100_devlist[i]->base_addr = hp100_port[i]; + hp100_devlist[i]->init = &hp100_probe; + + /* Try to create the device */ + if(register_netdev(hp100_devlist[i]) != 0) + { + /* DeAllocate everything */ + /* Note: if dev->priv is mallocated, there is no way to fail */ + kfree_s(hp100_devlist[i], sizeof(struct device)); + hp100_devlist[i] = (struct device *) NULL; + } + else + cards++; + } /* Loop over all devices */ + + return cards > 0 ? 0 : -ENODEV; +} + +void cleanup_module( void ) +{ + int i; + + /* TODO: Check if all skb's are released/freed. */ + for(i = 0; i < 5; i++) + if(hp100_devlist[i] != (struct device *) NULL) + { + unregister_netdev( hp100_devlist[i] ); + release_region( hp100_devlist[i]->base_addr, HP100_REGION_SIZE ); + if( ((struct hp100_private *)hp100_devlist[i]->priv)->mode==1 ) /* busmaster */ + kfree_s( ((struct hp100_private *)hp100_devlist[i]->priv)->page_vaddr, MAX_RINGSIZE+0x0f); + if ( ((struct hp100_private *)hp100_devlist[i]->priv) -> mem_ptr_virt ) + iounmap( ((struct hp100_private *)hp100_devlist[i]->priv) -> mem_ptr_virt ); + kfree_s( hp100_devlist[i]->priv, sizeof( struct hp100_private ) ); + hp100_devlist[i]->priv = NULL; + kfree_s(hp100_devlist[i], sizeof(struct device)); + hp100_devlist[i] = (struct device *) NULL; + } +} + +#endif /* MODULE */ + + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c hp100.c" + * c-indent-level: 2 + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/net/hp100.h b/linux/src/drivers/net/hp100.h new file mode 100644 index 00000000..e1884aaa --- /dev/null +++ b/linux/src/drivers/net/hp100.h @@ -0,0 +1,626 @@ +/* + * hp100.h: Hewlett Packard HP10/100VG ANY LAN ethernet driver for Linux. + * + * $Id: hp100.h,v 1.1 1999/04/26 05:52:20 tb Exp $ + * + * Authors: Jaroslav Kysela, <perex@pf.jcu.cz> + * Siegfried Loeffler <floeff@tunix.mathematik.uni-stuttgart.de> + * + * This driver is based on the 'hpfepkt' crynwr packet driver. + * + * This source/code is public free; you can distribute it and/or modify + * it under terms of the GNU General Public License (published by the + * Free Software Foundation) either version two of this License, or any + * later version. + */ + +/**************************************************************************** + * Hardware Constants + ****************************************************************************/ + +/* + * Page Identifiers + * (Swap Paging Register, PAGING, bits 3:0, Offset 0x02) + */ + +#define HP100_PAGE_PERFORMANCE 0x0 /* Page 0 */ +#define HP100_PAGE_MAC_ADDRESS 0x1 /* Page 1 */ +#define HP100_PAGE_HW_MAP 0x2 /* Page 2 */ +#define HP100_PAGE_EEPROM_CTRL 0x3 /* Page 3 */ +#define HP100_PAGE_MAC_CTRL 0x4 /* Page 4 */ +#define HP100_PAGE_MMU_CFG 0x5 /* Page 5 */ +#define HP100_PAGE_ID_MAC_ADDR 0x6 /* Page 6 */ +#define HP100_PAGE_MMU_POINTER 0x7 /* Page 7 */ + + +/* Registers that are present on all pages */ + +#define HP100_REG_HW_ID 0x00 /* R: (16) Unique card ID */ +#define HP100_REG_TRACE 0x00 /* W: (16) Used for debug output */ +#define HP100_REG_PAGING 0x02 /* R: (16),15:4 Card ID */ + /* W: (16),3:0 Switch pages */ +#define HP100_REG_OPTION_LSW 0x04 /* RW: (16) Select card functions */ +#define HP100_REG_OPTION_MSW 0x06 /* RW: (16) Select card functions */ + +/* Page 0 - Performance */ + +#define HP100_REG_IRQ_STATUS 0x08 /* RW: (16) Which ints are pending */ +#define HP100_REG_IRQ_MASK 0x0a /* RW: (16) Select ints to allow */ +#define HP100_REG_FRAGMENT_LEN 0x0c /* W: (16)12:0 Current fragment len */ +/* Note: For 32 bit systems, fragment len and offset registers are available */ +/* at offset 0x28 and 0x2c, where they can be written as 32bit values. */ +#define HP100_REG_OFFSET 0x0e /* RW: (16)12:0 Offset to start read */ +#define HP100_REG_DATA32 0x10 /* RW: (32) I/O mode data port */ +#define HP100_REG_DATA16 0x12 /* RW: WORDs must be read from here */ +#define HP100_REG_TX_MEM_FREE 0x14 /* RD: (32) Amount of free Tx mem */ +#define HP100_REG_TX_PDA_L 0x14 /* W: (32) BM: Ptr to PDL, Low Pri */ +#define HP100_REG_TX_PDA_H 0x1c /* W: (32) BM: Ptr to PDL, High Pri */ +#define HP100_REG_RX_PKT_CNT 0x18 /* RD: (8) Rx count of pkts on card */ +#define HP100_REG_TX_PKT_CNT 0x19 /* RD: (8) Tx count of pkts on card */ +#define HP100_REG_RX_PDL 0x1a /* R: (8) BM: # rx pdl not executed */ +#define HP100_REG_TX_PDL 0x1b /* R: (8) BM: # tx pdl not executed */ +#define HP100_REG_RX_PDA 0x18 /* W: (32) BM: Up to 31 addresses */ + /* which point to a PDL */ +#define HP100_REG_SL_EARLY 0x1c /* (32) Enhanced Slave Early Rx */ +#define HP100_REG_STAT_DROPPED 0x20 /* R (12) Dropped Packet Counter */ +#define HP100_REG_STAT_ERRORED 0x22 /* R (8) Errored Packet Counter */ +#define HP100_REG_STAT_ABORT 0x23 /* R (8) Abort Counter/OW Coll. Flag */ +#define HP100_REG_RX_RING 0x24 /* W (32) Slave: RX Ring Pointers */ +#define HP100_REG_32_FRAGMENT_LEN 0x28 /* W (13) Slave: Fragment Length Reg */ +#define HP100_REG_32_OFFSET 0x2c /* W (16) Slave: Offset Register */ + +/* Page 1 - MAC Address/Hash Table */ + +#define HP100_REG_MAC_ADDR 0x08 /* RW: (8) Cards MAC address */ +#define HP100_REG_HASH_BYTE0 0x10 /* RW: (8) Cards multicast filter */ + +/* Page 2 - Hardware Mapping */ + +#define HP100_REG_MEM_MAP_LSW 0x08 /* RW: (16) LSW of cards mem addr */ +#define HP100_REG_MEM_MAP_MSW 0x0a /* RW: (16) MSW of cards mem addr */ +#define HP100_REG_IO_MAP 0x0c /* RW: (8) Cards I/O address */ +#define HP100_REG_IRQ_CHANNEL 0x0d /* RW: (8) IRQ and edge/level int */ +#define HP100_REG_SRAM 0x0e /* RW: (8) How much RAM on card */ +#define HP100_REG_BM 0x0f /* RW: (8) Controls BM functions */ + +/* New on Page 2 for ETR chips: */ +#define HP100_REG_MODECTRL1 0x10 /* RW: (8) Mode Control 1 */ +#define HP100_REG_MODECTRL2 0x11 /* RW: (8) Mode Control 2 */ +#define HP100_REG_PCICTRL1 0x12 /* RW: (8) PCI Cfg 1 */ +#define HP100_REG_PCICTRL2 0x13 /* RW: (8) PCI Cfg 2 */ +#define HP100_REG_PCIBUSMLAT 0x15 /* RW: (8) PCI Bus Master Latency */ +#define HP100_REG_EARLYTXCFG 0x16 /* RW: (16) Early TX Cfg/Cntrl Reg */ +#define HP100_REG_EARLYRXCFG 0x18 /* RW: (8) Early RX Cfg/Cntrl Reg */ +#define HP100_REG_ISAPNPCFG1 0x1a /* RW: (8) ISA PnP Cfg/Cntrl Reg 1 */ +#define HP100_REG_ISAPNPCFG2 0x1b /* RW: (8) ISA PnP Cfg/Cntrl Reg 2 */ + +/* Page 3 - EEPROM/Boot ROM */ + +#define HP100_REG_EEPROM_CTRL 0x08 /* RW: (16) Used to load EEPROM */ +#define HP100_REG_BOOTROM_CTRL 0x0a + +/* Page 4 - LAN Configuration (MAC_CTRL) */ + +#define HP100_REG_10_LAN_CFG_1 0x08 /* RW: (8) Set 10M XCVR functions */ +#define HP100_REG_10_LAN_CFG_2 0x09 /* RW: (8) 10M XCVR functions */ +#define HP100_REG_VG_LAN_CFG_1 0x0a /* RW: (8) Set 100M XCVR functions */ +#define HP100_REG_VG_LAN_CFG_2 0x0b /* RW: (8) 100M LAN Training cfgregs */ +#define HP100_REG_MAC_CFG_1 0x0c /* RW: (8) Types of pkts to accept */ +#define HP100_REG_MAC_CFG_2 0x0d /* RW: (8) Misc MAC functions */ +#define HP100_REG_MAC_CFG_3 0x0e /* RW: (8) Misc MAC functions */ +#define HP100_REG_MAC_CFG_4 0x0f /* R: (8) Misc MAC states */ +#define HP100_REG_DROPPED 0x10 /* R: (16),11:0 Pkts cant fit in mem*/ +#define HP100_REG_CRC 0x12 /* R: (8) Pkts with CRC */ +#define HP100_REG_ABORT 0x13 /* R: (8) Aborted Tx pkts */ +#define HP100_REG_TRAIN_REQUEST 0x14 /* RW: (16) Endnode MAC register.*/ +#define HP100_REG_TRAIN_ALLOW 0x16 /* R: (16) Hub allowed register */ + +/* Page 5 - MMU */ + +#define HP100_REG_RX_MEM_STOP 0x0c /* RW: (16) End of Rx ring addr */ +#define HP100_REG_TX_MEM_STOP 0x0e /* RW: (16) End of Tx ring addr */ +#define HP100_REG_PDL_MEM_STOP 0x10 /* Not used by 802.12 devices */ +#define HP100_REG_ECB_MEM_STOP 0x14 /* I've no idea what this is */ + +/* Page 6 - Card ID/Physical LAN Address */ + +#define HP100_REG_BOARD_ID 0x08 /* R: (8) EISA/ISA card ID */ +#define HP100_REG_BOARD_IO_CHCK 0x0c /* R: (8) Added to ID to get FFh */ +#define HP100_REG_SOFT_MODEL 0x0d /* R: (8) Config program defined */ +#define HP100_REG_LAN_ADDR 0x10 /* R: (8) MAC addr of card */ +#define HP100_REG_LAN_ADDR_CHCK 0x16 /* R: (8) Added to addr to get FFh */ + +/* Page 7 - MMU Current Pointers */ + +#define HP100_REG_PTR_RXSTART 0x08 /* R: (16) Current begin of Rx ring */ +#define HP100_REG_PTR_RXEND 0x0a /* R: (16) Current end of Rx ring */ +#define HP100_REG_PTR_TXSTART 0x0c /* R: (16) Current begin of Tx ring */ +#define HP100_REG_PTR_TXEND 0x0e /* R: (16) Current end of Rx ring */ +#define HP100_REG_PTR_RPDLSTART 0x10 +#define HP100_REG_PTR_RPDLEND 0x12 +#define HP100_REG_PTR_RINGPTRS 0x14 +#define HP100_REG_PTR_MEMDEBUG 0x1a +/* ------------------------------------------------------------------------ */ + + +/* + * Hardware ID Register I (Always available, HW_ID, Offset 0x00) + */ +#define HP100_HW_ID_CASCADE 0x4850 /* Identifies Cascade Chip */ + +/* + * Hardware ID Register 2 & Paging Register + * (Always available, PAGING, Offset 0x02) + * Bits 15:4 are for the Chip ID + */ +#define HP100_CHIPID_MASK 0xFFF0 +#define HP100_CHIPID_SHASTA 0x5350 /* Not 802.12 compliant */ + /* EISA BM/SL, MCA16/32 SL, ISA SL */ +#define HP100_CHIPID_RAINIER 0x5360 /* Not 802.12 compliant EISA BM,*/ + /* PCI SL, MCA16/32 SL, ISA SL */ +#define HP100_CHIPID_LASSEN 0x5370 /* 802.12 compliant PCI BM, PCI SL */ + /* LRF supported */ + +/* + * Option Registers I and II + * (Always available, OPTION_LSW, Offset 0x04-0x05) + */ +#define HP100_DEBUG_EN 0x8000 /* 0:Dis., 1:Enable Debug Dump Ptr. */ +#define HP100_RX_HDR 0x4000 /* 0:Dis., 1:Enable putting pkt into */ + /* system mem. before Rx interrupt */ +#define HP100_MMAP_DIS 0x2000 /* 0:Enable, 1:Disable mem.mapping. */ + /* MMAP_DIS must be 0 and MEM_EN */ + /* must be 1 for memory-mapped */ + /* mode to be enabled */ +#define HP100_EE_EN 0x1000 /* 0:Disable,1:Enable EEPROM writing */ +#define HP100_BM_WRITE 0x0800 /* 0:Slave, 1:Bus Master for Tx data */ +#define HP100_BM_READ 0x0400 /* 0:Slave, 1:Bus Master for Rx data */ +#define HP100_TRI_INT 0x0200 /* 0:Don't, 1:Do tri-state the int */ +#define HP100_MEM_EN 0x0040 /* Config program set this to */ + /* 0:Disable, 1:Enable mem map. */ + /* See MMAP_DIS. */ +#define HP100_IO_EN 0x0020 /* 1:Enable I/O transfers */ +#define HP100_BOOT_EN 0x0010 /* 1:Enable boot ROM access */ +#define HP100_FAKE_INT 0x0008 /* 1:int */ +#define HP100_INT_EN 0x0004 /* 1:Enable ints from card */ +#define HP100_HW_RST 0x0002 /* 0:Reset, 1:Out of reset */ + /* NIC reset on 0 to 1 transition */ + +/* + * Option Register III + * (Always available, OPTION_MSW, Offset 0x06) + */ +#define HP100_PRIORITY_TX 0x0080 /* 1:Do all Tx pkts as priority */ +#define HP100_EE_LOAD 0x0040 /* 1:EEPROM loading, 0 when done */ +#define HP100_ADV_NXT_PKT 0x0004 /* 1:Advance to next pkt in Rx queue */ + /* h/w will set to 0 when done */ +#define HP100_TX_CMD 0x0002 /* 1:Tell h/w download done, h/w */ + /* will set to 0 when done */ + +/* + * Interrupt Status Registers I and II + * (Page PERFORMANCE, IRQ_STATUS, Offset 0x08-0x09) + * Note: With old chips, these Registers will clear when 1 is written to them + * with new chips this depends on setting of CLR_ISMODE + */ +#define HP100_RX_EARLY_INT 0x2000 +#define HP100_RX_PDA_ZERO 0x1000 +#define HP100_RX_PDL_FILL_COMPL 0x0800 +#define HP100_RX_PACKET 0x0400 /* 0:No, 1:Yes pkt has been Rx */ +#define HP100_RX_ERROR 0x0200 /* 0:No, 1:Yes Rx pkt had error */ +#define HP100_TX_PDA_ZERO 0x0020 /* 1 when PDA count goes to zero */ +#define HP100_TX_SPACE_AVAIL 0x0010 /* 0:<8192, 1:>=8192 Tx free bytes */ +#define HP100_TX_COMPLETE 0x0008 /* 0:No, 1:Yes a Tx has completed */ +#define HP100_MISC_ERROR 0x0004 /* 0:No, 1:Lan Link down or bus error*/ +#define HP100_TX_ERROR 0x0002 /* 0:No, 1:Yes Tx pkt had error */ + +/* + * Xmit Memory Free Count + * (Page PERFORMANCE, TX_MEM_FREE, Offset 0x14) (Read only, 32bit) + */ +#define HP100_AUTO_COMPARE 0x80000000 /* Tx Space avail & pkts<255 */ +#define HP100_FREE_SPACE 0x7fffffe0 /* Tx free memory */ + +/* + * IRQ Channel + * (Page HW_MAP, IRQ_CHANNEL, Offset 0x0d) + */ +#define HP100_ZERO_WAIT_EN 0x80 /* 0:No, 1:Yes asserts NOWS signal */ +#define HP100_IRQ_SCRAMBLE 0x40 +#define HP100_BOND_HP 0x20 +#define HP100_LEVEL_IRQ 0x10 /* 0:Edge, 1:Level type interrupts. */ + /* (Only valid on EISA cards) */ +#define HP100_IRQMASK 0x0F /* Isolate the IRQ bits */ + +/* + * SRAM Parameters + * (Page HW_MAP, SRAM, Offset 0x0e) + */ +#define HP100_RAM_SIZE_MASK 0xe0 /* AND to get SRAM size index */ +#define HP100_RAM_SIZE_SHIFT 0x05 /* Shift count(put index in lwr bits)*/ + +/* + * Bus Master Register + * (Page HW_MAP, BM, Offset 0x0f) + */ +#define HP100_BM_BURST_RD 0x01 /* EISA only: 1=Use burst trans. fm system */ + /* memory to chip (tx) */ +#define HP100_BM_BURST_WR 0x02 /* EISA only: 1=Use burst trans. fm system */ + /* memory to chip (rx) */ +#define HP100_BM_MASTER 0x04 /* 0:Slave, 1:BM mode */ +#define HP100_BM_PAGE_CK 0x08 /* This bit should be set whenever in*/ + /* an EISA system */ +#define HP100_BM_PCI_8CLK 0x40 /* ... cycles 8 clocks apart */ + + +/* + * Mode Control Register I + * (Page HW_MAP, MODECTRL1, Offset0x10) + */ +#define HP100_TX_DUALQ 0x10 + /* If set and BM -> dual tx pda queues*/ +#define HP100_ISR_CLRMODE 0x02 /* If set ISR will clear all pending */ + /* interrupts on read (etr only?) */ +#define HP100_EE_NOLOAD 0x04 /* Status whether res will be loaded */ + /* from the eeprom */ +#define HP100_TX_CNT_FLG 0x08 /* Controls Early TX Reg Cnt Field */ +#define HP100_PDL_USE3 0x10 /* If set BM engine will read only */ + /* first three data elements of a PDL */ + /* on the first access. */ +#define HP100_BUSTYPE_MASK 0xe0 /* Three bit bus type info */ + +/* + * Mode Control Register II + * (Page HW_MAP, MODECTRL2, Offset0x11) + */ +#define HP100_EE_MASK 0x0f /* Tell EEPROM circuit not to load */ + /* certain resources */ +#define HP100_DIS_CANCEL 0x20 /* For tx dualq mode operation */ +#define HP100_EN_PDL_WB 0x40 /* 1: Status of PDL completion may be */ + /* written back to system mem */ +#define HP100_EN_BUS_FAIL 0x80 /* Enables bus-fail portion of misc */ + /* interrupt */ + +/* + * PCI Configuration and Control Register I + * (Page HW_MAP, PCICTRL1, Offset 0x12) + */ +#define HP100_LO_MEM 0x01 /* 1: Mapped Mem requested below 1MB */ +#define HP100_NO_MEM 0x02 /* 1: Disables Req for sysmem to PCI */ + /* bios */ +#define HP100_USE_ISA 0x04 /* 1: isa type decodes will occur */ + /* simultaneously with PCI decodes */ +#define HP100_IRQ_HI_MASK 0xf0 /* pgmed by pci bios */ +#define HP100_PCI_IRQ_HI_MASK 0x78 /* Isolate 4 bits for PCI IRQ */ + +/* + * PCI Configuration and Control Register II + * (Page HW_MAP, PCICTRL2, Offset 0x13) + */ +#define HP100_RD_LINE_PDL 0x01 /* 1: PCI command Memory Read Line en */ +#define HP100_RD_TX_DATA_MASK 0x06 /* choose PCI memread cmds for TX */ +#define HP100_MWI 0x08 /* 1: en. PCI memory write invalidate */ +#define HP100_ARB_MODE 0x10 /* Select PCI arbitor type */ +#define HP100_STOP_EN 0x20 /* Enables PCI state machine to issue */ + /* pci stop if cascade not ready */ +#define HP100_IGNORE_PAR 0x40 /* 1: PCI state machine ignores parity*/ +#define HP100_PCI_RESET 0x80 /* 0->1: Reset PCI block */ + +/* + * Early TX Configuration and Control Register + * (Page HW_MAP, EARLYTXCFG, Offset 0x16) + */ +#define HP100_EN_EARLY_TX 0x8000 /* 1=Enable Early TX */ +#define HP100_EN_ADAPTIVE 0x4000 /* 1=Enable adaptive mode */ +#define HP100_EN_TX_UR_IRQ 0x2000 /* reserved, must be 0 */ +#define HP100_EN_LOW_TX 0x1000 /* reserved, must be 0 */ +#define HP100_ET_CNT_MASK 0x0fff /* bits 11..0: ET counters */ + +/* + * Early RX Configuration and Control Register + * (Page HW_MAP, EARLYRXCFG, Offset 0x18) + */ +#define HP100_EN_EARLY_RX 0x80 /* 1=Enable Early RX */ +#define HP100_EN_LOW_RX 0x40 /* reserved, must be 0 */ +#define HP100_RX_TRIP_MASK 0x1f /* bits 4..0: threshold at which the + * early rx circuit will start the + * dma of received packet into system + * memory for BM */ + +/* + * Serial Devices Control Register + * (Page EEPROM_CTRL, EEPROM_CTRL, Offset 0x08) + */ +#define HP100_EEPROM_LOAD 0x0001 /* 0->1 loads EEPROM into registers. */ + /* When it goes back to 0, load is */ + /* complete. This should take ~600us.*/ + +/* + * 10MB LAN Control and Configuration Register I + * (Page MAC_CTRL, 10_LAN_CFG_1, Offset 0x08) + */ +#define HP100_MAC10_SEL 0xc0 /* Get bits to indicate MAC */ +#define HP100_AUI_SEL 0x20 /* Status of AUI selection */ +#define HP100_LOW_TH 0x10 /* 0:No, 1:Yes allow better cabling */ +#define HP100_LINK_BEAT_DIS 0x08 /* 0:Enable, 1:Disable link beat */ +#define HP100_LINK_BEAT_ST 0x04 /* 0:No, 1:Yes link beat being Rx */ +#define HP100_R_ROL_ST 0x02 /* 0:No, 1:Yes Rx twisted pair has */ + /* been reversed */ +#define HP100_AUI_ST 0x01 /* 0:No, 1:Yes use AUI on TP card */ + +/* + * 10 MB LAN Control and Configuration Register II + * (Page MAC_CTRL, 10_LAN_CFG_2, Offset 0x09) + */ +#define HP100_SQU_ST 0x01 /* 0:No, 1:Yes collision signal sent */ + /* after Tx.Only used for AUI. */ +#define HP100_FULLDUP 0x02 /* 1: LXT901 XCVR fullduplx enabled */ +#define HP100_DOT3_MAC 0x04 /* 1: DOT 3 Mac sel. unless Autosel */ + +/* + * MAC Selection, use with MAC10_SEL bits + */ +#define HP100_AUTO_SEL_10 0x0 /* Auto select */ +#define HP100_XCVR_LXT901_10 0x1 /* LXT901 10BaseT transceiver */ +#define HP100_XCVR_7213 0x2 /* 7213 transceiver */ +#define HP100_XCVR_82503 0x3 /* 82503 transceiver */ + +/* + * 100MB LAN Training Register + * (Page MAC_CTRL, VG_LAN_CFG_2, Offset 0x0b) (old, pre 802.12) + */ +#define HP100_FRAME_FORMAT 0x08 /* 0:802.3, 1:802.5 frames */ +#define HP100_BRIDGE 0x04 /* 0:No, 1:Yes tell hub i am a bridge */ +#define HP100_PROM_MODE 0x02 /* 0:No, 1:Yes tell hub card is */ + /* promiscuous */ +#define HP100_REPEATER 0x01 /* 0:No, 1:Yes tell hub MAC wants to */ + /* be a cascaded repeater */ + +/* + * 100MB LAN Control and Configuration Register + * (Page MAC_CTRL, VG_LAN_CFG_1, Offset 0x0a) + */ +#define HP100_VG_SEL 0x80 /* 0:No, 1:Yes use 100 Mbit MAC */ +#define HP100_LINK_UP_ST 0x40 /* 0:No, 1:Yes endnode logged in */ +#define HP100_LINK_CABLE_ST 0x20 /* 0:No, 1:Yes cable can hear tones */ + /* from hub */ +#define HP100_LOAD_ADDR 0x10 /* 0->1 card addr will be sent */ + /* 100ms later the link status */ + /* bits are valid */ +#define HP100_LINK_CMD 0x08 /* 0->1 link will attempt to log in. */ + /* 100ms later the link status */ + /* bits are valid */ +#define HP100_TRN_DONE 0x04 /* NEW ETR-Chips only: Will be reset */ + /* after LinkUp Cmd is given and set */ + /* when training has completed. */ +#define HP100_LINK_GOOD_ST 0x02 /* 0:No, 1:Yes cable passed training */ +#define HP100_VG_RESET 0x01 /* 0:Yes, 1:No reset the 100VG MAC */ + + +/* + * MAC Configuration Register I + * (Page MAC_CTRL, MAC_CFG_1, Offset 0x0c) + */ +#define HP100_RX_IDLE 0x80 /* 0:Yes, 1:No currently receiving pkts */ +#define HP100_TX_IDLE 0x40 /* 0:Yes, 1:No currently Txing pkts */ +#define HP100_RX_EN 0x20 /* 1: allow receiving of pkts */ +#define HP100_TX_EN 0x10 /* 1: allow transmitting of pkts */ +#define HP100_ACC_ERRORED 0x08 /* 0:No, 1:Yes allow Rx of errored pkts */ +#define HP100_ACC_MC 0x04 /* 0:No, 1:Yes allow Rx of multicast pkts */ +#define HP100_ACC_BC 0x02 /* 0:No, 1:Yes allow Rx of broadcast pkts */ +#define HP100_ACC_PHY 0x01 /* 0:No, 1:Yes allow Rx of ALL phys. pkts */ +#define HP100_MAC1MODEMASK 0xf0 /* Hide ACC bits */ +#define HP100_MAC1MODE1 0x00 /* Receive nothing, must also disable RX */ +#define HP100_MAC1MODE2 0x00 +#define HP100_MAC1MODE3 HP100_MAC1MODE2 | HP100_ACC_BC +#define HP100_MAC1MODE4 HP100_MAC1MODE3 | HP100_ACC_MC +#define HP100_MAC1MODE5 HP100_MAC1MODE4 /* set mc hash to all ones also */ +#define HP100_MAC1MODE6 HP100_MAC1MODE5 | HP100_ACC_PHY /* Promiscuous */ +/* Note MODE6 will receive all GOOD packets on the LAN. This really needs + a mode 7 defined to be LAN Analyzer mode, which will receive errored and + runt packets, and keep the CRC bytes. */ +#define HP100_MAC1MODE7 HP100_MAC1MODE6 | HP100_ACC_ERRORED + +/* + * MAC Configuration Register II + * (Page MAC_CTRL, MAC_CFG_2, Offset 0x0d) + */ +#define HP100_TR_MODE 0x80 /* 0:No, 1:Yes support Token Ring formats */ +#define HP100_TX_SAME 0x40 /* 0:No, 1:Yes Tx same packet continuous */ +#define HP100_LBK_XCVR 0x20 /* 0:No, 1:Yes loopback through MAC & */ + /* transceiver */ +#define HP100_LBK_MAC 0x10 /* 0:No, 1:Yes loopback through MAC */ +#define HP100_CRC_I 0x08 /* 0:No, 1:Yes inhibit CRC on Tx packets */ +#define HP100_ACCNA 0x04 /* 1: For 802.5: Accept only token ring + * group addr that maches NA mask */ +#define HP100_KEEP_CRC 0x02 /* 0:No, 1:Yes keep CRC on Rx packets. */ + /* The length will reflect this. */ +#define HP100_ACCFA 0x01 /* 1: For 802.5: Accept only functional + * addrs that match FA mask (page1) */ +#define HP100_MAC2MODEMASK 0x02 +#define HP100_MAC2MODE1 0x00 +#define HP100_MAC2MODE2 0x00 +#define HP100_MAC2MODE3 0x00 +#define HP100_MAC2MODE4 0x00 +#define HP100_MAC2MODE5 0x00 +#define HP100_MAC2MODE6 0x00 +#define HP100_MAC2MODE7 KEEP_CRC + +/* + * MAC Configuration Register III + * (Page MAC_CTRL, MAC_CFG_3, Offset 0x0e) + */ +#define HP100_PACKET_PACE 0x03 /* Packet Pacing: + * 00: No packet pacing + * 01: 8 to 16 uS delay + * 10: 16 to 32 uS delay + * 11: 32 to 64 uS delay + */ +#define HP100_LRF_EN 0x04 /* 1: External LAN Rcv Filter and + * TCP/IP Checksumming enabled. */ +#define HP100_AUTO_MODE 0x10 /* 1: AutoSelect between 10/100 */ + +/* + * MAC Configuration Register IV + * (Page MAC_CTRL, MAC_CFG_4, Offset 0x0f) + */ +#define HP100_MAC_SEL_ST 0x01 /* (R): Status of external VGSEL + * Signal, 1=100VG, 0=10Mbit sel. */ +#define HP100_LINK_FAIL_ST 0x02 /* (R): Status of Link Fail portion + * of the Misc. Interrupt */ + +/* + * 100 MB LAN Training Request/Allowed Registers + * (Page MAC_CTRL, TRAIN_REQUEST and TRAIN_ALLOW, Offset 0x14-0x16)(ETR parts only) + */ +#define HP100_MACRQ_REPEATER 0x0001 /* 1: MAC tells HUB it wants to be + * a cascaded repeater + * 0: ... wants to be a DTE */ +#define HP100_MACRQ_PROMSC 0x0006 /* 2 bits: Promiscious mode + * 00: Rcv only unicast packets + * specifically addr to this + * endnode + * 10: Rcv all pckts fwded by + * the local repeater */ +#define HP100_MACRQ_FRAMEFMT_EITHER 0x0018 /* 11: either format allowed */ +#define HP100_MACRQ_FRAMEFMT_802_3 0x0000 /* 00: 802.3 is requested */ +#define HP100_MACRQ_FRAMEFMT_802_5 0x0010 /* 10: 802.5 format is requested */ +#define HP100_CARD_MACVER 0xe000 /* R: 3 bit Cards 100VG MAC version */ +#define HP100_MALLOW_REPEATER 0x0001 /* If reset, requested access as an + * end node is allowed */ +#define HP100_MALLOW_PROMSC 0x0004 /* 2 bits: Promiscious mode + * 00: Rcv only unicast packets + * specifically addr to this + * endnode + * 10: Rcv all pckts fwded by + * the local repeater */ +#define HP100_MALLOW_FRAMEFMT 0x00e0 /* 2 bits: Frame Format + * 00: 802.3 format will be used + * 10: 802.5 format will be used */ +#define HP100_MALLOW_ACCDENIED 0x0400 /* N bit */ +#define HP100_MALLOW_CONFIGURE 0x0f00 /* C bit */ +#define HP100_MALLOW_DUPADDR 0x1000 /* D bit */ +#define HP100_HUB_MACVER 0xe000 /* R: 3 bit 802.12 MAC/RMAC training */ + /* protocol of repeater */ + +/* ****************************************************************************** */ + +/* + * Set/Reset bits + */ +#define HP100_SET_HB 0x0100 /* 0:Set fields to 0 whose mask is 1 */ +#define HP100_SET_LB 0x0001 /* HB sets upper byte, LB sets lower byte */ +#define HP100_RESET_HB 0x0000 /* For readability when resetting bits */ +#define HP100_RESET_LB 0x0000 /* For readability when resetting bits */ + +/* + * Misc. Constants + */ +#define HP100_LAN_100 100 /* lan_type value for VG */ +#define HP100_LAN_10 10 /* lan_type value for 10BaseT */ +#define HP100_LAN_ERR (-1) /* lan_type value for link down */ + +#define TRUE 1 +#define FALSE 0 + + +/* + * Bus Master Data Structures ---------------------------------------------- + */ + +#define MAX_RX_PDL 30 /* Card limit = 31 */ +#define MAX_RX_FRAG 2 /* Don't need more... */ +#define MAX_TX_PDL 29 +#define MAX_TX_FRAG 2 /* Limit = 31 */ + +/* Define total PDL area size in bytes (should be 4096) */ +/* This is the size of kernel (dma) memory that will be allocated. */ +#define MAX_RINGSIZE ((MAX_RX_FRAG*8+4+4)*MAX_RX_PDL+(MAX_TX_FRAG*8+4+4)*MAX_TX_PDL)+16 + +/* Ethernet Packet Sizes */ +#define MIN_ETHER_SIZE 60 +#define MAX_ETHER_SIZE 1514 /* Needed for preallocation of */ + /* skb buffer when busmastering */ + +/* Tx or Rx Ring Entry */ +typedef struct hp100_ring { + u_int *pdl; /* Address of PDLs PDH, dword before + * this address is used for rx hdr */ + u_int pdl_paddr; /* Physical address of PDL */ + struct sk_buff *skb; + struct hp100_ring *next; +} hp100_ring_t; + + + +/* Mask for Header Descriptor */ +#define HP100_PKT_LEN_MASK 0x1FFF /* AND with RxLength to get length */ + + +/* Receive Packet Status. Note, the error bits are only valid if ACC_ERRORED + bit in the MAC Configuration Register 1 is set. */ +#define HP100_RX_PRI 0x8000 /* 0:No, 1:Yes packet is priority */ +#define HP100_SDF_ERR 0x4000 /* 0:No, 1:Yes start of frame error */ +#define HP100_SKEW_ERR 0x2000 /* 0:No, 1:Yes skew out of range */ +#define HP100_BAD_SYMBOL_ERR 0x1000 /* 0:No, 1:Yes invalid symbol received */ +#define HP100_RCV_IPM_ERR 0x0800 /* 0:No, 1:Yes pkt had an invalid packet */ + /* marker */ +#define HP100_SYMBOL_BAL_ERR 0x0400 /* 0:No, 1:Yes symbol balance error */ +#define HP100_VG_ALN_ERR 0x0200 /* 0:No, 1:Yes non-octet received */ +#define HP100_TRUNC_ERR 0x0100 /* 0:No, 1:Yes the packet was truncated */ +#define HP100_RUNT_ERR 0x0040 /* 0:No, 1:Yes pkt length < Min Pkt */ + /* Length Reg. */ +#define HP100_ALN_ERR 0x0010 /* 0:No, 1:Yes align error. */ +#define HP100_CRC_ERR 0x0008 /* 0:No, 1:Yes CRC occurred. */ + +/* The last three bits indicate the type of destination address */ + +#define HP100_MULTI_ADDR_HASH 0x0006 /* 110: Addr multicast, matched hash */ +#define HP100_BROADCAST_ADDR 0x0003 /* x11: Addr broadcast */ +#define HP100_MULTI_ADDR_NO_HASH 0x0002 /* 010: Addr multicast, didn't match hash */ +#define HP100_PHYS_ADDR_MATCH 0x0001 /* x01: Addr was physical and mine */ +#define HP100_PHYS_ADDR_NO_MATCH 0x0000 /* x00: Addr was physical but not mine */ + +/* + * macros + */ + +#define hp100_inb( reg ) \ + inb( ioaddr + HP100_REG_##reg ) +#define hp100_inw( reg ) \ + inw( ioaddr + HP100_REG_##reg ) +#define hp100_inl( reg ) \ + inl( ioaddr + HP100_REG_##reg ) +#define hp100_outb( data, reg ) \ + outb( data, ioaddr + HP100_REG_##reg ) +#define hp100_outw( data, reg ) \ + outw( data, ioaddr + HP100_REG_##reg ) +#define hp100_outl( data, reg ) \ + outl( data, ioaddr + HP100_REG_##reg ) +#define hp100_orb( data, reg ) \ + outb( inb( ioaddr + HP100_REG_##reg ) | (data), ioaddr + HP100_REG_##reg ) +#define hp100_orw( data, reg ) \ + outw( inw( ioaddr + HP100_REG_##reg ) | (data), ioaddr + HP100_REG_##reg ) +#define hp100_andb( data, reg ) \ + outb( inb( ioaddr + HP100_REG_##reg ) & (data), ioaddr + HP100_REG_##reg ) +#define hp100_andw( data, reg ) \ + outw( inw( ioaddr + HP100_REG_##reg ) & (data), ioaddr + HP100_REG_##reg ) + +#define hp100_page( page ) \ + outw( HP100_PAGE_##page, ioaddr + HP100_REG_PAGING ) +#define hp100_ints_off() \ + outw( HP100_INT_EN | HP100_RESET_LB, ioaddr + HP100_REG_OPTION_LSW ) +#define hp100_ints_on() \ + outw( HP100_INT_EN | HP100_SET_LB, ioaddr + HP100_REG_OPTION_LSW ) +#define hp100_mem_map_enable() \ + outw( HP100_MMAP_DIS | HP100_RESET_HB, ioaddr + HP100_REG_OPTION_LSW ) +#define hp100_mem_map_disable() \ + outw( HP100_MMAP_DIS | HP100_SET_HB, ioaddr + HP100_REG_OPTION_LSW ) + + +/* + * Local variables: + * c-indent-level: 2 + * tab-width: 8 + * End: +*/ diff --git a/linux/src/drivers/net/i82586.h b/linux/src/drivers/net/i82586.h new file mode 100644 index 00000000..d41702e9 --- /dev/null +++ b/linux/src/drivers/net/i82586.h @@ -0,0 +1,413 @@ +/* + * Intel 82586 IEEE 802.3 Ethernet LAN Coprocessor. + * + * See: + * Intel Microcommunications 1991 + * p1-1 to p1-37 + * Intel order No. 231658 + * ISBN 1-55512-119-5 + * + * Unfortunately, the above chapter mentions neither + * the System Configuration Pointer (SCP) nor the + * Intermediate System Configuration Pointer (ISCP), + * so we probably need to look elsewhere for the + * whole story -- some recommend the "Intel LAN + * Components manual" but I have neither a copy + * nor a full reference. But "elsewhere" may be + * in the same publication... + * The description of a later device, the + * "82596CA High-Performance 32-Bit Local Area Network + * Coprocessor", (ibid. p1-38 to p1-109) does mention + * the SCP and ISCP and also has an i82586 compatibility + * mode. Even more useful is "AP-235 An 82586 Data Link + * Driver" (ibid. p1-337 to p1-417). + */ + +#define I82586_MEMZ (64 * 1024) + +#define I82586_SCP_ADDR (I82586_MEMZ - sizeof(scp_t)) + +#define ADDR_LEN 6 +#define I82586NULL 0xFFFF + +#define toff(t,p,f) (unsigned short)((void *)(&((t *)((void *)0 + (p)))->f) - (void *)0) + +/* + * System Configuration Pointer (SCP). + */ +typedef struct scp_t scp_t; +struct scp_t +{ + unsigned short scp_sysbus; /* 82586 bus width: */ +#define SCP_SY_16BBUS (0x0 << 0) /* 16 bits */ +#define SCP_SY_8BBUS (0x1 << 0) /* 8 bits. */ + unsigned short scp_junk[2]; /* Unused */ + unsigned short scp_iscpl; /* lower 16 bits of ISCP_ADDR */ + unsigned short scp_iscph; /* upper 16 bits of ISCP_ADDR */ +}; + +/* + * Intermediate System Configuration Pointer (ISCP). + */ +typedef struct iscp_t iscp_t; +struct iscp_t +{ + unsigned short iscp_busy; /* set by CPU before first CA, */ + /* cleared by 82586 after read. */ + unsigned short iscp_offset; /* offset of SCB */ + unsigned short iscp_basel; /* base of SCB */ + unsigned short iscp_baseh; /* " */ +}; + +/* + * System Control Block (SCB). + * The 82586 writes its status to scb_status and then + * raises an interrupt to alert the CPU. + * The CPU writes a command to scb_command and + * then issues a Channel Attention (CA) to alert the 82586. + */ +typedef struct scb_t scb_t; +struct scb_t +{ + unsigned short scb_status; /* Status of 82586 */ +#define SCB_ST_INT (0xF << 12) /* Some of: */ +#define SCB_ST_CX (0x1 << 15) /* Cmd completed */ +#define SCB_ST_FR (0x1 << 14) /* Frame received */ +#define SCB_ST_CNA (0x1 << 13) /* Cmd unit not active */ +#define SCB_ST_RNR (0x1 << 12) /* Rcv unit not ready */ +#define SCB_ST_JUNK0 (0x1 << 11) /* 0 */ +#define SCB_ST_CUS (0x7 << 8) /* Cmd unit status */ +#define SCB_ST_CUS_IDLE (0 << 8) /* Idle */ +#define SCB_ST_CUS_SUSP (1 << 8) /* Suspended */ +#define SCB_ST_CUS_ACTV (2 << 8) /* Active */ +#define SCB_ST_JUNK1 (0x1 << 7) /* 0 */ +#define SCB_ST_RUS (0x7 << 4) /* Rcv unit status */ +#define SCB_ST_RUS_IDLE (0 << 4) /* Idle */ +#define SCB_ST_RUS_SUSP (1 << 4) /* Suspended */ +#define SCB_ST_RUS_NRES (2 << 4) /* No resources */ +#define SCB_ST_RUS_RDY (4 << 4) /* Ready */ + unsigned short scb_command; /* Next command */ +#define SCB_CMD_ACK_CX (0x1 << 15) /* Ack cmd completion */ +#define SCB_CMD_ACK_FR (0x1 << 14) /* Ack frame received */ +#define SCB_CMD_ACK_CNA (0x1 << 13) /* Ack CU not active */ +#define SCB_CMD_ACK_RNR (0x1 << 12) /* Ack RU not ready */ +#define SCB_CMD_JUNKX (0x1 << 11) /* Unused */ +#define SCB_CMD_CUC (0x7 << 8) /* Command Unit command */ +#define SCB_CMD_CUC_NOP (0 << 8) /* Nop */ +#define SCB_CMD_CUC_GO (1 << 8) /* Start cbl_offset */ +#define SCB_CMD_CUC_RES (2 << 8) /* Resume execution */ +#define SCB_CMD_CUC_SUS (3 << 8) /* Suspend " */ +#define SCB_CMD_CUC_ABT (4 << 8) /* Abort " */ +#define SCB_CMD_RESET (0x1 << 7) /* Reset chip (hardware) */ +#define SCB_CMD_RUC (0x7 << 4) /* Receive Unit command */ +#define SCB_CMD_RUC_NOP (0 << 4) /* Nop */ +#define SCB_CMD_RUC_GO (1 << 4) /* Start rfa_offset */ +#define SCB_CMD_RUC_RES (2 << 4) /* Resume reception */ +#define SCB_CMD_RUC_SUS (3 << 4) /* Suspend " */ +#define SCB_CMD_RUC_ABT (4 << 4) /* Abort " */ + unsigned short scb_cbl_offset; /* Offset of first command unit */ + /* Action Command */ + unsigned short scb_rfa_offset; /* Offset of first Receive */ + /* Frame Descriptor in the */ + /* Receive Frame Area */ + unsigned short scb_crcerrs; /* Properly aligned frames */ + /* received with a CRC error */ + unsigned short scb_alnerrs; /* Misaligned frames received */ + /* with a CRC error */ + unsigned short scb_rscerrs; /* Frames lost due to no space */ + unsigned short scb_ovrnerrs; /* Frames lost due to slow bus */ +}; + +#define scboff(p,f) toff(scb_t, p, f) + +/* + * The eight Action Commands. + */ +typedef enum acmd_e acmd_e; +enum acmd_e +{ + acmd_nop = 0, /* Do nothing */ + acmd_ia_setup = 1, /* Load an (ethernet) address into the */ + /* 82586 */ + acmd_configure = 2, /* Update the 82586 operating parameters */ + acmd_mc_setup = 3, /* Load a list of (ethernet) multicast */ + /* addresses into the 82586 */ + acmd_transmit = 4, /* Transmit a frame */ + acmd_tdr = 5, /* Perform a Time Domain Reflectometer */ + /* test on the serial link */ + acmd_dump = 6, /* Copy 82586 registers to memory */ + acmd_diagnose = 7, /* Run an internal self test */ +}; + +/* + * Generic Action Command header. + */ +typedef struct ach_t ach_t; +struct ach_t +{ + unsigned short ac_status; /* Command status: */ +#define AC_SFLD_C (0x1 << 15) /* Command completed */ +#define AC_SFLD_B (0x1 << 14) /* Busy executing */ +#define AC_SFLD_OK (0x1 << 13) /* Completed error free */ +#define AC_SFLD_A (0x1 << 12) /* Command aborted */ +#define AC_SFLD_FAIL (0x1 << 11) /* Selftest failed */ +#define AC_SFLD_S10 (0x1 << 10) /* No carrier sense */ + /* during transmission */ +#define AC_SFLD_S9 (0x1 << 9) /* Tx unsuccessful: */ + /* (stopped) lost CTS */ +#define AC_SFLD_S8 (0x1 << 8) /* Tx unsuccessful: */ + /* (stopped) slow DMA */ +#define AC_SFLD_S7 (0x1 << 7) /* Tx deferred: */ + /* other link traffic */ +#define AC_SFLD_S6 (0x1 << 6) /* Heart Beat: collision */ + /* detect after last tx */ +#define AC_SFLD_S5 (0x1 << 5) /* Tx stopped: */ + /* excessive collisions */ +#define AC_SFLD_MAXCOL (0xF << 0) /* Collision count */ + unsigned short ac_command; /* Command specifier: */ +#define AC_CFLD_EL (0x1 << 15) /* End of command list */ +#define AC_CFLD_S (0x1 << 14) /* Suspend on completion */ +#define AC_CFLD_I (0x1 << 13) /* Interrupt on completion */ +#define AC_CFLD_CMD (0x7 << 0) /* acmd_e */ + unsigned short ac_link; /* Next Action Command */ +}; + +#define acoff(p,f) toff(ach_t, p, f) + +/* + * The Nop Action Command. + */ +typedef struct ac_nop_t ac_nop_t; +struct ac_nop_t +{ + ach_t nop_h; +}; + +/* + * The IA-Setup Action Command. + */ +typedef struct ac_ias_t ac_ias_t; +struct ac_ias_t +{ + ach_t ias_h; + unsigned char ias_addr[ADDR_LEN]; /* The (ethernet) address */ +}; + +/* + * The Configure Action Command. + */ +typedef struct ac_cfg_t ac_cfg_t; +struct ac_cfg_t +{ + ach_t cfg_h; + unsigned char cfg_byte_cnt; /* Size foll data: 4-12 */ +#define AC_CFG_BYTE_CNT(v) (((v) & 0xF) << 0) + unsigned char cfg_fifolim; /* FIFO threshold */ +#define AC_CFG_FIFOLIM(v) (((v) & 0xF) << 0) + unsigned char cfg_byte8; +#define AC_CFG_SAV_BF(v) (((v) & 0x1) << 7) /* Save rxd bad frames */ +#define AC_CFG_SRDY(v) (((v) & 0x1) << 6) /* SRDY/ARDY pin means */ + /* external sync. */ + unsigned char cfg_byte9; +#define AC_CFG_ELPBCK(v) (((v) & 0x1) << 7) /* External loopback */ +#define AC_CFG_ILPBCK(v) (((v) & 0x1) << 6) /* Internal loopback */ +#define AC_CFG_PRELEN(v) (((v) & 0x3) << 4) /* Preamble length */ +#define AC_CFG_PLEN_2 0 /* 2 bytes */ +#define AC_CFG_PLEN_4 1 /* 4 bytes */ +#define AC_CFG_PLEN_8 2 /* 8 bytes */ +#define AC_CFG_PLEN_16 3 /* 16 bytes */ +#define AC_CFG_ALOC(v) (((v) & 0x1) << 3) /* Addr/len data is */ + /* explicit in buffers */ +#define AC_CFG_ADDRLEN(v) (((v) & 0x7) << 0) /* Bytes per address */ + unsigned char cfg_byte10; +#define AC_CFG_BOFMET(v) (((v) & 0x1) << 7) /* Use alternate expo. */ + /* backoff method */ +#define AC_CFG_ACR(v) (((v) & 0x7) << 4) /* Accelerated cont. res. */ +#define AC_CFG_LINPRIO(v) (((v) & 0x7) << 0) /* Linear priority */ + unsigned char cfg_ifs; /* Interframe spacing */ + unsigned char cfg_slotl; /* Slot time (low byte) */ + unsigned char cfg_byte13; +#define AC_CFG_RETRYNUM(v) (((v) & 0xF) << 4) /* Max. collision retry */ +#define AC_CFG_SLTTMHI(v) (((v) & 0x7) << 0) /* Slot time (high bits) */ + unsigned char cfg_byte14; +#define AC_CFG_FLGPAD(v) (((v) & 0x1) << 7) /* Pad with HDLC flags */ +#define AC_CFG_BTSTF(v) (((v) & 0x1) << 6) /* Do HDLC bitstuffing */ +#define AC_CFG_CRC16(v) (((v) & 0x1) << 5) /* 16 bit CCITT CRC */ +#define AC_CFG_NCRC(v) (((v) & 0x1) << 4) /* Insert no CRC */ +#define AC_CFG_TNCRS(v) (((v) & 0x1) << 3) /* Tx even if no carrier */ +#define AC_CFG_MANCH(v) (((v) & 0x1) << 2) /* Manchester coding */ +#define AC_CFG_BCDIS(v) (((v) & 0x1) << 1) /* Disable broadcast */ +#define AC_CFG_PRM(v) (((v) & 0x1) << 0) /* Promiscuous mode */ + unsigned char cfg_byte15; +#define AC_CFG_ICDS(v) (((v) & 0x1) << 7) /* Internal collision */ + /* detect source */ +#define AC_CFG_CDTF(v) (((v) & 0x7) << 4) /* Collision detect */ + /* filter in bit times */ +#define AC_CFG_ICSS(v) (((v) & 0x1) << 3) /* Internal carrier */ + /* sense source */ +#define AC_CFG_CSTF(v) (((v) & 0x7) << 0) /* Carrier sense */ + /* filter in bit times */ + unsigned short cfg_min_frm_len; +#define AC_CFG_MNFRM(v) (((v) & 0xFF) << 0) /* Min. bytes/frame (<= 255) */ +}; + +/* + * The MC-Setup Action Command. + */ +typedef struct ac_mcs_t ac_mcs_t; +struct ac_mcs_t +{ + ach_t mcs_h; + unsigned short mcs_cnt; /* No. of bytes of MC addresses */ +#if 0 + unsigned char mcs_data[ADDR_LEN]; /* The first MC address .. */ + ... +#endif +}; + +#define I82586_MAX_MULTICAST_ADDRESSES 128 /* Hardware hashed filter */ + +/* + * The Transmit Action Command. + */ +typedef struct ac_tx_t ac_tx_t; +struct ac_tx_t +{ + ach_t tx_h; + unsigned short tx_tbd_offset; /* Address of list of buffers. */ +#if 0 +Linux packets are passed down with the destination MAC address +and length/type field already prepended to the data, +so we do not need to insert it. Consistent with this +we must also set the AC_CFG_ALOC(..) flag during the +ac_cfg_t action command. + unsigned char tx_addr[ADDR_LEN]; /* The frame dest. address */ + unsigned short tx_length; /* The frame length */ +#endif /* 0 */ +}; + +/* + * The Time Domain Reflectometer Action Command. + */ +typedef struct ac_tdr_t ac_tdr_t; +struct ac_tdr_t +{ + ach_t tdr_h; + unsigned short tdr_result; /* Result. */ +#define AC_TDR_LNK_OK (0x1 << 15) /* No link problem */ +#define AC_TDR_XCVR_PRB (0x1 << 14) /* Txcvr cable problem */ +#define AC_TDR_ET_OPN (0x1 << 13) /* Open on the link */ +#define AC_TDR_ET_SRT (0x1 << 12) /* Short on the link */ +#define AC_TDR_TIME (0x7FF << 0) /* Distance to problem */ + /* site in transmit */ + /* clock cycles */ +}; + +/* + * The Dump Action Command. + */ +typedef struct ac_dmp_t ac_dmp_t; +struct ac_dmp_t +{ + ach_t dmp_h; + unsigned short dmp_offset; /* Result. */ +}; + +/* + * Size of the result of the dump command. + */ +#define DUMPBYTES 170 + +/* + * The Diagnose Action Command. + */ +typedef struct ac_dgn_t ac_dgn_t; +struct ac_dgn_t +{ + ach_t dgn_h; +}; + +/* + * Transmit Buffer Descriptor (TBD). + */ +typedef struct tbd_t tbd_t; +struct tbd_t +{ + unsigned short tbd_status; /* Written by the CPU */ +#define TBD_STATUS_EOF (0x1 << 15) /* This TBD is the */ + /* last for this frame */ +#define TBD_STATUS_ACNT (0x3FFF << 0) /* Actual count of data */ + /* bytes in this buffer */ + unsigned short tbd_next_bd_offset; /* Next in list */ + unsigned short tbd_bufl; /* Buffer address (low) */ + unsigned short tbd_bufh; /* " " (high) */ +}; + +/* + * Receive Buffer Descriptor (RBD). + */ +typedef struct rbd_t rbd_t; +struct rbd_t +{ + unsigned short rbd_status; /* Written by the 82586 */ +#define RBD_STATUS_EOF (0x1 << 15) /* This RBD is the */ + /* last for this frame */ +#define RBD_STATUS_F (0x1 << 14) /* ACNT field is valid */ +#define RBD_STATUS_ACNT (0x3FFF << 0) /* Actual no. of data */ + /* bytes in this buffer */ + unsigned short rbd_next_rbd_offset; /* Next rbd in list */ + unsigned short rbd_bufl; /* Data pointer (low) */ + unsigned short rbd_bufh; /* " " (high) */ + unsigned short rbd_el_size; /* EL+Data buf. size */ +#define RBD_EL (0x1 << 15) /* This BD is the */ + /* last in the list */ +#define RBD_SIZE (0x3FFF << 0) /* No. of bytes the */ + /* buffer can hold */ +}; + +#define rbdoff(p,f) toff(rbd_t, p, f) + +/* + * Frame Descriptor (FD). + */ +typedef struct fd_t fd_t; +struct fd_t +{ + unsigned short fd_status; /* Written by the 82586 */ +#define FD_STATUS_C (0x1 << 15) /* Completed storing frame */ +#define FD_STATUS_B (0x1 << 14) /* FD was consumed by RU */ +#define FD_STATUS_OK (0x1 << 13) /* Frame rxd successfully */ +#define FD_STATUS_S11 (0x1 << 11) /* CRC error */ +#define FD_STATUS_S10 (0x1 << 10) /* Alignment error */ +#define FD_STATUS_S9 (0x1 << 9) /* Ran out of resources */ +#define FD_STATUS_S8 (0x1 << 8) /* Rx DMA overrun */ +#define FD_STATUS_S7 (0x1 << 7) /* Frame too short */ +#define FD_STATUS_S6 (0x1 << 6) /* No EOF flag */ + unsigned short fd_command; /* Command */ +#define FD_COMMAND_EL (0x1 << 15) /* Last FD in list */ +#define FD_COMMAND_S (0x1 << 14) /* Suspend RU after rx */ + unsigned short fd_link_offset; /* Next FD */ + unsigned short fd_rbd_offset; /* First RBD (data) */ + /* Prepared by CPU, */ + /* updated by 82586 */ +#if 0 +I think the rest is unused since we +have set AC_CFG_ALOC(..). However, just +in case, we leave the space. +#endif /* 0 */ + unsigned char fd_dest[ADDR_LEN]; /* Destination address */ + /* Written by 82586 */ + unsigned char fd_src[ADDR_LEN]; /* Source address */ + /* Written by 82586 */ + unsigned short fd_length; /* Frame length or type */ + /* Written by 82586 */ +}; + +#define fdoff(p,f) toff(fd_t, p, f) + +/* + * This software may only be used and distributed + * according to the terms of the GNU Public License. + * + * For more details, see wavelan.c. + */ diff --git a/linux/src/drivers/net/iow.h b/linux/src/drivers/net/iow.h new file mode 100644 index 00000000..6e15688f --- /dev/null +++ b/linux/src/drivers/net/iow.h @@ -0,0 +1,6 @@ +#ifndef _ASM_IOW_H +#define _ASM_IOW_H + +/* no longer used */ + +#endif diff --git a/linux/src/drivers/net/lance.c b/linux/src/drivers/net/lance.c new file mode 100644 index 00000000..f64f0fee --- /dev/null +++ b/linux/src/drivers/net/lance.c @@ -0,0 +1,1293 @@ +/* lance.c: An AMD LANCE/PCnet ethernet driver for Linux. */ +/* + Written/copyright 1993-1998 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + This driver is for the Allied Telesis AT1500 and HP J2405A, and should work + with most other LANCE-based bus-master (NE2100/NE2500) ethercards. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Fixing alignment problem with 1.3.* kernel and some minor changes + by Andrey V. Savochkin, 1996. + + Problems or questions may be send to Donald Becker (see above) or to + Andrey Savochkin -- saw@shade.msu.ru or + Laboratory of Computation Methods, + Department of Mathematics and Mechanics, + Moscow State University, + Leninskye Gory, Moscow 119899 + + But I should to inform you that I'm not an expert in the LANCE card + and it may occurs that you will receive no answer on your mail + to Donald Becker. I didn't receive any answer on all my letters + to him. Who knows why... But may be you are more lucky? ;-> + SAW + + Thomas Bogendoerfer (tsbogend@bigbug.franken.de): + - added support for Linux/Alpha, but removed most of it, because + it worked only for the PCI chip. + - added hook for the 32bit lance driver + - added PCnetPCI II (79C970A) to chip table + Paul Gortmaker (gpg109@rsphy1.anu.edu.au): + - hopefully fix above so Linux/Alpha can use ISA cards too. + 8/20/96 Fixed 7990 autoIRQ failure and reversed unneeded alignment -djb + v1.12 10/27/97 Module support -djb + v1.14 2/3/98 Module support modified, made PCI support optional -djb +*/ + +static const char *version = "lance.c:v1.14 2/3/1998 dplatt@3do.com, becker@cesdis.gsfc.nasa.gov\n"; + +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +static unsigned int lance_portlist[] = { 0x300, 0x320, 0x340, 0x360, 0}; +int lance_probe(struct device *dev); +int lance_probe1(struct device *dev, int ioaddr, int irq, int options); + +#ifdef HAVE_DEVLIST +struct netdev_entry lance_drv = +{"lance", lance_probe1, LANCE_TOTAL_SIZE, lance_portlist}; +#endif + +#ifdef LANCE_DEBUG +int lance_debug = LANCE_DEBUG; +#else +int lance_debug = 1; +#endif + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the AMD 79C960, the "PCnet-ISA +single-chip ethernet controller for ISA". This chip is used in a wide +variety of boards from vendors such as Allied Telesis, HP, Kingston, +and Boca. This driver is also intended to work with older AMD 7990 +designs, such as the NE1500 and NE2100, and newer 79C961. For convenience, +I use the name LANCE to refer to all of the AMD chips, even though it properly +refers only to the original 7990. + +II. Board-specific settings + +The driver is designed to work the boards that use the faster +bus-master mode, rather than in shared memory mode. (Only older designs +have on-board buffer memory needed to support the slower shared memory mode.) + +Most ISA boards have jumpered settings for the I/O base, IRQ line, and DMA +channel. This driver probes the likely base addresses: +{0x300, 0x320, 0x340, 0x360}. +After the board is found it generates a DMA-timeout interrupt and uses +autoIRQ to find the IRQ line. The DMA channel can be set with the low bits +of the otherwise-unused dev->mem_start value (aka PARAM1). If unset it is +probed for by enabling each free DMA channel in turn and checking if +initialization succeeds. + +The HP-J2405A board is an exception: with this board it is easy to read the +EEPROM-set values for the base, IRQ, and DMA. (Of course you must already +_know_ the base address -- that field is for writing the EEPROM.) + +III. Driver operation + +IIIa. Ring buffers +The LANCE uses ring buffers of Tx and Rx descriptors. Each entry describes +the base and length of the data buffer, along with status bits. The length +of these buffers is set by LANCE_LOG_{RX,TX}_BUFFERS, which is log_2() of +the buffer length (rather than being directly the buffer length) for +implementation ease. The current values are 2 (Tx) and 4 (Rx), which leads to +ring sizes of 4 (Tx) and 16 (Rx). Increasing the number of ring entries +needlessly uses extra space and reduces the chance that an upper layer will +be able to reorder queued Tx packets based on priority. Decreasing the number +of entries makes it more difficult to achieve back-to-back packet transmission +and increases the chance that Rx ring will overflow. (Consider the worst case +of receiving back-to-back minimum-sized packets.) + +The LANCE has the capability to "chain" both Rx and Tx buffers, but this driver +statically allocates full-sized (slightly oversized -- PKT_BUF_SZ) buffers to +avoid the administrative overhead. For the Rx side this avoids dynamically +allocating full-sized buffers "just in case", at the expense of a +memory-to-memory data copy for each packet received. For most systems this +is a good tradeoff: the Rx buffer will always be in low memory, the copy +is inexpensive, and it primes the cache for later packet processing. For Tx +the buffers are only used when needed as low-memory bounce buffers. + +IIIB. 16M memory limitations. +For the ISA bus master mode all structures used directly by the LANCE, +the initialization block, Rx and Tx rings, and data buffers, must be +accessible from the ISA bus, i.e. in the lower 16M of real memory. +This is a problem for current Linux kernels on >16M machines. The network +devices are initialized after memory initialization, and the kernel doles out +memory from the top of memory downward. The current solution is to have a +special network initialization routine that's called before memory +initialization; this will eventually be generalized for all network devices. +As mentioned before, low-memory "bounce-buffers" are used when needed. + +IIIC. Synchronization +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +The send packet thread has partial control over the Tx ring and 'dev->tbusy' +flag. It sets the tbusy flag whenever it's queuing a Tx packet. If the next +queue slot is empty, it clears the tbusy flag when finished otherwise it sets +the 'lp->tx_full' flag. + +The interrupt handler has exclusive control over the Rx ring and records stats +from the Tx ring. (The Tx-done interrupt can't be selectively turned off, so +we can't avoid the interrupt overhead by having the Tx routine reap the Tx +stats.) After reaping the stats, it marks the queue entry as empty by setting +the 'base' to zero. Iff the 'lp->tx_full' flag is set, it clears both the +tx_full and tbusy flags. + +*/ + +/* Set the number of Tx and Rx buffers, using Log_2(# buffers). + Reasonable default values are 16 Tx buffers, and 16 Rx buffers. + That translates to 4 and 4 (16 == 2^^4). + This is a compile-time option for efficiency. + */ +#ifndef LANCE_LOG_TX_BUFFERS +#define LANCE_LOG_TX_BUFFERS 4 +#define LANCE_LOG_RX_BUFFERS 4 +#endif + +#define TX_RING_SIZE (1 << (LANCE_LOG_TX_BUFFERS)) +#define TX_RING_MOD_MASK (TX_RING_SIZE - 1) +#define TX_RING_LEN_BITS ((LANCE_LOG_TX_BUFFERS) << 29) + +#define RX_RING_SIZE (1 << (LANCE_LOG_RX_BUFFERS)) +#define RX_RING_MOD_MASK (RX_RING_SIZE - 1) +#define RX_RING_LEN_BITS ((LANCE_LOG_RX_BUFFERS) << 29) + +#define PKT_BUF_SZ 1544 + +/* Offsets from base I/O address. */ +#define LANCE_DATA 0x10 +#define LANCE_ADDR 0x12 +#define LANCE_RESET 0x14 +#define LANCE_BUS_IF 0x16 +#define LANCE_TOTAL_SIZE 0x18 + +/* The LANCE Rx and Tx ring descriptors. */ +struct lance_rx_head { + s32 base; + s16 buf_length; /* This length is 2s complement (negative)! */ + s16 msg_length; /* This length is "normal". */ +}; + +struct lance_tx_head { + s32 base; + s16 length; /* Length is 2s complement (negative)! */ + s16 misc; +}; + +/* The LANCE initialization block, described in databook. */ +struct lance_init_block { + u16 mode; /* Pre-set mode (reg. 15) */ + u8 phys_addr[6]; /* Physical ethernet address */ + u32 filter[2]; /* Multicast filter (unused). */ + /* Receive and transmit ring base, along with extra bits. */ + u32 rx_ring; /* Tx and Rx ring base pointers */ + u32 tx_ring; +}; + +struct lance_private { + /* The Tx and Rx ring entries must be aligned on 8-byte boundaries. */ + struct lance_rx_head rx_ring[RX_RING_SIZE]; + struct lance_tx_head tx_ring[TX_RING_SIZE]; + struct lance_init_block init_block; + const char *name; + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + /* The addresses of receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + unsigned long rx_buffs; /* Address of Rx and Tx buffers. */ + /* Tx low-memory "bounce buffer" address. */ + char (*tx_bounce_buffs)[PKT_BUF_SZ]; + int cur_rx, cur_tx; /* The next free ring entry */ + int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + int dma; + struct enet_statistics stats; + unsigned char chip_version; /* See lance_chip_type. */ + char tx_full; + unsigned long lock; +}; + +#define LANCE_MUST_PAD 0x00000001 +#define LANCE_ENABLE_AUTOSELECT 0x00000002 +#define LANCE_MUST_REINIT_RING 0x00000004 +#define LANCE_MUST_UNRESET 0x00000008 +#define LANCE_HAS_MISSED_FRAME 0x00000010 + +/* A mapping from the chip ID number to the part number and features. + These are from the datasheets -- in real life the '970 version + reportedly has the same ID as the '965. */ +static struct lance_chip_type { + int id_number; + const char *name; + int flags; +} chip_table[] = { + {0x0000, "LANCE 7990", /* Ancient lance chip. */ + LANCE_MUST_PAD + LANCE_MUST_UNRESET}, + {0x0003, "PCnet/ISA 79C960", /* 79C960 PCnet/ISA. */ + LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + + LANCE_HAS_MISSED_FRAME}, + {0x2260, "PCnet/ISA+ 79C961", /* 79C961 PCnet/ISA+, Plug-n-Play. */ + LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + + LANCE_HAS_MISSED_FRAME}, + {0x2420, "PCnet/PCI 79C970", /* 79C970 or 79C974 PCnet-SCSI, PCI. */ + LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + + LANCE_HAS_MISSED_FRAME}, + /* Bug: the PCnet/PCI actually uses the PCnet/VLB ID number, so just call + it the PCnet32. */ + {0x2430, "PCnet32", /* 79C965 PCnet for VL bus. */ + LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + + LANCE_HAS_MISSED_FRAME}, + {0x2621, "PCnet/PCI-II 79C970A", /* 79C970A PCInetPCI II. */ + LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + + LANCE_HAS_MISSED_FRAME}, + {0x0, "PCnet (unknown)", + LANCE_ENABLE_AUTOSELECT + LANCE_MUST_REINIT_RING + + LANCE_HAS_MISSED_FRAME}, +}; + +enum {OLD_LANCE = 0, PCNET_ISA=1, PCNET_ISAP=2, PCNET_PCI=3, PCNET_VLB=4, PCNET_PCI_II=5, LANCE_UNKNOWN=6}; + +/* Non-zero only if the current card is a PCI with BIOS-set IRQ. */ +static unsigned char pci_irq_line = 0; + +/* Non-zero if lance_probe1() needs to allocate low-memory bounce buffers. + Assume yes until we know the memory size. */ +static unsigned char lance_need_isa_bounce_buffers = 1; + +static int lance_open(struct device *dev); +static int lance_open_fail(struct device *dev); +static void lance_init_ring(struct device *dev, int mode); +static int lance_start_xmit(struct sk_buff *skb, struct device *dev); +static int lance_rx(struct device *dev); +static void lance_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int lance_close(struct device *dev); +static struct enet_statistics *lance_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev); + + + +#ifdef MODULE +#define MAX_CARDS 8 /* Max number of interfaces (cards) per module */ +#define IF_NAMELEN 8 /* # of chars for storing dev->name */ + +static int io[MAX_CARDS] = { 0, }; +static int dma[MAX_CARDS] = { 0, }; +static int irq[MAX_CARDS] = { 0, }; + +static char ifnames[MAX_CARDS][IF_NAMELEN] = { {0, }, }; +static struct device dev_lance[MAX_CARDS] = +{{ + 0, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL}}; + +int init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_CARDS; this_dev++) { + struct device *dev = &dev_lance[this_dev]; + dev->name = ifnames[this_dev]; + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->dma = dma[this_dev]; + dev->init = lance_probe; + if (io[this_dev] == 0) { + if (this_dev != 0) break; /* only complain once */ + printk(KERN_NOTICE "lance.c: Module autoprobing not allowed. Append \"io=0xNNN\" value(s).\n"); + return -EPERM; + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "lance.c: No PCnet/LANCE card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_CARDS; this_dev++) { + struct device *dev = &dev_lance[this_dev]; + if (dev->priv != NULL) { + kfree(dev->priv); + dev->priv = NULL; + free_dma(dev->dma); + release_region(dev->base_addr, LANCE_TOTAL_SIZE); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + +/* Starting in v2.1.*, the LANCE/PCnet probe is now similar to the other + board probes now that kmalloc() can allocate ISA DMA-able regions. + This also allows the LANCE driver to be used as a module. + */ +int lance_probe(struct device *dev) +{ + int *port, result; + + if (high_memory <= 16*1024*1024) + lance_need_isa_bounce_buffers = 0; + +#if defined(CONFIG_PCI) + if (pcibios_present()) { + int pci_index; + if (lance_debug > 1) + printk("lance.c: PCI bios is present, checking for devices...\n"); + for (pci_index = 0; pci_index < 8; pci_index++) { + unsigned char pci_bus, pci_device_fn; + unsigned int pci_ioaddr; + unsigned short pci_command; + + if (pcibios_find_device (PCI_VENDOR_ID_AMD, + PCI_DEVICE_ID_AMD_LANCE, pci_index, + &pci_bus, &pci_device_fn) != 0) + break; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + /* PCI Spec 2.1 states that it is either the driver or PCI card's + * responsibility to set the PCI Master Enable Bit if needed. + * (From Mark Stockton <marks@schooner.sys.hou.compaq.com>) + */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + if ( ! (pci_command & PCI_COMMAND_MASTER)) { + printk("PCI Master Bit has not been set. Setting...\n"); + pci_command |= PCI_COMMAND_MASTER; + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, pci_command); + } + printk("Found PCnet/PCI at %#x, irq %d.\n", + pci_ioaddr, pci_irq_line); + result = lance_probe1(dev, pci_ioaddr, pci_irq_line, 0); + pci_irq_line = 0; + if (!result) return 0; + } + } +#endif /* defined(CONFIG_PCI) */ + + for (port = lance_portlist; *port; port++) { + int ioaddr = *port; + + if ( check_region(ioaddr, LANCE_TOTAL_SIZE) == 0) { + /* Detect "normal" 0x57 0x57 and the NI6510EB 0x52 0x44 + signatures w/ minimal I/O reads */ + char offset15, offset14 = inb(ioaddr + 14); + + if ((offset14 == 0x52 || offset14 == 0x57) && + ((offset15 = inb(ioaddr + 15)) == 0x57 || offset15 == 0x44)) { + result = lance_probe1(dev, ioaddr, 0, 0); + if ( !result ) return 0; + } + } + } + return -ENODEV; +} + +int lance_probe1(struct device *dev, int ioaddr, int irq, int options) +{ + struct lance_private *lp; + short dma_channels; /* Mark spuriously-busy DMA channels */ + int i, reset_val, lance_version; + const char *chipname; + /* Flags for specific chips or boards. */ + unsigned char hpJ2405A = 0; /* HP ISA adaptor */ + int hp_builtin = 0; /* HP on-board ethernet. */ + static int did_version = 0; /* Already printed version info. */ + + /* First we look for special cases. + Check for HP's on-board ethernet by looking for 'HP' in the BIOS. + There are two HP versions, check the BIOS for the configuration port. + This method provided by L. Julliard, Laurent_Julliard@grenoble.hp.com. + */ + if (readw(0x000f0102) == 0x5048) { + static const short ioaddr_table[] = { 0x300, 0x320, 0x340, 0x360}; + int hp_port = (readl(0x000f00f1) & 1) ? 0x499 : 0x99; + /* We can have boards other than the built-in! Verify this is on-board. */ + if ((inb(hp_port) & 0xc0) == 0x80 + && ioaddr_table[inb(hp_port) & 3] == ioaddr) + hp_builtin = hp_port; + } + /* We also recognize the HP Vectra on-board here, but check below. */ + hpJ2405A = (inb(ioaddr) == 0x08 && inb(ioaddr+1) == 0x00 + && inb(ioaddr+2) == 0x09); + + /* Reset the LANCE. */ + reset_val = inw(ioaddr+LANCE_RESET); /* Reset the LANCE */ + + /* The Un-Reset needed is only needed for the real NE2100, and will + confuse the HP board. */ + if (!hpJ2405A) + outw(reset_val, ioaddr+LANCE_RESET); + + outw(0x0000, ioaddr+LANCE_ADDR); /* Switch to window 0 */ + if (inw(ioaddr+LANCE_DATA) != 0x0004) + return -ENODEV; + + /* Get the version of the chip. */ + outw(88, ioaddr+LANCE_ADDR); + if (inw(ioaddr+LANCE_ADDR) != 88) { + lance_version = 0; + } else { /* Good, it's a newer chip. */ + int chip_version = inw(ioaddr+LANCE_DATA); + outw(89, ioaddr+LANCE_ADDR); + chip_version |= inw(ioaddr+LANCE_DATA) << 16; + if (lance_debug > 2) + printk(" LANCE chip version is %#x.\n", chip_version); + if ((chip_version & 0xfff) != 0x003) + return -ENODEV; + chip_version = (chip_version >> 12) & 0xffff; + for (lance_version = 1; chip_table[lance_version].id_number; lance_version++) { + if (chip_table[lance_version].id_number == chip_version) + break; + } + } + + /* We can't use init_etherdev() to allocate dev->priv because it must + a ISA DMA-able region. */ + dev = init_etherdev(dev, 0); + dev->open = lance_open_fail; + chipname = chip_table[lance_version].name; + printk("%s: %s at %#3x,", dev->name, chipname, ioaddr); + + /* There is a 16 byte station address PROM at the base address. + The first six bytes are the station address. */ + for (i = 0; i < 6; i++) + printk(" %2.2x", dev->dev_addr[i] = inb(ioaddr + i)); + + dev->base_addr = ioaddr; + request_region(ioaddr, LANCE_TOTAL_SIZE, chip_table[lance_version].name); + + /* Make certain the data structures used by the LANCE are aligned and DMAble. */ + + lp = (struct lance_private *)(((unsigned long)kmalloc(sizeof(*lp)+7, + GFP_DMA | GFP_KERNEL)+7) & ~7); + if (lance_debug > 6) printk(" (#0x%05lx)", (unsigned long)lp); + memset(lp, 0, sizeof(*lp)); + dev->priv = lp; + lp->name = chipname; + lp->rx_buffs = (unsigned long)kmalloc(PKT_BUF_SZ*RX_RING_SIZE, + GFP_DMA | GFP_KERNEL); + if (lance_need_isa_bounce_buffers) + lp->tx_bounce_buffs = kmalloc(PKT_BUF_SZ*TX_RING_SIZE, + GFP_DMA | GFP_KERNEL); + else + lp->tx_bounce_buffs = NULL; + + lp->chip_version = lance_version; + + lp->init_block.mode = 0x0003; /* Disable Rx and Tx. */ + for (i = 0; i < 6; i++) + lp->init_block.phys_addr[i] = dev->dev_addr[i]; + lp->init_block.filter[0] = 0x00000000; + lp->init_block.filter[1] = 0x00000000; + lp->init_block.rx_ring = ((u32)virt_to_bus(lp->rx_ring) & 0xffffff) | RX_RING_LEN_BITS; + lp->init_block.tx_ring = ((u32)virt_to_bus(lp->tx_ring) & 0xffffff) | TX_RING_LEN_BITS; + + outw(0x0001, ioaddr+LANCE_ADDR); + inw(ioaddr+LANCE_ADDR); + outw((short) (u32) virt_to_bus(&lp->init_block), ioaddr+LANCE_DATA); + outw(0x0002, ioaddr+LANCE_ADDR); + inw(ioaddr+LANCE_ADDR); + outw(((u32)virt_to_bus(&lp->init_block)) >> 16, ioaddr+LANCE_DATA); + outw(0x0000, ioaddr+LANCE_ADDR); + inw(ioaddr+LANCE_ADDR); + + if (irq) { /* Set iff PCI card. */ + dev->dma = 4; /* Native bus-master, no DMA channel needed. */ + dev->irq = irq; + } else if (hp_builtin) { + static const char dma_tbl[4] = {3, 5, 6, 0}; + static const char irq_tbl[4] = {3, 4, 5, 9}; + unsigned char port_val = inb(hp_builtin); + dev->dma = dma_tbl[(port_val >> 4) & 3]; + dev->irq = irq_tbl[(port_val >> 2) & 3]; + printk(" HP Vectra IRQ %d DMA %d.\n", dev->irq, dev->dma); + } else if (hpJ2405A) { + static const char dma_tbl[4] = {3, 5, 6, 7}; + static const char irq_tbl[8] = {3, 4, 5, 9, 10, 11, 12, 15}; + short reset_val = inw(ioaddr+LANCE_RESET); + dev->dma = dma_tbl[(reset_val >> 2) & 3]; + dev->irq = irq_tbl[(reset_val >> 4) & 7]; + printk(" HP J2405A IRQ %d DMA %d.\n", dev->irq, dev->dma); + } else if (lance_version == PCNET_ISAP) { /* The plug-n-play version. */ + short bus_info; + outw(8, ioaddr+LANCE_ADDR); + bus_info = inw(ioaddr+LANCE_BUS_IF); + dev->dma = bus_info & 0x07; + dev->irq = (bus_info >> 4) & 0x0F; + } else { + /* The DMA channel may be passed in PARAM1. */ + if (dev->mem_start & 0x07) + dev->dma = dev->mem_start & 0x07; + } + + if (dev->dma == 0) { + /* Read the DMA channel status register, so that we can avoid + stuck DMA channels in the DMA detection below. */ + dma_channels = ((inb(DMA1_STAT_REG) >> 4) & 0x0f) | + (inb(DMA2_STAT_REG) & 0xf0); + } + if (dev->irq >= 2) + printk(" assigned IRQ %d", dev->irq); + else if (lance_version != 0) { /* 7990 boards need DMA detection first. */ + /* To auto-IRQ we enable the initialization-done and DMA error + interrupts. For ISA boards we get a DMA error, but VLB and PCI + boards will work. */ + autoirq_setup(0); + + /* Trigger an initialization just for the interrupt. */ + outw(0x0041, ioaddr+LANCE_DATA); + + dev->irq = autoirq_report(2); + if (dev->irq) + printk(", probed IRQ %d", dev->irq); + else { + printk(", failed to detect IRQ line.\n"); + return -ENODEV; + } + + /* Check for the initialization done bit, 0x0100, which means + that we don't need a DMA channel. */ + if (inw(ioaddr+LANCE_DATA) & 0x0100) + dev->dma = 4; + } + + if (dev->dma == 4) { + printk(", no DMA needed.\n"); + } else if (dev->dma) { + if (request_dma(dev->dma, chipname)) { + printk("DMA %d allocation failed.\n", dev->dma); + return -ENODEV; + } else + printk(", assigned DMA %d.\n", dev->dma); + } else { /* OK, we have to auto-DMA. */ + for (i = 0; i < 4; i++) { + static const char dmas[] = { 5, 6, 7, 3 }; + int dma = dmas[i]; + int boguscnt; + + /* Don't enable a permanently busy DMA channel, or the machine + will hang. */ + if (test_bit(dma, &dma_channels)) + continue; + outw(0x7f04, ioaddr+LANCE_DATA); /* Clear the memory error bits. */ + if (request_dma(dma, chipname)) + continue; + set_dma_mode(dma, DMA_MODE_CASCADE); + enable_dma(dma); + + /* Trigger an initialization. */ + outw(0x0001, ioaddr+LANCE_DATA); + for (boguscnt = 100; boguscnt > 0; --boguscnt) + if (inw(ioaddr+LANCE_DATA) & 0x0900) + break; + if (inw(ioaddr+LANCE_DATA) & 0x0100) { + dev->dma = dma; + printk(", DMA %d.\n", dev->dma); + break; + } else { + disable_dma(dma); + free_dma(dma); + } + } + if (i == 4) { /* Failure: bail. */ + printk("DMA detection failed.\n"); + return -ENODEV; + } + } + + if (lance_version == 0 && dev->irq == 0) { + /* We may auto-IRQ now that we have a DMA channel. */ + /* Trigger an initialization just for the interrupt. */ + autoirq_setup(0); + outw(0x0041, ioaddr+LANCE_DATA); + + dev->irq = autoirq_report(4); + if (dev->irq == 0) { + printk(" Failed to detect the 7990 IRQ line.\n"); + return -ENODEV; + } + printk(" Auto-IRQ detected IRQ%d.\n", dev->irq); + } + + if (chip_table[lp->chip_version].flags & LANCE_ENABLE_AUTOSELECT) { + /* Turn on auto-select of media (10baseT or BNC) so that the user + can watch the LEDs even if the board isn't opened. */ + outw(0x0002, ioaddr+LANCE_ADDR); + /* Don't touch 10base2 power bit. */ + outw(inw(ioaddr+LANCE_BUS_IF) | 0x0002, ioaddr+LANCE_BUS_IF); + } + + if (lance_debug > 0 && did_version++ == 0) + printk(version); + + /* The LANCE-specific entries in the device structure. */ + dev->open = lance_open; + dev->hard_start_xmit = lance_start_xmit; + dev->stop = lance_close; + dev->get_stats = lance_get_stats; + dev->set_multicast_list = set_multicast_list; + + return 0; +} + +static int +lance_open_fail(struct device *dev) +{ + return -ENODEV; +} + + + +static int +lance_open(struct device *dev) +{ + struct lance_private *lp = (struct lance_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + if (dev->irq == 0 || + request_irq(dev->irq, &lance_interrupt, 0, lp->name, dev)) { + return -EAGAIN; + } + + MOD_INC_USE_COUNT; + + /* We used to allocate DMA here, but that was silly. + DMA lines can't be shared! We now permanently allocate them. */ + + /* Reset the LANCE */ + inw(ioaddr+LANCE_RESET); + + /* The DMA controller is used as a no-operation slave, "cascade mode". */ + if (dev->dma != 4) { + enable_dma(dev->dma); + set_dma_mode(dev->dma, DMA_MODE_CASCADE); + } + + /* Un-Reset the LANCE, needed only for the NE2100. */ + if (chip_table[lp->chip_version].flags & LANCE_MUST_UNRESET) + outw(0, ioaddr+LANCE_RESET); + + if (chip_table[lp->chip_version].flags & LANCE_ENABLE_AUTOSELECT) { + /* This is 79C960-specific: Turn on auto-select of media (AUI, BNC). */ + outw(0x0002, ioaddr+LANCE_ADDR); + /* Only touch autoselect bit. */ + outw(inw(ioaddr+LANCE_BUS_IF) | 0x0002, ioaddr+LANCE_BUS_IF); + } + + if (lance_debug > 1) + printk("%s: lance_open() irq %d dma %d tx/rx rings %#x/%#x init %#x.\n", + dev->name, dev->irq, dev->dma, + (u32) virt_to_bus(lp->tx_ring), + (u32) virt_to_bus(lp->rx_ring), + (u32) virt_to_bus(&lp->init_block)); + + lance_init_ring(dev, GFP_KERNEL); + /* Re-initialize the LANCE, and start it when done. */ + outw(0x0001, ioaddr+LANCE_ADDR); + outw((short) (u32) virt_to_bus(&lp->init_block), ioaddr+LANCE_DATA); + outw(0x0002, ioaddr+LANCE_ADDR); + outw(((u32)virt_to_bus(&lp->init_block)) >> 16, ioaddr+LANCE_DATA); + + outw(0x0004, ioaddr+LANCE_ADDR); + outw(0x0915, ioaddr+LANCE_DATA); + + outw(0x0000, ioaddr+LANCE_ADDR); + outw(0x0001, ioaddr+LANCE_DATA); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + i = 0; + while (i++ < 100) + if (inw(ioaddr+LANCE_DATA) & 0x0100) + break; + /* + * We used to clear the InitDone bit, 0x0100, here but Mark Stockton + * reports that doing so triggers a bug in the '974. + */ + outw(0x0042, ioaddr+LANCE_DATA); + + if (lance_debug > 2) + printk("%s: LANCE open after %d ticks, init block %#x csr0 %4.4x.\n", + dev->name, i, (u32) virt_to_bus(&lp->init_block), inw(ioaddr+LANCE_DATA)); + + return 0; /* Always succeed */ +} + +/* The LANCE has been halted for one reason or another (busmaster memory + arbitration error, Tx FIFO underflow, driver stopped it to reconfigure, + etc.). Modern LANCE variants always reload their ring-buffer + configuration when restarted, so we must reinitialize our ring + context before restarting. As part of this reinitialization, + find all packets still on the Tx ring and pretend that they had been + sent (in effect, drop the packets on the floor) - the higher-level + protocols will time out and retransmit. It'd be better to shuffle + these skbs to a temp list and then actually re-Tx them after + restarting the chip, but I'm too lazy to do so right now. dplatt@3do.com +*/ + +static void +lance_purge_tx_ring(struct device *dev) +{ + struct lance_private *lp = (struct lance_private *)dev->priv; + int i; + + for (i = 0; i < TX_RING_SIZE; i++) { + if (lp->tx_skbuff[i]) { + dev_kfree_skb(lp->tx_skbuff[i],FREE_WRITE); + lp->tx_skbuff[i] = NULL; + } + } +} + + +/* Initialize the LANCE Rx and Tx rings. */ +static void +lance_init_ring(struct device *dev, int gfp) +{ + struct lance_private *lp = (struct lance_private *)dev->priv; + int i; + + lp->lock = 0, lp->tx_full = 0; + lp->cur_rx = lp->cur_tx = 0; + lp->dirty_rx = lp->dirty_tx = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb; + void *rx_buff; + + skb = alloc_skb(PKT_BUF_SZ, GFP_DMA | gfp); + lp->rx_skbuff[i] = skb; + if (skb) { + skb->dev = dev; + rx_buff = skb->tail; + } else + rx_buff = kmalloc(PKT_BUF_SZ, GFP_DMA | gfp); + if (rx_buff == NULL) + lp->rx_ring[i].base = 0; + else + lp->rx_ring[i].base = (u32)virt_to_bus(rx_buff) | 0x80000000; + lp->rx_ring[i].buf_length = -PKT_BUF_SZ; + } + /* The Tx buffer address is filled in as needed, but we do need to clear + the upper ownership bit. */ + for (i = 0; i < TX_RING_SIZE; i++) { + lp->tx_skbuff[i] = 0; + lp->tx_ring[i].base = 0; + } + + lp->init_block.mode = 0x0000; + for (i = 0; i < 6; i++) + lp->init_block.phys_addr[i] = dev->dev_addr[i]; + lp->init_block.filter[0] = 0x00000000; + lp->init_block.filter[1] = 0x00000000; + lp->init_block.rx_ring = ((u32)virt_to_bus(lp->rx_ring) & 0xffffff) | RX_RING_LEN_BITS; + lp->init_block.tx_ring = ((u32)virt_to_bus(lp->tx_ring) & 0xffffff) | TX_RING_LEN_BITS; +} + +static void +lance_restart(struct device *dev, unsigned int csr0_bits, int must_reinit) +{ + struct lance_private *lp = (struct lance_private *)dev->priv; + + if (must_reinit || + (chip_table[lp->chip_version].flags & LANCE_MUST_REINIT_RING)) { + lance_purge_tx_ring(dev); + lance_init_ring(dev, GFP_ATOMIC); + } + outw(0x0000, dev->base_addr + LANCE_ADDR); + outw(csr0_bits, dev->base_addr + LANCE_DATA); +} + +static int +lance_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct lance_private *lp = (struct lance_private *)dev->priv; + int ioaddr = dev->base_addr; + int entry; + unsigned long flags; + + /* Transmitter timeout, serious problems. */ + if (dev->tbusy) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 20) + return 1; + outw(0, ioaddr+LANCE_ADDR); + printk("%s: transmit timed out, status %4.4x, resetting.\n", + dev->name, inw(ioaddr+LANCE_DATA)); + outw(0x0004, ioaddr+LANCE_DATA); + lp->stats.tx_errors++; +#ifndef final_version + { + int i; + printk(" Ring data dump: dirty_tx %d cur_tx %d%s cur_rx %d.", + lp->dirty_tx, lp->cur_tx, lp->tx_full ? " (full)" : "", + lp->cur_rx); + for (i = 0 ; i < RX_RING_SIZE; i++) + printk("%s %08x %04x %04x", i & 0x3 ? "" : "\n ", + lp->rx_ring[i].base, -lp->rx_ring[i].buf_length, + lp->rx_ring[i].msg_length); + for (i = 0 ; i < TX_RING_SIZE; i++) + printk("%s %08x %04x %04x", i & 0x3 ? "" : "\n ", + lp->tx_ring[i].base, -lp->tx_ring[i].length, + lp->tx_ring[i].misc); + printk("\n"); + } +#endif + lance_restart(dev, 0x0043, 1); + + dev->tbusy=0; + dev->trans_start = jiffies; + + return 0; + } + + if (lance_debug > 3) { + outw(0x0000, ioaddr+LANCE_ADDR); + printk("%s: lance_start_xmit() called, csr0 %4.4x.\n", dev->name, + inw(ioaddr+LANCE_DATA)); + outw(0x0000, ioaddr+LANCE_DATA); + } + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (set_bit(0, (void*)&dev->tbusy) != 0) { + printk("%s: Transmitter access conflict.\n", dev->name); + return 1; + } + + if (set_bit(0, (void*)&lp->lock) != 0) { + if (lance_debug > 0) + printk("%s: tx queue lock!.\n", dev->name); + /* don't clear dev->tbusy flag. */ + return 1; + } + + /* Fill in a Tx ring entry */ + + /* Mask to ring buffer boundary. */ + entry = lp->cur_tx & TX_RING_MOD_MASK; + + /* Caution: the write order is important here, set the base address + with the "ownership" bits last. */ + + /* The old LANCE chips doesn't automatically pad buffers to min. size. */ + if (chip_table[lp->chip_version].flags & LANCE_MUST_PAD) { + lp->tx_ring[entry].length = + -(ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN); + } else + lp->tx_ring[entry].length = -skb->len; + + lp->tx_ring[entry].misc = 0x0000; + + /* If any part of this buffer is >16M we must copy it to a low-memory + buffer. */ + if ((u32)virt_to_bus(skb->data) + skb->len > 0x01000000) { + if (lance_debug > 5) + printk("%s: bouncing a high-memory packet (%#x).\n", + dev->name, (u32)virt_to_bus(skb->data)); + memcpy(&lp->tx_bounce_buffs[entry], skb->data, skb->len); + lp->tx_ring[entry].base = + ((u32)virt_to_bus((lp->tx_bounce_buffs + entry)) & 0xffffff) | 0x83000000; + dev_kfree_skb (skb, FREE_WRITE); + } else { + lp->tx_skbuff[entry] = skb; + lp->tx_ring[entry].base = ((u32)virt_to_bus(skb->data) & 0xffffff) | 0x83000000; + } + lp->cur_tx++; + + /* Trigger an immediate send poll. */ + outw(0x0000, ioaddr+LANCE_ADDR); + outw(0x0048, ioaddr+LANCE_DATA); + + dev->trans_start = jiffies; + + save_flags(flags); + cli(); + lp->lock = 0; + if (lp->tx_ring[(entry+1) & TX_RING_MOD_MASK].base == 0) + dev->tbusy=0; + else + lp->tx_full = 1; + restore_flags(flags); + + return 0; +} + +/* The LANCE interrupt handler. */ +static void +lance_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = (struct device *)dev_id; + struct lance_private *lp; + int csr0, ioaddr, boguscnt=10; + int must_restart; + + if (dev == NULL) { + printk ("lance_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + ioaddr = dev->base_addr; + lp = (struct lance_private *)dev->priv; + if (dev->interrupt) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + + dev->interrupt = 1; + + outw(0x00, dev->base_addr + LANCE_ADDR); + while ((csr0 = inw(dev->base_addr + LANCE_DATA)) & 0x8600 + && --boguscnt >= 0) { + /* Acknowledge all of the current interrupt sources ASAP. */ + outw(csr0 & ~0x004f, dev->base_addr + LANCE_DATA); + + must_restart = 0; + + if (lance_debug > 5) + printk("%s: interrupt csr0=%#2.2x new csr=%#2.2x.\n", + dev->name, csr0, inw(dev->base_addr + LANCE_DATA)); + + if (csr0 & 0x0400) /* Rx interrupt */ + lance_rx(dev); + + if (csr0 & 0x0200) { /* Tx-done interrupt */ + int dirty_tx = lp->dirty_tx; + + while (dirty_tx < lp->cur_tx) { + int entry = dirty_tx & TX_RING_MOD_MASK; + int status = lp->tx_ring[entry].base; + + if (status < 0) + break; /* It still hasn't been Txed */ + + lp->tx_ring[entry].base = 0; + + if (status & 0x40000000) { + /* There was an major error, log it. */ + int err_status = lp->tx_ring[entry].misc; + lp->stats.tx_errors++; + if (err_status & 0x0400) lp->stats.tx_aborted_errors++; + if (err_status & 0x0800) lp->stats.tx_carrier_errors++; + if (err_status & 0x1000) lp->stats.tx_window_errors++; + if (err_status & 0x4000) { + /* Ackk! On FIFO errors the Tx unit is turned off! */ + lp->stats.tx_fifo_errors++; + /* Remove this verbosity later! */ + printk("%s: Tx FIFO error! Status %4.4x.\n", + dev->name, csr0); + /* Restart the chip. */ + must_restart = 1; + } + } else { + if (status & 0x18000000) + lp->stats.collisions++; + lp->stats.tx_packets++; + } + + /* We must free the original skb if it's not a data-only copy + in the bounce buffer. */ + if (lp->tx_skbuff[entry]) { + dev_kfree_skb(lp->tx_skbuff[entry],FREE_WRITE); + lp->tx_skbuff[entry] = 0; + } + dirty_tx++; + } + +#ifndef final_version + if (lp->cur_tx - dirty_tx >= TX_RING_SIZE) { + printk("out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dirty_tx, lp->cur_tx, lp->tx_full); + dirty_tx += TX_RING_SIZE; + } +#endif + + if (lp->tx_full && dev->tbusy + && dirty_tx > lp->cur_tx - TX_RING_SIZE + 2) { + /* The ring is no longer full, clear tbusy. */ + lp->tx_full = 0; + dev->tbusy = 0; + mark_bh(NET_BH); + } + + lp->dirty_tx = dirty_tx; + } + + /* Log misc errors. */ + if (csr0 & 0x4000) lp->stats.tx_errors++; /* Tx babble. */ + if (csr0 & 0x1000) lp->stats.rx_errors++; /* Missed a Rx frame. */ + if (csr0 & 0x0800) { + printk("%s: Bus master arbitration failure, status %4.4x.\n", + dev->name, csr0); + /* Restart the chip. */ + must_restart = 1; + } + + if (must_restart) { + /* stop the chip to clear the error condition, then restart */ + outw(0x0000, dev->base_addr + LANCE_ADDR); + outw(0x0004, dev->base_addr + LANCE_DATA); + lance_restart(dev, 0x0002, 0); + } + } + + /* Clear any other interrupt, and set interrupt enable. */ + outw(0x0000, dev->base_addr + LANCE_ADDR); + outw(0x7940, dev->base_addr + LANCE_DATA); + + if (lance_debug > 4) + printk("%s: exiting interrupt, csr%d=%#4.4x.\n", + dev->name, inw(ioaddr + LANCE_ADDR), + inw(dev->base_addr + LANCE_DATA)); + + dev->interrupt = 0; + return; +} + +static int +lance_rx(struct device *dev) +{ + struct lance_private *lp = (struct lance_private *)dev->priv; + int entry = lp->cur_rx & RX_RING_MOD_MASK; + int i; + + /* If we own the next entry, it's a new packet. Send it up. */ + while (lp->rx_ring[entry].base >= 0) { + int status = lp->rx_ring[entry].base >> 24; + + if (status != 0x03) { /* There was an error. */ + /* There is a tricky error noted by John Murphy, + <murf@perftech.com> to Russ Nelson: Even with full-sized + buffers it's possible for a jabber packet to use two + buffers, with only the last correctly noting the error. */ + if (status & 0x01) /* Only count a general error at the */ + lp->stats.rx_errors++; /* end of a packet.*/ + if (status & 0x20) lp->stats.rx_frame_errors++; + if (status & 0x10) lp->stats.rx_over_errors++; + if (status & 0x08) lp->stats.rx_crc_errors++; + if (status & 0x04) lp->stats.rx_fifo_errors++; + lp->rx_ring[entry].base &= 0x03ffffff; + } + else + { + /* Malloc up new buffer, compatible with net3. */ + short pkt_len = (lp->rx_ring[entry].msg_length & 0xfff)-4; + struct sk_buff *skb; + + if(pkt_len<60) + { + printk("%s: Runt packet!\n",dev->name); + lp->stats.rx_errors++; + } + else + { + skb = dev_alloc_skb(pkt_len+2); + if (skb == NULL) + { + printk("%s: Memory squeeze, deferring packet.\n", dev->name); + for (i=0; i < RX_RING_SIZE; i++) + if (lp->rx_ring[(entry+i) & RX_RING_MOD_MASK].base < 0) + break; + + if (i > RX_RING_SIZE -2) + { + lp->stats.rx_dropped++; + lp->rx_ring[entry].base |= 0x80000000; + lp->cur_rx++; + } + break; + } + skb->dev = dev; + skb_reserve(skb,2); /* 16 byte align */ + skb_put(skb,pkt_len); /* Make room */ + eth_copy_and_sum(skb, + (unsigned char *)bus_to_virt((lp->rx_ring[entry].base & 0x00ffffff)), + pkt_len,0); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + } + /* The docs say that the buffer length isn't touched, but Andrew Boyd + of QNX reports that some revs of the 79C965 clear it. */ + lp->rx_ring[entry].buf_length = -PKT_BUF_SZ; + lp->rx_ring[entry].base |= 0x80000000; + entry = (++lp->cur_rx) & RX_RING_MOD_MASK; + } + + /* We should check that at least two ring entries are free. If not, + we should free one and mark stats->rx_dropped++. */ + + return 0; +} + +static int +lance_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct lance_private *lp = (struct lance_private *)dev->priv; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (chip_table[lp->chip_version].flags & LANCE_HAS_MISSED_FRAME) { + outw(112, ioaddr+LANCE_ADDR); + lp->stats.rx_missed_errors = inw(ioaddr+LANCE_DATA); + } + outw(0, ioaddr+LANCE_ADDR); + + if (lance_debug > 1) + printk("%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, inw(ioaddr+LANCE_DATA)); + + /* We stop the LANCE here -- it occasionally polls + memory if we don't. */ + outw(0x0004, ioaddr+LANCE_DATA); + + if (dev->dma != 4) + disable_dma(dev->dma); + + free_irq(dev->irq, dev); + + /* Free all the skbuffs in the Rx and Tx queues. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = lp->rx_skbuff[i]; + lp->rx_skbuff[i] = 0; + lp->rx_ring[i].base = 0; /* Not owned by LANCE chip. */ + if (skb) { + skb->free = 1; + dev_kfree_skb(skb, FREE_WRITE); + } + } + for (i = 0; i < TX_RING_SIZE; i++) { + if (lp->tx_skbuff[i]) + dev_kfree_skb(lp->tx_skbuff[i], FREE_WRITE); + lp->tx_skbuff[i] = 0; + } + + MOD_DEC_USE_COUNT; + return 0; +} + +static struct enet_statistics * +lance_get_stats(struct device *dev) +{ + struct lance_private *lp = (struct lance_private *)dev->priv; + short ioaddr = dev->base_addr; + short saved_addr; + unsigned long flags; + + if (chip_table[lp->chip_version].flags & LANCE_HAS_MISSED_FRAME) { + save_flags(flags); + cli(); + saved_addr = inw(ioaddr+LANCE_ADDR); + outw(112, ioaddr+LANCE_ADDR); + lp->stats.rx_missed_errors = inw(ioaddr+LANCE_DATA); + outw(saved_addr, ioaddr+LANCE_ADDR); + restore_flags(flags); + } + + return &lp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + */ + +static void set_multicast_list(struct device *dev) +{ + short ioaddr = dev->base_addr; + + outw(0, ioaddr+LANCE_ADDR); + outw(0x0004, ioaddr+LANCE_DATA); /* Temporarily stop the lance. */ + + if (dev->flags&IFF_PROMISC) { + /* Log any net taps. */ + printk("%s: Promiscuous mode enabled.\n", dev->name); + outw(15, ioaddr+LANCE_ADDR); + outw(0x8000, ioaddr+LANCE_DATA); /* Set promiscuous mode */ + } else { + short multicast_table[4]; + int i; + int num_addrs=dev->mc_count; + if(dev->flags&IFF_ALLMULTI) + num_addrs=1; + /* FIXIT: We don't use the multicast table, but rely on upper-layer filtering. */ + memset(multicast_table, (num_addrs == 0) ? 0 : -1, sizeof(multicast_table)); + for (i = 0; i < 4; i++) { + outw(8 + i, ioaddr+LANCE_ADDR); + outw(multicast_table[i], ioaddr+LANCE_DATA); + } + outw(15, ioaddr+LANCE_ADDR); + outw(0x0000, ioaddr+LANCE_DATA); /* Unset promiscuous mode */ + } + + lance_restart(dev, 0x0142, 0); /* Resume normal operation */ + +} + + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c lance.c" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/ne.c b/linux/src/drivers/net/ne.c new file mode 100644 index 00000000..ed2efbb0 --- /dev/null +++ b/linux/src/drivers/net/ne.c @@ -0,0 +1,811 @@ +/* ne.c: A general non-shared-memory NS8390 ethernet driver for linux. */ +/* + Written 1992-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This driver should work with many programmed-I/O 8390-based ethernet + boards. Currently it supports the NE1000, NE2000, many clones, + and some Cabletron products. + + Changelog: + + Paul Gortmaker : use ENISR_RDC to monitor Tx PIO uploads, made + sanity checks and bad clone support optional. + Paul Gortmaker : new reset code, reset card after probe at boot. + Paul Gortmaker : multiple card support for module users. + Paul Gortmaker : Support for PCI ne2k clones, similar to lance.c + Paul Gortmaker : Allow users with bad cards to avoid full probe. + Paul Gortmaker : PCI probe changes, more PCI cards supported. + +*/ + +/* Routines for the NatSemi-based designs (NE[12]000). */ + +static const char *version = + "ne.c:v1.10 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include "8390.h" + +/* Some defines that people can play with if so inclined. */ + +/* Do we support clones that don't adhere to 14,15 of the SAprom ? */ +#define SUPPORT_NE_BAD_CLONES + +/* Do we perform extra sanity checks on stuff ? */ +/* #define NE_SANITY_CHECK */ + +/* Do we implement the read before write bugfix ? */ +/* #define NE_RW_BUGFIX */ + +/* Do we have a non std. amount of memory? (in units of 256 byte pages) */ +/* #define PACKETBUF_MEMSIZE 0x40 */ + +#if defined(HAVE_DEVLIST) || !defined(MODULE) +/* A zero-terminated list of I/O addresses to be probed. */ +static unsigned int netcard_portlist[] = +{ 0x300, 0x280, 0x320, 0x340, 0x360, 0}; +#endif /* defined(HAVE_DEVLIST) || !defined(MODULE) */ + +#ifdef CONFIG_PCI +/* Ack! People are making PCI ne2000 clones! Oh the horror, the horror... */ +static struct { unsigned short vendor, dev_id;} +pci_clone_list[] = { + {PCI_VENDOR_ID_REALTEK, PCI_DEVICE_ID_REALTEK_8029}, + {PCI_VENDOR_ID_WINBOND2, PCI_DEVICE_ID_WINBOND2_89C940}, + {PCI_VENDOR_ID_COMPEX, PCI_DEVICE_ID_COMPEX_RL2000}, + {PCI_VENDOR_ID_KTI, PCI_DEVICE_ID_KTI_ET32P2}, + {PCI_VENDOR_ID_NETVIN, PCI_DEVICE_ID_NETVIN_NV5000SC}, + {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C926}, + {0,} +}; +#endif + +#ifdef SUPPORT_NE_BAD_CLONES +/* A list of bad clones that we none-the-less recognize. */ +static struct { const char *name8, *name16; unsigned char SAprefix[4];} +bad_clone_list[] = { + {"DE100", "DE200", {0x00, 0xDE, 0x01,}}, + {"DE120", "DE220", {0x00, 0x80, 0xc8,}}, + {"DFI1000", "DFI2000", {'D', 'F', 'I',}}, /* Original, eh? */ + {"EtherNext UTP8", "EtherNext UTP16", {0x00, 0x00, 0x79}}, + {"NE1000","NE2000-invalid", {0x00, 0x00, 0xd8}}, /* Ancient real NE1000. */ + {"NN1000", "NN2000", {0x08, 0x03, 0x08}}, /* Outlaw no-name clone. */ + {"4-DIM8","4-DIM16", {0x00,0x00,0x4d,}}, /* Outlaw 4-Dimension cards. */ + {"Con-Intl_8", "Con-Intl_16", {0x00, 0x00, 0x24}}, /* Connect Int'nl */ + {"ET-100","ET-200", {0x00, 0x45, 0x54}}, /* YANG and YA clone */ + {"COMPEX","COMPEX16",{0x00,0x80,0x48}}, /* Broken ISA Compex cards */ + {"E-LAN100", "E-LAN200", {0x00, 0x00, 0x5d}}, /* Broken ne1000 clones */ + {0,} +}; +#endif + +/* ---- No user-serviceable parts below ---- */ + +#define NE_BASE (dev->base_addr) +#define NE_CMD 0x00 +#define NE_DATAPORT 0x10 /* NatSemi-defined port window offset. */ +#define NE_RESET 0x1f /* Issue a read to reset, a write to clear. */ +#define NE_IO_EXTENT 0x20 + +#define NE1SM_START_PG 0x20 /* First page of TX buffer */ +#define NE1SM_STOP_PG 0x40 /* Last page +1 of RX ring */ +#define NESM_START_PG 0x40 /* First page of TX buffer */ +#define NESM_STOP_PG 0x80 /* Last page +1 of RX ring */ + +/* Non-zero only if the current card is a PCI with BIOS-set IRQ. */ +static unsigned char pci_irq_line = 0; + +int ne_probe(struct device *dev); +#ifdef CONFIG_PCI +static int ne_probe_pci(struct device *dev); +#endif +static int ne_probe1(struct device *dev, int ioaddr); + +static int ne_open(struct device *dev); +static int ne_close(struct device *dev); + +static void ne_reset_8390(struct device *dev); +static void ne_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void ne_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void ne_block_output(struct device *dev, const int count, + const unsigned char *buf, const int start_page); + + +/* Probe for various non-shared-memory ethercards. + + NEx000-clone boards have a Station Address PROM (SAPROM) in the packet + buffer memory space. NE2000 clones have 0x57,0x57 in bytes 0x0e,0x0f of + the SAPROM, while other supposed NE2000 clones must be detected by their + SA prefix. + + Reading the SAPROM from a word-wide card with the 8390 set in byte-wide + mode results in doubled values, which can be detected and compensated for. + + The probe is also responsible for initializing the card and filling + in the 'dev' and 'ei_status' structures. + + We use the minimum memory size for some ethercard product lines, iff we can't + distinguish models. You can increase the packet buffer size by setting + PACKETBUF_MEMSIZE. Reported Cabletron packet buffer locations are: + E1010 starts at 0x100 and ends at 0x2000. + E1010-x starts at 0x100 and ends at 0x8000. ("-x" means "more memory") + E2010 starts at 0x100 and ends at 0x4000. + E2010-x starts at 0x100 and ends at 0xffff. */ + +#ifdef HAVE_DEVLIST +struct netdev_entry netcard_drv = +{"ne", ne_probe1, NE_IO_EXTENT, netcard_portlist}; +#else + +/* Note that this probe only picks up one card at a time, even for multiple + PCI ne2k cards. Use "ether=0,0,eth1" if you have a second PCI ne2k card. + This keeps things consistent regardless of the bus type of the card. */ + +int ne_probe(struct device *dev) +{ +#ifndef MODULE + int i; +#endif /* MODULE */ + int base_addr = dev ? dev->base_addr : 0; + + /* First check any supplied i/o locations. User knows best. <cough> */ + if (base_addr > 0x1ff) /* Check a single specified location. */ + return ne_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + +#ifdef CONFIG_PCI + /* Then look for any installed PCI clones */ + if (pcibios_present() && (ne_probe_pci(dev) == 0)) + return 0; +#endif + +#ifndef MODULE + /* Last resort. The semi-risky ISA auto-probe. */ + for (i = 0; netcard_portlist[i]; i++) { + int ioaddr = netcard_portlist[i]; + if (check_region(ioaddr, NE_IO_EXTENT)) + continue; + if (ne_probe1(dev, ioaddr) == 0) + return 0; + } +#endif + + return ENODEV; +} +#endif + +#ifdef CONFIG_PCI +static int ne_probe_pci(struct device *dev) +{ + int i; + + for (i = 0; pci_clone_list[i].vendor != 0; i++) { + unsigned char pci_bus, pci_device_fn; + unsigned int pci_ioaddr; + u16 pci_command, new_command; + int pci_index; + + for (pci_index = 0; pci_index < 8; pci_index++) { + if (pcibios_find_device (pci_clone_list[i].vendor, + pci_clone_list[i].dev_id, pci_index, + &pci_bus, &pci_device_fn) != 0) + break; /* No more of these type of cards */ + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + /* Strip the I/O address out of the returned value */ + pci_ioaddr &= PCI_BASE_ADDRESS_IO_MASK; + /* Avoid already found cards from previous calls */ + if (check_region(pci_ioaddr, NE_IO_EXTENT)) + continue; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + break; /* Beauty -- got a valid card. */ + } + if (pci_irq_line == 0) continue; /* Try next PCI ID */ + printk("ne.c: PCI BIOS reports NE 2000 clone at i/o %#x, irq %d.\n", + pci_ioaddr, pci_irq_line); + + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + + /* Activate the card: fix for brain-damaged Win98 BIOSes. */ + new_command = pci_command | PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled this" + " NE2k clone! Updating PCI command %4.4x->%4.4x.\n", + pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + + if (ne_probe1(dev, pci_ioaddr) != 0) { /* Shouldn't happen. */ + printk(KERN_ERR "ne.c: Probe of PCI card at %#x failed.\n", pci_ioaddr); + pci_irq_line = 0; + return -ENXIO; + } + pci_irq_line = 0; + return 0; + } + return -ENODEV; +} +#endif /* CONFIG_PCI */ + +static int ne_probe1(struct device *dev, int ioaddr) +{ + int i; + unsigned char SA_prom[32]; + int wordlength = 2; + const char *name = NULL; + int start_page, stop_page; + int neX000, ctron, bad_card; + int reg0 = inb_p(ioaddr); + static unsigned version_printed = 0; + + if (reg0 == 0xFF) + return ENODEV; + + /* Do a preliminary verification that we have a 8390. */ + { int regd; + outb_p(E8390_NODMA+E8390_PAGE1+E8390_STOP, ioaddr + E8390_CMD); + regd = inb_p(ioaddr + 0x0d); + outb_p(0xff, ioaddr + 0x0d); + outb_p(E8390_NODMA+E8390_PAGE0, ioaddr + E8390_CMD); + inb_p(ioaddr + EN0_COUNTER0); /* Clear the counter by reading. */ + if (inb_p(ioaddr + EN0_COUNTER0) != 0) { + outb_p(reg0, ioaddr); + outb_p(regd, ioaddr + 0x0d); /* Restore the old values. */ + return ENODEV; + } + } + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk(KERN_ERR "ne.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + if (ei_debug && version_printed++ == 0) + printk(version); + + printk("NE*000 ethercard probe at %#3x:", ioaddr); + + /* A user with a poor card that fails to ack the reset, or that + does not have a valid 0x57,0x57 signature can still use this + without having to recompile. Specifying an i/o address along + with an otherwise unused dev->mem_end value of "0xBAD" will + cause the driver to skip these parts of the probe. */ + + bad_card = ((dev->base_addr != 0) && (dev->mem_end == 0xbad)); + + /* Reset card. Who knows what dain-bramaged state it was left in. */ + { unsigned long reset_start_time = jiffies; + + /* DON'T change these to inb_p/outb_p or reset will fail on clones. */ + outb(inb(ioaddr + NE_RESET), ioaddr + NE_RESET); + + while ((inb_p(ioaddr + EN0_ISR) & ENISR_RESET) == 0) + if (jiffies - reset_start_time > 2*HZ/100) { + if (bad_card) { + printk(" (warning: no reset ack)"); + break; + } else { + printk(" not found (no reset ack).\n"); + return ENODEV; + } + } + + outb_p(0xff, ioaddr + EN0_ISR); /* Ack all intr. */ + } + + /* Read the 16 bytes of station address PROM. + We must first initialize registers, similar to NS8390_init(eifdev, 0). + We can't reliably read the SAPROM address without this. + (I learned the hard way!). */ + { + struct {unsigned char value, offset; } program_seq[] = { + {E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/ + {0x48, EN0_DCFG}, /* Set byte-wide (0x48) access. */ + {0x00, EN0_RCNTLO}, /* Clear the count regs. */ + {0x00, EN0_RCNTHI}, + {0x00, EN0_IMR}, /* Mask completion irq. */ + {0xFF, EN0_ISR}, + {E8390_RXOFF, EN0_RXCR}, /* 0x20 Set to monitor */ + {E8390_TXOFF, EN0_TXCR}, /* 0x02 and loopback mode. */ + {32, EN0_RCNTLO}, + {0x00, EN0_RCNTHI}, + {0x00, EN0_RSARLO}, /* DMA starting at 0x0000. */ + {0x00, EN0_RSARHI}, + {E8390_RREAD+E8390_START, E8390_CMD}, + }; + for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++) + outb_p(program_seq[i].value, ioaddr + program_seq[i].offset); + + } + for(i = 0; i < 32 /*sizeof(SA_prom)*/; i+=2) { + SA_prom[i] = inb(ioaddr + NE_DATAPORT); + SA_prom[i+1] = inb(ioaddr + NE_DATAPORT); + if (SA_prom[i] != SA_prom[i+1]) + wordlength = 1; + } + + /* At this point, wordlength *only* tells us if the SA_prom is doubled + up or not because some broken PCI cards don't respect the byte-wide + request in program_seq above, and hence don't have doubled up values. + These broken cards would otherwise be detected as an ne1000. */ + + if (wordlength == 2) + for (i = 0; i < 16; i++) + SA_prom[i] = SA_prom[i+i]; + + if (pci_irq_line || ioaddr >= 0x400) + wordlength = 2; /* Catch broken PCI cards mentioned above. */ + + if (wordlength == 2) { + /* We must set the 8390 for word mode. */ + outb_p(0x49, ioaddr + EN0_DCFG); + start_page = NESM_START_PG; + stop_page = NESM_STOP_PG; + } else { + start_page = NE1SM_START_PG; + stop_page = NE1SM_STOP_PG; + } + + neX000 = (SA_prom[14] == 0x57 && SA_prom[15] == 0x57); + ctron = (SA_prom[0] == 0x00 && SA_prom[1] == 0x00 && SA_prom[2] == 0x1d); + + /* Set up the rest of the parameters. */ + if (neX000 || bad_card) { + name = (wordlength == 2) ? "NE2000" : "NE1000"; + } else if (ctron) { + name = (wordlength == 2) ? "Ctron-8" : "Ctron-16"; + start_page = 0x01; + stop_page = (wordlength == 2) ? 0x40 : 0x20; + } else { +#ifdef SUPPORT_NE_BAD_CLONES + /* Ack! Well, there might be a *bad* NE*000 clone there. + Check for total bogus addresses. */ + for (i = 0; bad_clone_list[i].name8; i++) { + if (SA_prom[0] == bad_clone_list[i].SAprefix[0] && + SA_prom[1] == bad_clone_list[i].SAprefix[1] && + SA_prom[2] == bad_clone_list[i].SAprefix[2]) { + if (wordlength == 2) { + name = bad_clone_list[i].name16; + } else { + name = bad_clone_list[i].name8; + } + break; + } + } + if (bad_clone_list[i].name8 == NULL) { + printk(" not found (invalid signature %2.2x %2.2x).\n", + SA_prom[14], SA_prom[15]); + return ENXIO; + } +#else + printk(" not found.\n"); + return ENXIO; +#endif + + } + + if (pci_irq_line) + dev->irq = pci_irq_line; + + if (dev->irq < 2) { + autoirq_setup(0); + outb_p(0x50, ioaddr + EN0_IMR); /* Enable one interrupt. */ + outb_p(0x00, ioaddr + EN0_RCNTLO); + outb_p(0x00, ioaddr + EN0_RCNTHI); + outb_p(E8390_RREAD+E8390_START, ioaddr); /* Trigger it... */ + outb_p(0x00, ioaddr + EN0_IMR); /* Mask it again. */ + dev->irq = autoirq_report(0); + if (ei_debug > 2) + printk(" autoirq is %d\n", dev->irq); + } else if (dev->irq == 2) + /* Fixup for users that don't know that IRQ 2 is really IRQ 9, + or don't know which one to set. */ + dev->irq = 9; + + if (! dev->irq) { + printk(" failed to detect IRQ line.\n"); + return EAGAIN; + } + + /* Snarf the interrupt now. There's no point in waiting since we cannot + share (with ISA cards) and the board will usually be enabled. */ + { + int irqval = request_irq(dev->irq, ei_interrupt, + pci_irq_line ? SA_SHIRQ : 0, name, dev); + if (irqval) { + printk (" unable to get IRQ %d (irqval=%d).\n", dev->irq, irqval); + return EAGAIN; + } + } + + dev->base_addr = ioaddr; + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk (" unable to get memory for dev->priv.\n"); + free_irq(dev->irq, NULL); + return -ENOMEM; + } + + request_region(ioaddr, NE_IO_EXTENT, name); + + for(i = 0; i < ETHER_ADDR_LEN; i++) { + printk(" %2.2x", SA_prom[i]); + dev->dev_addr[i] = SA_prom[i]; + } + + printk("\n%s: %s found at %#x, using IRQ %d.\n", + dev->name, name, ioaddr, dev->irq); + + ei_status.name = name; + ei_status.tx_start_page = start_page; + ei_status.stop_page = stop_page; + ei_status.word16 = (wordlength == 2); + + ei_status.rx_start_page = start_page + TX_PAGES; +#ifdef PACKETBUF_MEMSIZE + /* Allow the packet buffer size to be overridden by know-it-alls. */ + ei_status.stop_page = ei_status.tx_start_page + PACKETBUF_MEMSIZE; +#endif + + ei_status.reset_8390 = &ne_reset_8390; + ei_status.block_input = &ne_block_input; + ei_status.block_output = &ne_block_output; + ei_status.get_8390_hdr = &ne_get_8390_hdr; + dev->open = &ne_open; + dev->stop = &ne_close; + NS8390_init(dev, 0); + return 0; +} + +static int +ne_open(struct device *dev) +{ + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static int +ne_close(struct device *dev) +{ + if (ei_debug > 1) + printk("%s: Shutting down ethercard.\n", dev->name); + ei_close(dev); + MOD_DEC_USE_COUNT; + return 0; +} + +/* Hard reset the card. This used to pause for the same period that a + 8390 reset command required, but that shouldn't be necessary. */ +static void +ne_reset_8390(struct device *dev) +{ + unsigned long reset_start_time = jiffies; + + if (ei_debug > 1) printk("resetting the 8390 t=%ld...", jiffies); + + /* DON'T change these to inb_p/outb_p or reset will fail on clones. */ + outb(inb(NE_BASE + NE_RESET), NE_BASE + NE_RESET); + + ei_status.txing = 0; + ei_status.dmaing = 0; + + /* This check _should_not_ be necessary, omit eventually. */ + while ((inb_p(NE_BASE+EN0_ISR) & ENISR_RESET) == 0) + if (jiffies - reset_start_time > 2*HZ/100) { + printk("%s: ne_reset_8390() did not complete.\n", dev->name); + break; + } + outb_p(ENISR_RESET, NE_BASE + EN0_ISR); /* Ack intr. */ +} + +/* Grab the 8390 specific header. Similar to the block_input routine, but + we don't need to be concerned with ring wrap as the header will be at + the start of a page, so we optimize accordingly. */ + +static void +ne_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + + int nic_base = dev->base_addr; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_get_8390_hdr " + "[DMAstat:%d][irqlock:%d][intr:%d].\n", + dev->name, ei_status.dmaing, ei_status.irqlock, + dev->interrupt); + return; + } + + ei_status.dmaing |= 0x01; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base+ NE_CMD); + outb_p(sizeof(struct e8390_pkt_hdr), nic_base + EN0_RCNTLO); + outb_p(0, nic_base + EN0_RCNTHI); + outb_p(0, nic_base + EN0_RSARLO); /* On page boundary */ + outb_p(ring_page, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + NE_CMD); + + if (ei_status.word16) + insw(NE_BASE + NE_DATAPORT, hdr, sizeof(struct e8390_pkt_hdr)>>1); + else + insb(NE_BASE + NE_DATAPORT, hdr, sizeof(struct e8390_pkt_hdr)); + + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} + +/* Block input and output, similar to the Crynwr packet driver. If you + are porting to a new ethercard, look at the packet driver source for hints. + The NEx000 doesn't share the on-board packet memory -- you have to put + the packet out through the "remote DMA" dataport using outb. */ + +static void +ne_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ +#ifdef NE_SANITY_CHECK + int xfer_count = count; +#endif + int nic_base = dev->base_addr; + char *buf = skb->data; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_block_input " + "[DMAstat:%d][irqlock:%d][intr:%d].\n", + dev->name, ei_status.dmaing, ei_status.irqlock, + dev->interrupt); + return; + } + ei_status.dmaing |= 0x01; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base+ NE_CMD); + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(ring_offset & 0xff, nic_base + EN0_RSARLO); + outb_p(ring_offset >> 8, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + NE_CMD); + if (ei_status.word16) { + insw(NE_BASE + NE_DATAPORT,buf,count>>1); + if (count & 0x01) { + buf[count-1] = inb(NE_BASE + NE_DATAPORT); +#ifdef NE_SANITY_CHECK + xfer_count++; +#endif + } + } else { + insb(NE_BASE + NE_DATAPORT, buf, count); + } + +#ifdef NE_SANITY_CHECK + /* This was for the ALPHA version only, but enough people have + been encountering problems so it is still here. If you see + this message you either 1) have a slightly incompatible clone + or 2) have noise/speed problems with your bus. */ + if (ei_debug > 1) { /* DMA termination address check... */ + int addr, tries = 20; + do { + /* DON'T check for 'inb_p(EN0_ISR) & ENISR_RDC' here + -- it's broken for Rx on some cards! */ + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + addr = (high << 8) + low; + if (((ring_offset + xfer_count) & 0xff) == low) + break; + } while (--tries > 0); + if (tries <= 0) + printk("%s: RX transfer address mismatch," + "%#4.4x (expected) vs. %#4.4x (actual).\n", + dev->name, ring_offset + xfer_count, addr); + } +#endif + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} + +static void +ne_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page) +{ + int nic_base = NE_BASE; + unsigned long dma_start; +#ifdef NE_SANITY_CHECK + int retries = 0; +#endif + + /* Round the count up for word writes. Do we need to do this? + What effect will an odd byte count have on the 8390? + I should check someday. */ + if (ei_status.word16 && (count & 0x01)) + count++; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_block_output." + "[DMAstat:%d][irqlock:%d][intr:%d]\n", + dev->name, ei_status.dmaing, ei_status.irqlock, + dev->interrupt); + return; + } + ei_status.dmaing |= 0x01; + /* We should already be in page 0, but to be safe... */ + outb_p(E8390_PAGE0+E8390_START+E8390_NODMA, nic_base + NE_CMD); + +#ifdef NE_SANITY_CHECK + retry: +#endif + +#ifdef NE8390_RW_BUGFIX + /* Handle the read-before-write bug the same way as the + Crynwr packet driver -- the NatSemi method doesn't work. + Actually this doesn't always work either, but if you have + problems with your NEx000 this is better than nothing! */ + outb_p(0x42, nic_base + EN0_RCNTLO); + outb_p(0x00, nic_base + EN0_RCNTHI); + outb_p(0x42, nic_base + EN0_RSARLO); + outb_p(0x00, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + NE_CMD); + /* Make certain that the dummy read has occurred. */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + SLOW_DOWN_IO; +#endif + + outb_p(ENISR_RDC, nic_base + EN0_ISR); + + /* Now the normal output. */ + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(0x00, nic_base + EN0_RSARLO); + outb_p(start_page, nic_base + EN0_RSARHI); + + outb_p(E8390_RWRITE+E8390_START, nic_base + NE_CMD); + if (ei_status.word16) { + outsw(NE_BASE + NE_DATAPORT, buf, count>>1); + } else { + outsb(NE_BASE + NE_DATAPORT, buf, count); + } + + dma_start = jiffies; + +#ifdef NE_SANITY_CHECK + /* This was for the ALPHA version only, but enough people have + been encountering problems so it is still here. */ + if (ei_debug > 1) { /* DMA termination address check... */ + int addr, tries = 20; + do { + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + addr = (high << 8) + low; + if ((start_page << 8) + count == addr) + break; + } while (--tries > 0); + if (tries <= 0) { + printk("%s: Tx packet transfer address mismatch," + "%#4.4x (expected) vs. %#4.4x (actual).\n", + dev->name, (start_page << 8) + count, addr); + if (retries++ == 0) + goto retry; + } + } +#endif + + while ((inb_p(nic_base + EN0_ISR) & ENISR_RDC) == 0) + if (jiffies - dma_start > 2*HZ/100) { /* 20ms */ + printk("%s: timeout waiting for Tx RDC.\n", dev->name); + ne_reset_8390(dev); + NS8390_init(dev,1); + break; + } + + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; + return; +} + + +#ifdef MODULE +#define MAX_NE_CARDS 4 /* Max number of NE cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_NE_CARDS] = { 0, }; +static struct device dev_ne[MAX_NE_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_NE_CARDS] = { 0, }; +static int irq[MAX_NE_CARDS] = { 0, }; +static int bad[MAX_NE_CARDS] = { 0, }; + +/* This is set up so that no autoprobe takes place. We can't guarantee +that the ne2k probe is the last 8390 based probe to take place (as it +is at boot) and so the probe will get confused by any other 8390 cards. +ISA device autoprobes on a running machine are not recommended anyway. */ + +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++) { + struct device *dev = &dev_ne[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->init = ne_probe; + dev->mem_end = bad[this_dev]; + if (register_netdev(dev) == 0) { + found++; + continue; + } + if (found != 0) /* Got at least one. */ + return 0; + if (io[this_dev] != 0) + printk(KERN_WARNING "ne.c: No NE*000 card found at i/o = %#x\n", io[this_dev]); + else + printk(KERN_NOTICE "ne.c: No PCI cards found. Use \"io=0xNNN\" value(s) for ISA cards.\n"); + return -ENXIO; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++) { + struct device *dev = &dev_ne[this_dev]; + if (dev->priv != NULL) { + kfree(dev->priv); + dev->priv = NULL; + free_irq(dev->irq, dev); + irq2dev_map[dev->irq] = NULL; + release_region(dev->base_addr, NE_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DKERNEL -Wall -O6 -fomit-frame-pointer -I/usr/src/linux/net/tcp -c ne.c" + * version-control: t + * kept-new-versions: 5 + * End: + */ diff --git a/linux/src/drivers/net/ne2k-pci.c b/linux/src/drivers/net/ne2k-pci.c new file mode 100644 index 00000000..3aa556fd --- /dev/null +++ b/linux/src/drivers/net/ne2k-pci.c @@ -0,0 +1,640 @@ +/* ne2k-pci.c: A NE2000 clone on PCI bus driver for Linux. */ +/* + A Linux device driver for PCI NE2000 clones. + + Authorship and other copyrights: + 1992-1998 by Donald Becker, NE2000 core and various modifications. + 1995-1998 by Paul Gortmaker, core modifications and PCI support. + + Copyright 1993 assigned to the United States Government as represented + by the Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + People are making PCI ne2000 clones! Oh the horror, the horror... + + Issues remaining: + No full-duplex support. +*/ + +/* Our copyright info must remain in the binary. */ +static const char *version = +"ne2k-pci.c:v0.99L 2/7/98 D. Becker/P. Gortmaker http://cesdis.gsfc.nasa.gov/linux/drivers/ne2k-pci.html\n"; + +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include "8390.h" + +/* Set statically or when loading the driver module. */ +static int debug = 1; + +/* Some defines that people can play with if so inclined. */ + +/* Do #define LOAD_8390_BY_KERNELD to automatically load 8390 support. */ +#ifdef LOAD_8390_BY_KERNELD +#include <linux/kerneld.h> +#endif +/* Use 32 bit data-movement operations instead of 16 bit. */ +#define USE_LONGIO + +/* Do we implement the read before write bugfix ? */ +/* #define NE_RW_BUGFIX */ + +/* Do we have a non std. amount of memory? (in units of 256 byte pages) */ +/* #define PACKETBUF_MEMSIZE 0x40 */ + +static struct { + unsigned short vendor, dev_id; + char *name; +} +pci_clone_list[] = { + {0x10ec, 0x8029, "RealTek RTL-8029"}, + {0x1050, 0x0940, "Winbond 89C940"}, + {0x11f6, 0x1401, "Compex RL2000"}, + {0x8e2e, 0x3000, "KTI ET32P2"}, + {0x4a14, 0x5000, "NetVin NV5000SC"}, + {0x1106, 0x0926, "Via 82C926"}, + {0,} +}; + +/* ---- No user-serviceable parts below ---- */ + +#define NE_BASE (dev->base_addr) +#define NE_CMD 0x00 +#define NE_DATAPORT 0x10 /* NatSemi-defined port window offset. */ +#define NE_RESET 0x1f /* Issue a read to reset, a write to clear. */ +#define NE_IO_EXTENT 0x20 + +#define NESM_START_PG 0x40 /* First page of TX buffer */ +#define NESM_STOP_PG 0x80 /* Last page +1 of RX ring */ + +int ne2k_pci_probe(struct device *dev); +static struct device *ne2k_pci_probe1(struct device *dev, int ioaddr, int irq); + +static int ne_open(struct device *dev); +static int ne_close(struct device *dev); + +static void ne_reset_8390(struct device *dev); +static void ne_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void ne_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void ne_block_output(struct device *dev, const int count, + const unsigned char *buf, const int start_page); + + + +/* No room in the standard 8390 structure for extra info we need. */ +struct ne2k_pci_card { + struct ne2k_pci_card *next; + struct device *dev; + unsigned char pci_bus, pci_device_fn; +}; +/* A list of all installed devices, for removing the driver module. */ +static struct ne2k_pci_card *ne2k_card_list = NULL; + +#ifdef LOAD_8390_BY_KERNELD +static int (*Lethdev_init)(struct device *dev); +static void (*LNS8390_init)(struct device *dev, int startp); +static int (*Lei_open)(struct device *dev); +static int (*Lei_close)(struct device *dev); +static void (*Lei_interrupt)(int irq, void *dev_id, struct pt_regs *regs); +#else +#define Lethdev_init ethdev_init +#define LNS8390_init NS8390_init +#define Lei_open ei_open +#define Lei_close ei_close +#define Lei_interrupt ei_interrupt +#endif + +#ifdef MODULE + +int +init_module(void) +{ + /* We must emit version information. */ + if (debug) + printk(KERN_INFO "%s", version); + + return ne2k_pci_probe(0); +} + +void +cleanup_module(void) +{ + struct device *dev; + struct ne2k_pci_card *this_card; + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (ne2k_card_list) { + dev = ne2k_card_list->dev; + unregister_netdev(dev); + release_region(dev->base_addr, NE_IO_EXTENT); + kfree(dev); + this_card = ne2k_card_list; + ne2k_card_list = ne2k_card_list->next; + kfree(this_card); + } + +#ifdef LOAD_8390_BY_KERNELD + release_module("8390", 0); +#endif +} + +#endif /* MODULE */ + +/* + NEx000-clone boards have a Station Address (SA) PROM (SAPROM) in the packet + buffer memory space. By-the-spec NE2000 clones have 0x57,0x57 in bytes + 0x0e,0x0f of the SAPROM, while other supposed NE2000 clones must be + detected by their SA prefix. + + Reading the SAPROM from a word-wide card with the 8390 set in byte-wide + mode results in doubled values, which can be detected and compensated for. + + The probe is also responsible for initializing the card and filling + in the 'dev' and 'ei_status' structures. +*/ + +#ifdef HAVE_DEVLIST +struct netdev_entry netcard_drv = +{"ne2k_pci", ne2k_pci_probe1, NE_IO_EXTENT, 0}; +#endif + +int ne2k_pci_probe(struct device *dev) +{ + static int pci_index = 0; /* Static, for multiple calls. */ + int cards_found = 0; + int i; + + if ( ! pcibios_present()) + return -ENODEV; + + for (;pci_index < 0xff; pci_index++) { + unsigned char pci_bus, pci_device_fn; + u8 pci_irq_line; + u16 pci_command, new_command, vendor, device; + u32 pci_ioaddr; + + if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8, pci_index, + &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + break; + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + + /* Note: some vendor IDs (RealTek) have non-NE2k cards as well. */ + for (i = 0; pci_clone_list[i].vendor != 0; i++) + if (pci_clone_list[i].vendor == vendor + && pci_clone_list[i].dev_id == device) + break; + if (pci_clone_list[i].vendor == 0) + continue; + +#ifndef MODULE + { + static unsigned version_printed = 0; + if (version_printed++ == 0) + printk(KERN_INFO "%s", version); + } +#endif + + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= PCI_BASE_ADDRESS_IO_MASK; + + /* Avoid already found cards from previous calls */ + if (check_region(pci_ioaddr, NE_IO_EXTENT)) + continue; + + /* Activate the card: fix for brain-damaged Win98 BIOSes. */ + new_command = pci_command | PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled this" + " NE2k clone! Updating PCI command %4.4x->%4.4x.\n", + pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + + if (pci_irq_line <= 0 || pci_irq_line >= NR_IRQS) + printk(KERN_WARNING " WARNING: The PCI BIOS assigned this PCI NE2k" + " card to IRQ %d, which is unlikely to work!.\n" + KERN_WARNING " You should use the PCI BIOS setup to assign" + " a valid IRQ line.\n", pci_irq_line); + + printk("ne2k-pci.c: PCI NE2000 clone '%s' at I/O %#x, IRQ %d.\n", + pci_clone_list[i].name, pci_ioaddr, pci_irq_line); + dev = ne2k_pci_probe1(dev, pci_ioaddr, pci_irq_line); + if (dev == 0) { + /* Should not happen. */ + printk(KERN_ERR "ne2k-pci: Probe of PCI card at %#x failed.\n", + pci_ioaddr); + continue; + } else { + struct ne2k_pci_card *ne2k_card = + kmalloc(sizeof(struct ne2k_pci_card), GFP_KERNEL); + ne2k_card->next = ne2k_card_list; + ne2k_card_list = ne2k_card; + ne2k_card->dev = dev; + ne2k_card->pci_bus = pci_bus; + ne2k_card->pci_device_fn = pci_device_fn; + } + dev = 0; + + cards_found++; + } + + return cards_found ? 0 : -ENODEV; +} + +static struct device *ne2k_pci_probe1(struct device *dev, int ioaddr, int irq) +{ + int i; + unsigned char SA_prom[32]; + const char *name = NULL; + int start_page, stop_page; + int reg0 = inb(ioaddr); + + if (reg0 == 0xFF) + return 0; + + /* Do a preliminary verification that we have a 8390. */ + { + int regd; + outb(E8390_NODMA+E8390_PAGE1+E8390_STOP, ioaddr + E8390_CMD); + regd = inb(ioaddr + 0x0d); + outb(0xff, ioaddr + 0x0d); + outb(E8390_NODMA+E8390_PAGE0, ioaddr + E8390_CMD); + inb(ioaddr + EN0_COUNTER0); /* Clear the counter by reading. */ + if (inb(ioaddr + EN0_COUNTER0) != 0) { + outb(reg0, ioaddr); + outb(regd, ioaddr + 0x0d); /* Restore the old values. */ + return 0; + } + } + + dev = init_etherdev(dev, 0); + + /* Reset card. Who knows what dain-bramaged state it was left in. */ + { + unsigned long reset_start_time = jiffies; + + outb(inb(ioaddr + NE_RESET), ioaddr + NE_RESET); + + /* This looks like a horrible timing loop, but it should never take + more than a few cycles. + */ + while ((inb(ioaddr + EN0_ISR) & ENISR_RESET) == 0) + /* Limit wait: '2' avoids jiffy roll-over. */ + if (jiffies - reset_start_time > 2) { + printk("ne2k-pci: Card failure (no reset ack).\n"); + return 0; + } + + outb(0xff, ioaddr + EN0_ISR); /* Ack all intr. */ + } + +#if defined(LOAD_8390_BY_KERNELD) + /* We are now certain the 8390 module is required. */ + if (request_module("8390")) { + printk("ne2k-pci: Failed to load the 8390 core module.\n"); + return 0; + } + if ((Lethdev_init = (void*)get_module_symbol(0, "ethdev_init")) == 0 || + (LNS8390_init = (void*)get_module_symbol(0, "NS8390_init")) == 0 || + (Lei_open = (void*)get_module_symbol(0, "ei_open")) == 0 || + (Lei_close = (void*)get_module_symbol(0, "ei_close")) == 0 || + (Lei_interrupt = (void*)get_module_symbol(0, "ei_interrupt")) == 0 ) { + printk("ne2k-pci: Failed to resolve an 8390 symbol.\n"); + release_module("8390", 0); + return 0; + } +#endif + + /* Read the 16 bytes of station address PROM. + We must first initialize registers, similar to NS8390_init(eifdev, 0). + We can't reliably read the SAPROM address without this. + (I learned the hard way!). */ + { + struct {unsigned char value, offset; } program_seq[] = { + {E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/ + {0x49, EN0_DCFG}, /* Set word-wide access. */ + {0x00, EN0_RCNTLO}, /* Clear the count regs. */ + {0x00, EN0_RCNTHI}, + {0x00, EN0_IMR}, /* Mask completion irq. */ + {0xFF, EN0_ISR}, + {E8390_RXOFF, EN0_RXCR}, /* 0x20 Set to monitor */ + {E8390_TXOFF, EN0_TXCR}, /* 0x02 and loopback mode. */ + {32, EN0_RCNTLO}, + {0x00, EN0_RCNTHI}, + {0x00, EN0_RSARLO}, /* DMA starting at 0x0000. */ + {0x00, EN0_RSARHI}, + {E8390_RREAD+E8390_START, E8390_CMD}, + }; + for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++) + outb(program_seq[i].value, ioaddr + program_seq[i].offset); + + } + +#ifdef notdef + /* Some broken PCI cards don't respect the byte-wide + request in program_seq above, and hence don't have doubled up values. + */ + for(i = 0; i < 32 /*sizeof(SA_prom)*/; i+=2) { + SA_prom[i] = inb(ioaddr + NE_DATAPORT); + SA_prom[i+1] = inb(ioaddr + NE_DATAPORT); + if (SA_prom[i] != SA_prom[i+1]) + sa_prom_doubled = 0; + } + + if (sa_prom_doubled) + for (i = 0; i < 16; i++) + SA_prom[i] = SA_prom[i+i]; +#else + for(i = 0; i < 32 /*sizeof(SA_prom)*/; i++) + SA_prom[i] = inb(ioaddr + NE_DATAPORT); + +#endif + + /* We always set the 8390 registers for word mode. */ + outb(0x49, ioaddr + EN0_DCFG); + start_page = NESM_START_PG; + stop_page = NESM_STOP_PG; + + /* Set up the rest of the parameters. */ + name = "PCI NE2000"; + + dev->irq = irq; + dev->base_addr = ioaddr; + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (Lethdev_init(dev)) { + printk ("%s: unable to get memory for dev->priv.\n", dev->name); + return 0; + } + + request_region(ioaddr, NE_IO_EXTENT, dev->name); + + printk("%s: %s found at %#x, IRQ %d, ", + dev->name, name, ioaddr, dev->irq); + for(i = 0; i < 6; i++) { + printk("%2.2X%s", SA_prom[i], i == 5 ? ".\n": ":"); + dev->dev_addr[i] = SA_prom[i]; + } + + ei_status.name = name; + ei_status.tx_start_page = start_page; + ei_status.stop_page = stop_page; + ei_status.word16 = 1; + + ei_status.rx_start_page = start_page + TX_PAGES; +#ifdef PACKETBUF_MEMSIZE + /* Allow the packet buffer size to be overridden by know-it-alls. */ + ei_status.stop_page = ei_status.tx_start_page + PACKETBUF_MEMSIZE; +#endif + + ei_status.reset_8390 = &ne_reset_8390; + ei_status.block_input = &ne_block_input; + ei_status.block_output = &ne_block_output; + ei_status.get_8390_hdr = &ne_get_8390_hdr; + dev->open = &ne_open; + dev->stop = &ne_close; + LNS8390_init(dev, 0); + return dev; +} + +static int +ne_open(struct device *dev) +{ + if (request_irq(dev->irq, Lei_interrupt, SA_SHIRQ, dev->name, dev)) + return -EAGAIN; + Lei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static int +ne_close(struct device *dev) +{ + Lei_close(dev); + free_irq(dev->irq, dev); + MOD_DEC_USE_COUNT; + return 0; +} + +/* Hard reset the card. This used to pause for the same period that a + 8390 reset command required, but that shouldn't be necessary. */ +static void +ne_reset_8390(struct device *dev) +{ + unsigned long reset_start_time = jiffies; + + if (debug > 1) printk("%s: Resetting the 8390 t=%ld...", + dev->name, jiffies); + + outb(inb(NE_BASE + NE_RESET), NE_BASE + NE_RESET); + + ei_status.txing = 0; + ei_status.dmaing = 0; + + /* This check _should_not_ be necessary, omit eventually. */ + while ((inb(NE_BASE+EN0_ISR) & ENISR_RESET) == 0) + if (jiffies - reset_start_time > 2) { + printk("%s: ne_reset_8390() did not complete.\n", dev->name); + break; + } + outb(ENISR_RESET, NE_BASE + EN0_ISR); /* Ack intr. */ +} + +/* Grab the 8390 specific header. Similar to the block_input routine, but + we don't need to be concerned with ring wrap as the header will be at + the start of a page, so we optimize accordingly. */ + +static void +ne_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + + int nic_base = dev->base_addr; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_get_8390_hdr " + "[DMAstat:%d][irqlock:%d][intr:%d].\n", + dev->name, ei_status.dmaing, ei_status.irqlock, + dev->interrupt); + return; + } + + ei_status.dmaing |= 0x01; + outb(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base+ NE_CMD); + outb(sizeof(struct e8390_pkt_hdr), nic_base + EN0_RCNTLO); + outb(0, nic_base + EN0_RCNTHI); + outb(0, nic_base + EN0_RSARLO); /* On page boundary */ + outb(ring_page, nic_base + EN0_RSARHI); + outb(E8390_RREAD+E8390_START, nic_base + NE_CMD); + +#if defined(USE_LONGIO) + *(u32*)hdr = inl(NE_BASE + NE_DATAPORT); +#else + insw(NE_BASE + NE_DATAPORT, hdr, sizeof(struct e8390_pkt_hdr)>>1); +#endif + + outb(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} + +/* Block input and output, similar to the Crynwr packet driver. If you + are porting to a new ethercard, look at the packet driver source for hints. + The NEx000 doesn't share the on-board packet memory -- you have to put + the packet out through the "remote DMA" dataport using outb. */ + +static void +ne_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + int nic_base = dev->base_addr; + char *buf = skb->data; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_block_input " + "[DMAstat:%d][irqlock:%d][intr:%d].\n", + dev->name, ei_status.dmaing, ei_status.irqlock, + dev->interrupt); + return; + } + ei_status.dmaing |= 0x01; + outb(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base+ NE_CMD); + outb(count & 0xff, nic_base + EN0_RCNTLO); + outb(count >> 8, nic_base + EN0_RCNTHI); + outb(ring_offset & 0xff, nic_base + EN0_RSARLO); + outb(ring_offset >> 8, nic_base + EN0_RSARHI); + outb(E8390_RREAD+E8390_START, nic_base + NE_CMD); + +#if defined(USE_LONGIO) + insl(NE_BASE + NE_DATAPORT, buf, count>>2); + if (count & 3) { + buf += count & ~3; + if (count & 2) + *((u16*)buf)++ = inw(NE_BASE + NE_DATAPORT); + if (count & 1) + *buf = inb(NE_BASE + NE_DATAPORT); + } +#else + insw(NE_BASE + NE_DATAPORT,buf,count>>1); + if (count & 0x01) { + buf[count-1] = inb(NE_BASE + NE_DATAPORT); + } +#endif + + outb(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} + +static void +ne_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page) +{ + int nic_base = NE_BASE; + unsigned long dma_start; + + /* On little-endian it's always safe to round the count up for + word writes. */ + if (count & 0x01) + count++; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_block_output." + "[DMAstat:%d][irqlock:%d][intr:%d]\n", + dev->name, ei_status.dmaing, ei_status.irqlock, + dev->interrupt); + return; + } + ei_status.dmaing |= 0x01; + /* We should already be in page 0, but to be safe... */ + outb(E8390_PAGE0+E8390_START+E8390_NODMA, nic_base + NE_CMD); + +#ifdef NE8390_RW_BUGFIX + /* Handle the read-before-write bug the same way as the + Crynwr packet driver -- the NatSemi method doesn't work. + Actually this doesn't always work either, but if you have + problems with your NEx000 this is better than nothing! */ + outb(0x42, nic_base + EN0_RCNTLO); + outb(0x00, nic_base + EN0_RCNTHI); + outb(0x42, nic_base + EN0_RSARLO); + outb(0x00, nic_base + EN0_RSARHI); + outb(E8390_RREAD+E8390_START, nic_base + NE_CMD); +#endif + outb(ENISR_RDC, nic_base + EN0_ISR); + + /* Now the normal output. */ + outb(count & 0xff, nic_base + EN0_RCNTLO); + outb(count >> 8, nic_base + EN0_RCNTHI); + outb(0x00, nic_base + EN0_RSARLO); + outb(start_page, nic_base + EN0_RSARHI); + outb(E8390_RWRITE+E8390_START, nic_base + NE_CMD); +#if defined(USE_LONGIO) + outsl(NE_BASE + NE_DATAPORT, buf, count>>2); + if (count & 3) { + buf += count & ~3; + if (count & 2) + outw(*((u16*)buf)++, NE_BASE + NE_DATAPORT); + } +#else + outsw(NE_BASE + NE_DATAPORT, buf, count>>1); +#endif + + dma_start = jiffies; + + while ((inb(nic_base + EN0_ISR) & ENISR_RDC) == 0) + if (jiffies - dma_start > 2) { /* Avoid clock roll-over. */ + printk("%s: timeout waiting for Tx RDC.\n", dev->name); + ne_reset_8390(dev); + LNS8390_init(dev,1); + break; + } + + outb(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; + return; +} + + +/* + * Local variables: + * compile-command: "gcc -DMODVERSIONS -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer -I/usr/src/linux/drivers/net/ -c ne2k-pci.c" + * alt-compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer -I/usr/src/linux/drivers/net/ -c ne2k-pci.c" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * version-control: t + * kept-new-versions: 5 + * End: + */ diff --git a/linux/src/drivers/net/net_init.c b/linux/src/drivers/net/net_init.c new file mode 100644 index 00000000..3d4c42d4 --- /dev/null +++ b/linux/src/drivers/net/net_init.c @@ -0,0 +1,439 @@ +/* netdrv_init.c: Initialization for network devices. */ +/* + Written 1993,1994,1995 by Donald Becker. + + The author may be reached as becker@cesdis.gsfc.nasa.gov or + C/O Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This file contains the initialization for the "pl14+" style ethernet + drivers. It should eventually replace most of drivers/net/Space.c. + It's primary advantage is that it's able to allocate low-memory buffers. + A secondary advantage is that the dangerous NE*000 netcards can reserve + their I/O port region before the SCSI probes start. + + Modifications/additions by Bjorn Ekwall <bj0rn@blox.se>: + ethdev_index[MAX_ETH_CARDS] + register_netdev() / unregister_netdev() + + Modifications by Wolfgang Walter + Use dev_close cleanly so we always shut things down tidily. + + Changed 29/10/95, Alan Cox to pass sockaddr's around for mac addresses. + + 14/06/96 - Paul Gortmaker: Add generic eth_change_mtu() function. + + August 12, 1996 - Lawrence V. Stefani: Added fddi_change_mtu() and + fddi_setup() functions. + Sept. 10, 1996 - Lawrence V. Stefani: Increased hard_header_len to + include 3 pad bytes. +*/ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/malloc.h> +#include <linux/if_ether.h> +#include <linux/string.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/fddidevice.h> +#include <linux/trdevice.h> +#include <linux/if_arp.h> +#ifdef CONFIG_NET_ALIAS +#include <linux/net_alias.h> +#endif + +/* The network devices currently exist only in the socket namespace, so these + entries are unused. The only ones that make sense are + open start the ethercard + close stop the ethercard + ioctl To get statistics, perhaps set the interface port (AUI, BNC, etc.) + One can also imagine getting raw packets using + read & write + but this is probably better handled by a raw packet socket. + + Given that almost all of these functions are handled in the current + socket-based scheme, putting ethercard devices in /dev/ seems pointless. + + [Removed all support for /dev network devices. When someone adds + streams then by magic we get them, but otherwise they are un-needed + and a space waste] +*/ + +/* The list of used and available "eth" slots (for "eth0", "eth1", etc.) */ +#define MAX_ETH_CARDS 16 /* same as the number if irq's in irq2dev[] */ +static struct device *ethdev_index[MAX_ETH_CARDS]; + + +/* Fill in the fields of the device structure with ethernet-generic values. + + If no device structure is passed, a new one is constructed, complete with + a SIZEOF_PRIVATE private data area. + + If an empty string area is passed as dev->name, or a new structure is made, + a new name string is constructed. The passed string area should be 8 bytes + long. + */ + +struct device * +init_etherdev(struct device *dev, int sizeof_priv) +{ + int new_device = 0; + int i; + + /* Use an existing correctly named device in Space.c:dev_base. */ + if (dev == NULL) { + int alloc_size = sizeof(struct device) + sizeof("eth%d ") + + sizeof_priv + 3; + struct device *cur_dev; + char pname[8]; /* Putative name for the device. */ + + for (i = 0; i < MAX_ETH_CARDS; ++i) + if (ethdev_index[i] == NULL) { + sprintf(pname, "eth%d", i); + for (cur_dev = dev_base; cur_dev; cur_dev = cur_dev->next) + if (strcmp(pname, cur_dev->name) == 0) { + dev = cur_dev; + dev->init = NULL; + sizeof_priv = (sizeof_priv + 3) & ~3; + dev->priv = sizeof_priv + ? kmalloc(sizeof_priv, GFP_KERNEL) + : NULL; + if (dev->priv) memset(dev->priv, 0, sizeof_priv); + goto found; + } + } + + alloc_size &= ~3; /* Round to dword boundary. */ + + dev = (struct device *)kmalloc(alloc_size, GFP_KERNEL); + memset(dev, 0, alloc_size); + if (sizeof_priv) + dev->priv = (void *) (dev + 1); + dev->name = sizeof_priv + (char *)(dev + 1); + new_device = 1; + } + + found: /* From the double loop above. */ + + if (dev->name && + ((dev->name[0] == '\0') || (dev->name[0] == ' '))) { + for (i = 0; i < MAX_ETH_CARDS; ++i) + if (ethdev_index[i] == NULL) { + sprintf(dev->name, "eth%d", i); + ethdev_index[i] = dev; + break; + } + } + + ether_setup(dev); /* Hmmm, should this be called here? */ + + if (new_device) { + /* Append the device to the device queue. */ + struct device **old_devp = &dev_base; + while ((*old_devp)->next) + old_devp = & (*old_devp)->next; + (*old_devp)->next = dev; + dev->next = 0; + } + return dev; +} + + +static int eth_mac_addr(struct device *dev, void *p) +{ + struct sockaddr *addr=p; + if(dev->start) + return -EBUSY; + memcpy(dev->dev_addr, addr->sa_data,dev->addr_len); + return 0; +} + +static int eth_change_mtu(struct device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > 1500)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} + +#ifdef CONFIG_FDDI + +static int fddi_change_mtu(struct device *dev, int new_mtu) +{ + if ((new_mtu < FDDI_K_SNAP_HLEN) || (new_mtu > FDDI_K_SNAP_DLEN)) + return(-EINVAL); + dev->mtu = new_mtu; + return(0); +} + +#endif + +void ether_setup(struct device *dev) +{ + int i; + /* Fill in the fields of the device structure with ethernet-generic values. + This should be in a common file instead of per-driver. */ + for (i = 0; i < DEV_NUMBUFFS; i++) + skb_queue_head_init(&dev->buffs[i]); + + /* register boot-defined "eth" devices */ + if (dev->name && (strncmp(dev->name, "eth", 3) == 0)) { + i = simple_strtoul(dev->name + 3, NULL, 0); + if (ethdev_index[i] == NULL) { + ethdev_index[i] = dev; + } + else if (dev != ethdev_index[i]) { + /* Really shouldn't happen! */ + printk("ether_setup: Ouch! Someone else took %s\n", + dev->name); + } + } + + dev->change_mtu = eth_change_mtu; + dev->hard_header = eth_header; + dev->rebuild_header = eth_rebuild_header; + dev->set_mac_address = eth_mac_addr; + dev->header_cache_bind = eth_header_cache_bind; + dev->header_cache_update= eth_header_cache_update; + + dev->type = ARPHRD_ETHER; + dev->hard_header_len = ETH_HLEN; + dev->mtu = 1500; /* eth_mtu */ + dev->addr_len = ETH_ALEN; + dev->tx_queue_len = 100; /* Ethernet wants good queues */ + + memset(dev->broadcast,0xFF, ETH_ALEN); + + /* New-style flags. */ + dev->flags = IFF_BROADCAST|IFF_MULTICAST; + dev->family = AF_INET; + dev->pa_addr = 0; + dev->pa_brdaddr = 0; + dev->pa_mask = 0; + dev->pa_alen = 4; +} + +#ifdef CONFIG_TR + +void tr_setup(struct device *dev) +{ + int i; + /* Fill in the fields of the device structure with ethernet-generic values. + This should be in a common file instead of per-driver. */ + for (i = 0; i < DEV_NUMBUFFS; i++) + skb_queue_head_init(&dev->buffs[i]); + + dev->hard_header = tr_header; + dev->rebuild_header = tr_rebuild_header; + + dev->type = ARPHRD_IEEE802; + dev->hard_header_len = TR_HLEN; + dev->mtu = 2000; /* bug in fragmenter...*/ + dev->addr_len = TR_ALEN; + dev->tx_queue_len = 100; /* Long queues on tr */ + + memset(dev->broadcast,0xFF, TR_ALEN); + + /* New-style flags. */ + dev->flags = IFF_BROADCAST; + dev->family = AF_INET; + dev->pa_addr = 0; + dev->pa_brdaddr = 0; + dev->pa_mask = 0; + dev->pa_alen = 4; +} + +#endif + +#ifdef CONFIG_FDDI + +void fddi_setup(struct device *dev) + { + int i; + + /* + * Fill in the fields of the device structure with FDDI-generic values. + * This should be in a common file instead of per-driver. + */ + for (i=0; i < DEV_NUMBUFFS; i++) + skb_queue_head_init(&dev->buffs[i]); + + dev->change_mtu = fddi_change_mtu; + dev->hard_header = fddi_header; + dev->rebuild_header = fddi_rebuild_header; + + dev->type = ARPHRD_FDDI; + dev->hard_header_len = FDDI_K_SNAP_HLEN+3; /* Assume 802.2 SNAP hdr len + 3 pad bytes */ + dev->mtu = FDDI_K_SNAP_DLEN; /* Assume max payload of 802.2 SNAP frame */ + dev->addr_len = FDDI_K_ALEN; + dev->tx_queue_len = 100; /* Long queues on FDDI */ + + memset(dev->broadcast, 0xFF, FDDI_K_ALEN); + + /* New-style flags */ + dev->flags = IFF_BROADCAST | IFF_MULTICAST; + dev->family = AF_INET; + dev->pa_addr = 0; + dev->pa_brdaddr = 0; + dev->pa_mask = 0; + dev->pa_alen = 4; + return; + } + +#endif + +int ether_config(struct device *dev, struct ifmap *map) +{ + if (map->mem_start != (u_long)(-1)) + dev->mem_start = map->mem_start; + if (map->mem_end != (u_long)(-1)) + dev->mem_end = map->mem_end; + if (map->base_addr != (u_short)(-1)) + dev->base_addr = map->base_addr; + if (map->irq != (u_char)(-1)) + dev->irq = map->irq; + if (map->dma != (u_char)(-1)) + dev->dma = map->dma; + if (map->port != (u_char)(-1)) + dev->if_port = map->port; + return 0; +} + +int register_netdev(struct device *dev) +{ + struct device *d = dev_base; + unsigned long flags; + int i=MAX_ETH_CARDS; + + save_flags(flags); + cli(); + + if (dev && dev->init) { + if (dev->name && + ((dev->name[0] == '\0') || (dev->name[0] == ' '))) { + for (i = 0; i < MAX_ETH_CARDS; ++i) + if (ethdev_index[i] == NULL) { + sprintf(dev->name, "eth%d", i); +/* printk("loading device '%s'...\n", dev->name);*/ + ethdev_index[i] = dev; + break; + } + } + + sti(); /* device probes assume interrupts enabled */ + if (dev->init(dev) != 0) { + if (i < MAX_ETH_CARDS) ethdev_index[i] = NULL; + restore_flags(flags); + return -EIO; + } + cli(); + + /* Add device to end of chain */ + if (dev_base) { + while (d->next) + d = d->next; + d->next = dev; + } + else + dev_base = dev; + dev->next = NULL; + } + restore_flags(flags); + return 0; +} + +void unregister_netdev(struct device *dev) +{ + struct device *d = dev_base; + unsigned long flags; + int i; + + save_flags(flags); + cli(); + + if (dev == NULL) + { + printk("was NULL\n"); + restore_flags(flags); + return; + } + /* else */ + if (dev->start) + printk("ERROR '%s' busy and not MOD_IN_USE.\n", dev->name); + + /* + * must jump over main_device+aliases + * avoid alias devices unregistration so that only + * net_alias module manages them + */ +#ifdef CONFIG_NET_ALIAS + if (dev_base == dev) + dev_base = net_alias_nextdev(dev); + else + { + while(d && (net_alias_nextdev(d) != dev)) /* skip aliases */ + d = net_alias_nextdev(d); + + if (d && (net_alias_nextdev(d) == dev)) + { + /* + * Critical: Bypass by consider devices as blocks (maindev+aliases) + */ + net_alias_nextdev_set(d, net_alias_nextdev(dev)); + } +#else + if (dev_base == dev) + dev_base = dev->next; + else + { + while (d && (d->next != dev)) + d = d->next; + + if (d && (d->next == dev)) + { + d->next = dev->next; + } +#endif + else + { + printk("unregister_netdev: '%s' not found\n", dev->name); + restore_flags(flags); + return; + } + } + for (i = 0; i < MAX_ETH_CARDS; ++i) + { + if (ethdev_index[i] == dev) + { + ethdev_index[i] = NULL; + break; + } + } + + restore_flags(flags); + + /* + * You can i.e use a interfaces in a route though it is not up. + * We call close_dev (which is changed: it will down a device even if + * dev->flags==0 (but it will not call dev->stop if IFF_UP + * is not set). + * This will call notifier_call_chain(&netdev_chain, NETDEV_DOWN, dev), + * dev_mc_discard(dev), .... + */ + + dev_close(dev); +} + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c net_init.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/ni52.c b/linux/src/drivers/net/ni52.c new file mode 100644 index 00000000..6d486e95 --- /dev/null +++ b/linux/src/drivers/net/ni52.c @@ -0,0 +1,1387 @@ +/* + * net-3-driver for the NI5210 card (i82586 Ethernet chip) + * + * This is an extension to the Linux operating system, and is covered by the + * same Gnu Public License that covers that work. + * + * Alphacode 0.80 (96/02/19) for Linux 1.3.66 (or later) + * Copyrights (c) 1994,1995,1996 by M.Hipp (Michael.Hipp@student.uni-tuebingen.de) + * [feel free to mail ....] + * + * when using as module: (no autoprobing!) + * compile with: gcc -D__KERNEL__ -DMODULE -O2 -c ni52.c + * run with e.g: insmod ni52.o io=0x360 irq=9 memstart=0xd0000 memend=0xd4000 + * + * CAN YOU PLEASE REPORT ME YOUR PERFORMANCE EXPERIENCES !!. + * + * If you find a bug, please report me: + * The kernel panic output and any kmsg from the ni52 driver + * the ni5210-driver-version and the linux-kernel version + * how many shared memory (memsize) on the netcard, + * bootprom: yes/no, base_addr, mem_start + * maybe the ni5210-card revision and the i82586 version + * + * autoprobe for: base_addr: 0x300,0x280,0x360,0x320,0x340 + * mem_start: 0xd0000,0xd2000,0xc8000,0xca000,0xd4000,0xd6000, + * 0xd8000,0xcc000,0xce000,0xda000,0xdc000 + * + * sources: + * skeleton.c from Donald Becker + * + * I have also done a look in the following sources: (mail me if you need them) + * crynwr-packet-driver by Russ Nelson + * Garret A. Wollman's (fourth) i82586-driver for BSD + * (before getting an i82596 (yes 596 not 586) manual, the existing drivers helped + * me a lot to understand this tricky chip.) + * + * Known Problems: + * The internal sysbus seems to be slow. So we often lose packets because of + * overruns while receiving from a fast remote host. + * This can slow down TCP connections. Maybe the newer ni5210 cards are better. + * my experience is, that if a machine sends with more then about 500-600K/s + * the fifo/sysbus overflows. + * + * IMPORTANT NOTE: + * On fast networks, it's a (very) good idea to have 16K shared memory. With + * 8K, we can store only 4 receive frames, so it can (easily) happen that a remote + * machine 'overruns' our system. + * + * Known i82586/card problems (I'm sure, there are many more!): + * Running the NOP-mode, the i82586 sometimes seems to forget to report + * every xmit-interrupt until we restart the CU. + * Another MAJOR bug is, that the RU sometimes seems to ignore the EL-Bit + * in the RBD-Struct which indicates an end of the RBD queue. + * Instead, the RU fetches another (randomly selected and + * usually used) RBD and begins to fill it. (Maybe, this happens only if + * the last buffer from the previous RFD fits exact into the queue and + * the next RFD can't fetch an initial RBD. Anyone knows more? ) + * + * results from ftp performance tests with Linux 1.2.5 + * send and receive about 350-400 KByte/s (peak up to 460 kbytes/s) + * sending in NOP-mode: peak performance up to 530K/s (but better don't run this mode) + */ + +/* + * 19.Feb.96: more Mcast changes, module support (MH) + * + * 18.Nov.95: Mcast changes (AC). + * + * 23.April.95: fixed(?) receiving problems by configuring a RFD more + * than the number of RBD's. Can maybe cause other problems. + * 18.April.95: Added MODULE support (MH) + * 17.April.95: MC related changes in init586() and set_multicast_list(). + * removed use of 'jiffies' in init586() (MH) + * + * 19.Sep.94: Added Multicast support (not tested yet) (MH) + * + * 18.Sep.94: Workaround for 'EL-Bug'. Removed flexible RBD-handling. + * Now, every RFD has exact one RBD. (MH) + * + * 14.Sep.94: added promiscuous mode, a few cleanups (MH) + * + * 19.Aug.94: changed request_irq() parameter (MH) + * + * 20.July.94: removed cleanup bugs, removed a 16K-mem-probe-bug (MH) + * + * 19.July.94: lotsa cleanups .. (MH) + * + * 17.July.94: some patches ... verified to run with 1.1.29 (MH) + * + * 4.July.94: patches for Linux 1.1.24 (MH) + * + * 26.March.94: patches for Linux 1.0 and iomem-auto-probe (MH) + * + * 30.Sep.93: Added nop-chain .. driver now runs with only one Xmit-Buff, too (MH) + * + * < 30.Sep.93: first versions + */ + +static int debuglevel = 0; /* debug-printk 0: off 1: a few 2: more */ +static int automatic_resume = 0; /* experimental .. better should be zero */ +static int rfdadd = 0; /* rfdadd=1 may be better for 8K MEM cards */ +static int fifo=0x8; /* don't change */ + +/* #define REALLY_SLOW_IO */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/bitops.h> +#include <asm/io.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "ni52.h" + +#define DEBUG /* debug on */ +#define SYSBUSVAL 1 /* 8 Bit */ + +#define ni_attn586() {outb(0,dev->base_addr+NI52_ATTENTION);} +#define ni_reset586() {outb(0,dev->base_addr+NI52_RESET);} +#define ni_disint() {outb(0,dev->base_addr+NI52_INTDIS);} +#define ni_enaint() {outb(0,dev->base_addr+NI52_INTENA);} + +#define make32(ptr16) (p->memtop + (short) (ptr16) ) +#define make24(ptr32) ((char *) (ptr32) - p->base) +#define make16(ptr32) ((unsigned short) ((unsigned long) (ptr32) - (unsigned long) p->memtop )) + +/******************* how to calculate the buffers ***************************** + + * IMPORTANT NOTE: if you configure only one NUM_XMIT_BUFFS, the driver works + * --------------- in a different (more stable?) mode. Only in this mode it's + * possible to configure the driver with 'NO_NOPCOMMANDS' + +sizeof(scp)=12; sizeof(scb)=16; sizeof(iscp)=8; +sizeof(scp)+sizeof(iscp)+sizeof(scb) = 36 = INIT +sizeof(rfd) = 24; sizeof(rbd) = 12; +sizeof(tbd) = 8; sizeof(transmit_cmd) = 16; +sizeof(nop_cmd) = 8; + + * if you don't know the driver, better do not change these values: */ + +#define RECV_BUFF_SIZE 1524 /* slightly oversized */ +#define XMIT_BUFF_SIZE 1524 /* slightly oversized */ +#define NUM_XMIT_BUFFS 1 /* config for both, 8K and 16K shmem */ +#define NUM_RECV_BUFFS_8 4 /* config for 8K shared mem */ +#define NUM_RECV_BUFFS_16 9 /* config for 16K shared mem */ +#define NO_NOPCOMMANDS /* only possible with NUM_XMIT_BUFFS=1 */ + +/**************************************************************************/ + +/* different DELAYs */ +#define DELAY(x) __delay((loops_per_sec>>5)*(x)); +#define DELAY_16(); { __delay( (loops_per_sec>>16)+1 ); } +#define DELAY_18(); { __delay( (loops_per_sec>>18)+1 ); } + +/* wait for command with timeout: */ +#define WAIT_4_SCB_CMD() { int i; \ + for(i=0;i<16384;i++) { \ + if(!p->scb->cmd_cuc) break; \ + DELAY_18(); \ + if(i == 16383) { \ + printk("%s: scb_cmd timed out: %04x,%04x .. disabling i82586!!\n",dev->name,p->scb->cmd_cuc,p->scb->cus); \ + if(!p->reseted) { p->reseted = 1; ni_reset586(); } } } } + +#define WAIT_4_SCB_CMD_RUC() { int i; \ + for(i=0;i<16384;i++) { \ + if(!p->scb->cmd_ruc) break; \ + DELAY_18(); \ + if(i == 16383) { \ + printk("%s: scb_cmd (ruc) timed out: %04x,%04x .. disabling i82586!!\n",dev->name,p->scb->cmd_ruc,p->scb->rus); \ + if(!p->reseted) { p->reseted = 1; ni_reset586(); } } } } + +#define WAIT_4_STAT_COMPL(addr) { int i; \ + for(i=0;i<32767;i++) { \ + if((addr)->cmd_status & STAT_COMPL) break; \ + DELAY_16(); DELAY_16(); } } + +#define NI52_TOTAL_SIZE 16 +#define NI52_ADDR0 0x02 +#define NI52_ADDR1 0x07 +#define NI52_ADDR2 0x01 + +static int ni52_probe1(struct device *dev,int ioaddr); +static void ni52_interrupt(int irq,void *dev_id,struct pt_regs *reg_ptr); +static int ni52_open(struct device *dev); +static int ni52_close(struct device *dev); +static int ni52_send_packet(struct sk_buff *,struct device *); +static struct enet_statistics *ni52_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev); +#if 0 +static void ni52_dump(struct device *,void *); +#endif + +/* helper-functions */ +static int init586(struct device *dev); +static int check586(struct device *dev,char *where,unsigned size); +static void alloc586(struct device *dev); +static void startrecv586(struct device *dev); +static void *alloc_rfa(struct device *dev,void *ptr); +static void ni52_rcv_int(struct device *dev); +static void ni52_xmt_int(struct device *dev); +static void ni52_rnr_int(struct device *dev); + +struct priv +{ + struct enet_statistics stats; + unsigned long base; + char *memtop; + int lock,reseted; + volatile struct rfd_struct *rfd_last,*rfd_top,*rfd_first; + volatile struct scp_struct *scp; /* volatile is important */ + volatile struct iscp_struct *iscp; /* volatile is important */ + volatile struct scb_struct *scb; /* volatile is important */ + volatile struct tbd_struct *xmit_buffs[NUM_XMIT_BUFFS]; + volatile struct transmit_cmd_struct *xmit_cmds[NUM_XMIT_BUFFS]; +#if (NUM_XMIT_BUFFS == 1) + volatile struct nop_cmd_struct *nop_cmds[2]; +#else + volatile struct nop_cmd_struct *nop_cmds[NUM_XMIT_BUFFS]; +#endif + volatile int nop_point,num_recv_buffs; + volatile char *xmit_cbuffs[NUM_XMIT_BUFFS]; + volatile int xmit_count,xmit_last; +}; + +/********************************************** + * close device + */ +static int ni52_close(struct device *dev) +{ + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = NULL; + + ni_reset586(); /* the hard way to stop the receiver */ + + dev->start = 0; + dev->tbusy = 0; + + MOD_DEC_USE_COUNT; + + return 0; +} + +/********************************************** + * open device + */ +static int ni52_open(struct device *dev) +{ + ni_disint(); + alloc586(dev); + init586(dev); + startrecv586(dev); + ni_enaint(); + + if(request_irq(dev->irq, &ni52_interrupt,0,"ni5210",NULL)) + { + ni_reset586(); + return -EAGAIN; + } + irq2dev_map[dev->irq] = dev; + + dev->interrupt = 0; + dev->tbusy = 0; + dev->start = 1; + + MOD_INC_USE_COUNT; + + return 0; /* most done by init */ +} + +/********************************************** + * Check to see if there's an 82586 out there. + */ +static int check586(struct device *dev,char *where,unsigned size) +{ + struct priv pb; + struct priv *p = /* (struct priv *) dev->priv*/ &pb; + char *iscp_addrs[2]; + int i; + + p->base = (unsigned long) where + size - 0x01000000; + p->memtop = where + size; + p->scp = (struct scp_struct *)(p->base + SCP_DEFAULT_ADDRESS); + memset((char *)p->scp,0, sizeof(struct scp_struct)); + for(i=0;i<sizeof(struct scp_struct);i++) /* memory was writeable? */ + if(((char *)p->scp)[i]) + return 0; + p->scp->sysbus = SYSBUSVAL; /* 1 = 8Bit-Bus, 0 = 16 Bit */ + if(p->scp->sysbus != SYSBUSVAL) + return 0; + + iscp_addrs[0] = where; + iscp_addrs[1]= (char *) p->scp - sizeof(struct iscp_struct); + + for(i=0;i<2;i++) + { + p->iscp = (struct iscp_struct *) iscp_addrs[i]; + memset((char *)p->iscp,0, sizeof(struct iscp_struct)); + + p->scp->iscp = make24(p->iscp); + p->iscp->busy = 1; + + ni_reset586(); + ni_attn586(); + DELAY(1); /* wait a while... */ + + if(p->iscp->busy) /* i82586 clears 'busy' after successful init */ + return 0; + } + return 1; +} + +/****************************************************************** + * set iscp at the right place, called by ni52_probe1 and open586. + */ +void alloc586(struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + + ni_reset586(); + DELAY(1); + + p->scp = (struct scp_struct *) (p->base + SCP_DEFAULT_ADDRESS); + p->scb = (struct scb_struct *) (dev->mem_start); + p->iscp = (struct iscp_struct *) ((char *)p->scp - sizeof(struct iscp_struct)); + + memset((char *) p->iscp,0,sizeof(struct iscp_struct)); + memset((char *) p->scp ,0,sizeof(struct scp_struct)); + + p->scp->iscp = make24(p->iscp); + p->scp->sysbus = SYSBUSVAL; + p->iscp->scb_offset = make16(p->scb); + + p->iscp->busy = 1; + ni_reset586(); + ni_attn586(); + + DELAY(1); + + if(p->iscp->busy) + printk("%s: Init-Problems (alloc).\n",dev->name); + + p->reseted = 0; + + memset((char *)p->scb,0,sizeof(struct scb_struct)); +} + +/********************************************** + * probe the ni5210-card + */ +int ni52_probe(struct device *dev) +{ +#ifndef MODULE + int *port; + static int ports[] = {0x300, 0x280, 0x360 , 0x320 , 0x340, 0}; +#endif + int base_addr = dev->base_addr; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + if( (inb(base_addr+NI52_MAGIC1) == NI52_MAGICVAL1) && + (inb(base_addr+NI52_MAGIC2) == NI52_MAGICVAL2)) + return ni52_probe1(dev, base_addr); + else if (base_addr > 0) /* Don't probe at all. */ + return ENXIO; + +#ifdef MODULE + printk("%s: no autoprobing allowed for modules.\n",dev->name); +#else + for (port = ports; *port; port++) { + int ioaddr = *port; + if (check_region(ioaddr, NI52_TOTAL_SIZE)) + continue; + if( !(inb(ioaddr+NI52_MAGIC1) == NI52_MAGICVAL1) || + !(inb(ioaddr+NI52_MAGIC2) == NI52_MAGICVAL2)) + continue; + + dev->base_addr = ioaddr; + if (ni52_probe1(dev, ioaddr) == 0) + return 0; + } + +#ifdef FULL_IO_PROBE + for(dev->base_addr=0x200;dev->base_addr<0x400;dev->base_addr+=8) + { + int ioaddr = dev->base_addr; + if (check_region(ioaddr, NI52_TOTAL_SIZE)) + continue; + if( !(inb(ioaddr+NI52_MAGIC1) == NI52_MAGICVAL1) || + !(inb(ioaddr+NI52_MAGIC2) == NI52_MAGICVAL2)) + continue; + if (ni52_probe1(dev, ioaddr) == 0) + return 0; + } +#endif + +#endif + + dev->base_addr = base_addr; + return ENODEV; +} + +static int ni52_probe1(struct device *dev,int ioaddr) +{ + int i,size; + + for(i=0;i<ETH_ALEN;i++) + dev->dev_addr[i] = inb(dev->base_addr+i); + + if(dev->dev_addr[0] != NI52_ADDR0 || dev->dev_addr[1] != NI52_ADDR1 + || dev->dev_addr[2] != NI52_ADDR2) + return ENODEV; + + printk("%s: NI5210 found at %#3lx, ",dev->name,dev->base_addr); + + request_region(ioaddr,NI52_TOTAL_SIZE,"ni5210"); + + /* + * check (or search) IO-Memory, 8K and 16K + */ +#ifdef MODULE + size = dev->mem_end - dev->mem_start; + if(size != 0x2000 && size != 0x4000) + { + printk("\n%s: Illegal memory size %d. Allowed is 0x2000 or 0x4000 bytes.\n",dev->name,size); + return ENODEV; + } + if(!check586(dev,(char *) dev->mem_start,size)) + { + printk("?memcheck, Can't find memory at 0x%lx with size %d!\n",dev->mem_start,size); + return ENODEV; + } +#else + if(dev->mem_start != 0) /* no auto-mem-probe */ + { + size = 0x4000; /* check for 16K mem */ + if(!check586(dev,(char *) dev->mem_start,size)) { + size = 0x2000; /* check for 8K mem */ + if(!check586(dev,(char *) dev->mem_start,size)) { + printk("?memprobe, Can't find memory at 0x%lx!\n",dev->mem_start); + return ENODEV; + } + } + } + else + { + static long memaddrs[] = { 0xc8000,0xca000,0xcc000,0xce000,0xd0000,0xd2000, + 0xd4000,0xd6000,0xd8000,0xda000,0xdc000, 0 }; + for(i=0;;i++) + { + if(!memaddrs[i]) { + printk("?memprobe, Can't find io-memory!\n"); + return ENODEV; + } + dev->mem_start = memaddrs[i]; + size = 0x2000; /* check for 8K mem */ + if(check586(dev,(char *)dev->mem_start,size)) /* 8K-check */ + break; + size = 0x4000; /* check for 16K mem */ + if(check586(dev,(char *)dev->mem_start,size)) /* 16K-check */ + break; + } + } + dev->mem_end = dev->mem_start + size; /* set mem_end showed by 'ifconfig' */ +#endif + + dev->priv = (void *) kmalloc(sizeof(struct priv),GFP_KERNEL); + if(dev->priv == NULL) + { + printk("%s: Ooops .. can't allocate private driver memory.\n",dev->name); + return -ENOMEM; + } + /* warning: we don't free it on errors */ + memset((char *) dev->priv,0,sizeof(struct priv)); + + ((struct priv *) (dev->priv))->memtop = (char *) dev->mem_start + size; + ((struct priv *) (dev->priv))->base = dev->mem_start + size - 0x01000000; + alloc586(dev); + + /* set number of receive-buffs according to memsize */ + if(size == 0x2000) + ((struct priv *) dev->priv)->num_recv_buffs = NUM_RECV_BUFFS_8; + else + ((struct priv *) dev->priv)->num_recv_buffs = NUM_RECV_BUFFS_16; + + printk("Memaddr: 0x%lx, Memsize: %d, ",dev->mem_start,size); + + if(dev->irq < 2) + { + autoirq_setup(0); + ni_reset586(); + ni_attn586(); + if(!(dev->irq = autoirq_report(2))) + { + printk("?autoirq, Failed to detect IRQ line!\n"); + return 1; + } + printk("IRQ %d (autodetected).\n",dev->irq); + } + else { + if(dev->irq == 2) + dev->irq = 9; + printk("IRQ %d (assigned and not checked!).\n",dev->irq); + } + + dev->open = &ni52_open; + dev->stop = &ni52_close; + dev->get_stats = &ni52_get_stats; + dev->hard_start_xmit = &ni52_send_packet; + dev->set_multicast_list = &set_multicast_list; + + dev->if_port = 0; + + ether_setup(dev); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 0; + + return 0; +} + +/********************************************** + * init the chip (ni52-interrupt should be disabled?!) + * needs a correct 'allocated' memory + */ + +static int init586(struct device *dev) +{ + void *ptr; + int i,result=0; + struct priv *p = (struct priv *) dev->priv; + volatile struct configure_cmd_struct *cfg_cmd; + volatile struct iasetup_cmd_struct *ias_cmd; + volatile struct tdr_cmd_struct *tdr_cmd; + volatile struct mcsetup_cmd_struct *mc_cmd; + struct dev_mc_list *dmi=dev->mc_list; + int num_addrs=dev->mc_count; + + ptr = (void *) ((char *)p->scb + sizeof(struct scb_struct)); + + cfg_cmd = (struct configure_cmd_struct *)ptr; /* configure-command */ + cfg_cmd->cmd_status = 0; + cfg_cmd->cmd_cmd = CMD_CONFIGURE | CMD_LAST; + cfg_cmd->cmd_link = 0xffff; + + cfg_cmd->byte_cnt = 0x0a; /* number of cfg bytes */ + cfg_cmd->fifo = fifo; /* fifo-limit (8=tx:32/rx:64) */ + cfg_cmd->sav_bf = 0x40; /* hold or discard bad recv frames (bit 7) */ + cfg_cmd->adr_len = 0x2e; /* addr_len |!src_insert |pre-len |loopback */ + cfg_cmd->priority = 0x00; + cfg_cmd->ifs = 0x60; + cfg_cmd->time_low = 0x00; + cfg_cmd->time_high = 0xf2; + cfg_cmd->promisc = 0; + if(dev->flags & IFF_ALLMULTI) { + int len = ((char *) p->iscp - (char *) ptr - 8) / 6; + if(num_addrs > len) { + printk("%s: switching to promisc. mode\n",dev->name); + dev->flags|=IFF_PROMISC; + } + } + if(dev->flags&IFF_PROMISC) + { + cfg_cmd->promisc=1; + dev->flags|=IFF_PROMISC; + } + cfg_cmd->carr_coll = 0x00; + + p->scb->cbl_offset = make16(cfg_cmd); + p->scb->cmd_ruc = 0; + + p->scb->cmd_cuc = CUC_START; /* cmd.-unit start */ + ni_attn586(); + + WAIT_4_STAT_COMPL(cfg_cmd); + + if((cfg_cmd->cmd_status & (STAT_OK|STAT_COMPL)) != (STAT_COMPL|STAT_OK)) + { + printk("%s: configure command failed: %x\n",dev->name,cfg_cmd->cmd_status); + return 1; + } + + /* + * individual address setup + */ + ias_cmd = (struct iasetup_cmd_struct *)ptr; + + ias_cmd->cmd_status = 0; + ias_cmd->cmd_cmd = CMD_IASETUP | CMD_LAST; + ias_cmd->cmd_link = 0xffff; + + memcpy((char *)&ias_cmd->iaddr,(char *) dev->dev_addr,ETH_ALEN); + + p->scb->cbl_offset = make16(ias_cmd); + + p->scb->cmd_cuc = CUC_START; /* cmd.-unit start */ + ni_attn586(); + + WAIT_4_STAT_COMPL(ias_cmd); + + if((ias_cmd->cmd_status & (STAT_OK|STAT_COMPL)) != (STAT_OK|STAT_COMPL)) { + printk("%s (ni52): individual address setup command failed: %04x\n",dev->name,ias_cmd->cmd_status); + return 1; + } + + /* + * TDR, wire check .. e.g. no resistor e.t.c + */ + tdr_cmd = (struct tdr_cmd_struct *)ptr; + + tdr_cmd->cmd_status = 0; + tdr_cmd->cmd_cmd = CMD_TDR | CMD_LAST; + tdr_cmd->cmd_link = 0xffff; + tdr_cmd->status = 0; + + p->scb->cbl_offset = make16(tdr_cmd); + p->scb->cmd_cuc = CUC_START; /* cmd.-unit start */ + ni_attn586(); + + WAIT_4_STAT_COMPL(tdr_cmd); + + if(!(tdr_cmd->cmd_status & STAT_COMPL)) + { + printk("%s: Problems while running the TDR.\n",dev->name); + } + else + { + DELAY_16(); /* wait for result */ + result = tdr_cmd->status; + + p->scb->cmd_cuc = p->scb->cus & STAT_MASK; + ni_attn586(); /* ack the interrupts */ + + if(result & TDR_LNK_OK) + ; + else if(result & TDR_XCVR_PRB) + printk("%s: TDR: Transceiver problem. Check the cable(s)!\n",dev->name); + else if(result & TDR_ET_OPN) + printk("%s: TDR: No correct termination %d clocks away.\n",dev->name,result & TDR_TIMEMASK); + else if(result & TDR_ET_SRT) + { + if (result & TDR_TIMEMASK) /* time == 0 -> strange :-) */ + printk("%s: TDR: Detected a short circuit %d clocks away.\n",dev->name,result & TDR_TIMEMASK); + } + else + printk("%s: TDR: Unknown status %04x\n",dev->name,result); + } + + /* + * Multicast setup + */ + if(num_addrs && !(dev->flags & IFF_PROMISC) ) + { + mc_cmd = (struct mcsetup_cmd_struct *) ptr; + mc_cmd->cmd_status = 0; + mc_cmd->cmd_cmd = CMD_MCSETUP | CMD_LAST; + mc_cmd->cmd_link = 0xffff; + mc_cmd->mc_cnt = num_addrs * 6; + + for(i=0;i<num_addrs;i++,dmi=dmi->next) + memcpy((char *) mc_cmd->mc_list[i], dmi->dmi_addr,6); + + p->scb->cbl_offset = make16(mc_cmd); + p->scb->cmd_cuc = CUC_START; + ni_attn586(); + + WAIT_4_STAT_COMPL(mc_cmd); + + if( (mc_cmd->cmd_status & (STAT_COMPL|STAT_OK)) != (STAT_COMPL|STAT_OK) ) + printk("%s: Can't apply multicast-address-list.\n",dev->name); + } + + /* + * alloc nop/xmit-cmds + */ +#if (NUM_XMIT_BUFFS == 1) + for(i=0;i<2;i++) + { + p->nop_cmds[i] = (struct nop_cmd_struct *)ptr; + p->nop_cmds[i]->cmd_cmd = CMD_NOP; + p->nop_cmds[i]->cmd_status = 0; + p->nop_cmds[i]->cmd_link = make16((p->nop_cmds[i])); + ptr = (char *) ptr + sizeof(struct nop_cmd_struct); + } +#else + for(i=0;i<NUM_XMIT_BUFFS;i++) + { + p->nop_cmds[i] = (struct nop_cmd_struct *)ptr; + p->nop_cmds[i]->cmd_cmd = CMD_NOP; + p->nop_cmds[i]->cmd_status = 0; + p->nop_cmds[i]->cmd_link = make16((p->nop_cmds[i])); + ptr = (char *) ptr + sizeof(struct nop_cmd_struct); + } +#endif + + ptr = alloc_rfa(dev,(void *)ptr); /* init receive-frame-area */ + + /* + * alloc xmit-buffs / init xmit_cmds + */ + for(i=0;i<NUM_XMIT_BUFFS;i++) + { + p->xmit_cmds[i] = (struct transmit_cmd_struct *)ptr; /*transmit cmd/buff 0*/ + ptr = (char *) ptr + sizeof(struct transmit_cmd_struct); + p->xmit_cbuffs[i] = (char *)ptr; /* char-buffs */ + ptr = (char *) ptr + XMIT_BUFF_SIZE; + p->xmit_buffs[i] = (struct tbd_struct *)ptr; /* TBD */ + ptr = (char *) ptr + sizeof(struct tbd_struct); + if((void *)ptr > (void *)p->iscp) + { + printk("%s: not enough shared-mem for your configuration!\n",dev->name); + return 1; + } + memset((char *)(p->xmit_cmds[i]) ,0, sizeof(struct transmit_cmd_struct)); + memset((char *)(p->xmit_buffs[i]),0, sizeof(struct tbd_struct)); + p->xmit_cmds[i]->cmd_link = make16(p->nop_cmds[(i+1)%NUM_XMIT_BUFFS]); + p->xmit_cmds[i]->cmd_status = STAT_COMPL; + p->xmit_cmds[i]->cmd_cmd = CMD_XMIT | CMD_INT; + p->xmit_cmds[i]->tbd_offset = make16((p->xmit_buffs[i])); + p->xmit_buffs[i]->next = 0xffff; + p->xmit_buffs[i]->buffer = make24((p->xmit_cbuffs[i])); + } + + p->xmit_count = 0; + p->xmit_last = 0; +#ifndef NO_NOPCOMMANDS + p->nop_point = 0; +#endif + + /* + * 'start transmitter' + */ +#ifndef NO_NOPCOMMANDS + p->scb->cbl_offset = make16(p->nop_cmds[0]); + p->scb->cmd_cuc = CUC_START; + ni_attn586(); + WAIT_4_SCB_CMD(); +#else + p->xmit_cmds[0]->cmd_link = make16(p->xmit_cmds[0]); + p->xmit_cmds[0]->cmd_cmd = CMD_XMIT | CMD_SUSPEND | CMD_INT; +#endif + + /* + * ack. interrupts + */ + p->scb->cmd_cuc = p->scb->cus & STAT_MASK; + ni_attn586(); + DELAY_16(); + + ni_enaint(); + + return 0; +} + +/****************************************************** + * This is a helper routine for ni52_rnr_int() and init586(). + * It sets up the Receive Frame Area (RFA). + */ + +static void *alloc_rfa(struct device *dev,void *ptr) +{ + volatile struct rfd_struct *rfd = (struct rfd_struct *)ptr; + volatile struct rbd_struct *rbd; + int i; + struct priv *p = (struct priv *) dev->priv; + + memset((char *) rfd,0,sizeof(struct rfd_struct)*(p->num_recv_buffs+rfdadd)); + p->rfd_first = rfd; + + for(i = 0; i < (p->num_recv_buffs+rfdadd); i++) { + rfd[i].next = make16(rfd + (i+1) % (p->num_recv_buffs+rfdadd) ); + rfd[i].rbd_offset = 0xffff; + } + rfd[p->num_recv_buffs-1+rfdadd].last = RFD_SUSP; /* RU suspend */ + + ptr = (void *) (rfd + (p->num_recv_buffs + rfdadd) ); + + rbd = (struct rbd_struct *) ptr; + ptr = (void *) (rbd + p->num_recv_buffs); + + /* clr descriptors */ + memset((char *) rbd,0,sizeof(struct rbd_struct)*(p->num_recv_buffs)); + + for(i=0;i<p->num_recv_buffs;i++) + { + rbd[i].next = make16((rbd + (i+1) % p->num_recv_buffs)); + rbd[i].size = RECV_BUFF_SIZE; + rbd[i].buffer = make24(ptr); + ptr = (char *) ptr + RECV_BUFF_SIZE; + } + + p->rfd_top = p->rfd_first; + p->rfd_last = p->rfd_first + (p->num_recv_buffs - 1 + rfdadd); + + p->scb->rfa_offset = make16(p->rfd_first); + p->rfd_first->rbd_offset = make16(rbd); + + return ptr; +} + + +/************************************************** + * Interrupt Handler ... + */ + +static void ni52_interrupt(int irq,void *dev_id,struct pt_regs *reg_ptr) +{ + struct device *dev = (struct device *) irq2dev_map[irq]; + unsigned short stat; + int cnt=0; + struct priv *p; + + if (!dev) { + printk ("ni5210-interrupt: irq %d for unknown device.\n",irq); + return; + } + p = (struct priv *) dev->priv; + + if(debuglevel > 1) + printk("I"); + + dev->interrupt = 1; + + WAIT_4_SCB_CMD(); /* wait for last command */ + + while((stat=p->scb->cus & STAT_MASK)) + { + p->scb->cmd_cuc = stat; + ni_attn586(); + + if(stat & STAT_FR) /* received a frame */ + ni52_rcv_int(dev); + + if(stat & STAT_RNR) /* RU went 'not ready' */ + { + printk("(R)"); + if(p->scb->rus & RU_SUSPEND) /* special case: RU_SUSPEND */ + { + WAIT_4_SCB_CMD(); + p->scb->cmd_ruc = RUC_RESUME; + ni_attn586(); + WAIT_4_SCB_CMD_RUC(); + } + else + { + printk("%s: Receiver-Unit went 'NOT READY': %04x/%02x.\n",dev->name,(int) stat,(int) p->scb->rus); + ni52_rnr_int(dev); + } + } + + if(stat & STAT_CX) /* command with I-bit set complete */ + ni52_xmt_int(dev); + +#ifndef NO_NOPCOMMANDS + if(stat & STAT_CNA) /* CU went 'not ready' */ + { + if(dev->start) + printk("%s: oops! CU has left active state. stat: %04x/%02x.\n",dev->name,(int) stat,(int) p->scb->cus); + } +#endif + + if(debuglevel > 1) + printk("%d",cnt++); + + WAIT_4_SCB_CMD(); /* wait for ack. (ni52_xmt_int can be faster than ack!!) */ + if(p->scb->cmd_cuc) /* timed out? */ + { + printk("%s: Acknowledge timed out.\n",dev->name); + ni_disint(); + break; + } + } + + if(debuglevel > 1) + printk("i"); + + dev->interrupt = 0; +} + +/******************************************************* + * receive-interrupt + */ + +static void ni52_rcv_int(struct device *dev) +{ + int status,cnt=0; + unsigned short totlen; + struct sk_buff *skb; + struct rbd_struct *rbd; + struct priv *p = (struct priv *) dev->priv; + + if(debuglevel > 0) + printk("R"); + + for(;(status = p->rfd_top->stat_high) & RFD_COMPL;) + { + rbd = (struct rbd_struct *) make32(p->rfd_top->rbd_offset); + + if(status & RFD_OK) /* frame received without error? */ + { + if( (totlen = rbd->status) & RBD_LAST) /* the first and the last buffer? */ + { + totlen &= RBD_MASK; /* length of this frame */ + rbd->status = 0; + skb = (struct sk_buff *) dev_alloc_skb(totlen+2); + if(skb != NULL) + { + skb->dev = dev; + skb_reserve(skb,2); + skb_put(skb,totlen); + eth_copy_and_sum(skb,(char *) p->base+(unsigned long) rbd->buffer,totlen,0); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + p->stats.rx_packets++; + } + else + p->stats.rx_dropped++; + } + else + { + int rstat; + /* free all RBD's until RBD_LAST is set */ + totlen = 0; + while(!((rstat=rbd->status) & RBD_LAST)) + { + totlen += rstat & RBD_MASK; + if(!rstat) + { + printk("%s: Whoops .. no end mark in RBD list\n",dev->name); + break; + } + rbd->status = 0; + rbd = (struct rbd_struct *) make32(rbd->next); + } + totlen += rstat & RBD_MASK; + rbd->status = 0; + printk("%s: received oversized frame! length: %d\n",dev->name,totlen); + p->stats.rx_dropped++; + } + } + else /* frame !(ok), only with 'save-bad-frames' */ + { + printk("%s: oops! rfd-error-status: %04x\n",dev->name,status); + p->stats.rx_errors++; + } + p->rfd_top->stat_high = 0; + p->rfd_top->last = RFD_SUSP; /* maybe exchange by RFD_LAST */ + p->rfd_top->rbd_offset = 0xffff; + p->rfd_last->last = 0; /* delete RFD_SUSP */ + p->rfd_last = p->rfd_top; + p->rfd_top = (struct rfd_struct *) make32(p->rfd_top->next); /* step to next RFD */ + p->scb->rfa_offset = make16(p->rfd_top); + + if(debuglevel > 0) + printk("%d",cnt++); + } + + if(automatic_resume) + { + WAIT_4_SCB_CMD(); + p->scb->cmd_ruc = RUC_RESUME; + ni_attn586(); + WAIT_4_SCB_CMD_RUC(); + } + +#ifdef WAIT_4_BUSY + { + int i; + for(i=0;i<1024;i++) + { + if(p->rfd_top->status) + break; + DELAY_16(); + if(i == 1023) + printk("%s: RU hasn't fetched next RFD (not busy/complete)\n",dev->name); + } + } +#endif + +#if 0 + if(!at_least_one) + { + int i; + volatile struct rfd_struct *rfds=p->rfd_top; + volatile struct rbd_struct *rbds; + printk("%s: received a FC intr. without having a frame: %04x %d\n",dev->name,status,old_at_least); + for(i=0;i< (p->num_recv_buffs+4);i++) + { + rbds = (struct rbd_struct *) make32(rfds->rbd_offset); + printk("%04x:%04x ",rfds->status,rbds->status); + rfds = (struct rfd_struct *) make32(rfds->next); + } + printk("\nerrs: %04x %04x stat: %04x\n",(int)p->scb->rsc_errs,(int)p->scb->ovrn_errs,(int)p->scb->status); + printk("\nerrs: %04x %04x rus: %02x, cus: %02x\n",(int)p->scb->rsc_errs,(int)p->scb->ovrn_errs,(int)p->scb->rus,(int)p->scb->cus); + } + old_at_least = at_least_one; +#endif + + if(debuglevel > 0) + printk("r"); +} + +/********************************************************** + * handle 'Receiver went not ready'. + */ + +static void ni52_rnr_int(struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + + p->stats.rx_errors++; + + WAIT_4_SCB_CMD(); /* wait for the last cmd, WAIT_4_FULLSTAT?? */ + p->scb->cmd_ruc = RUC_ABORT; /* usually the RU is in the 'no resource'-state .. abort it now. */ + ni_attn586(); + WAIT_4_SCB_CMD_RUC(); /* wait for accept cmd. */ + + alloc_rfa(dev,(char *)p->rfd_first); +/* maybe add a check here, before restarting the RU */ + startrecv586(dev); /* restart RU */ + + printk("%s: Receive-Unit restarted. Status: %04x\n",dev->name,p->scb->rus); + +} + +/********************************************************** + * handle xmit - interrupt + */ + +static void ni52_xmt_int(struct device *dev) +{ + int status; + struct priv *p = (struct priv *) dev->priv; + + if(debuglevel > 0) + printk("X"); + + status = p->xmit_cmds[p->xmit_last]->cmd_status; + if(!(status & STAT_COMPL)) + printk("%s: strange .. xmit-int without a 'COMPLETE'\n",dev->name); + + if(status & STAT_OK) + { + p->stats.tx_packets++; + p->stats.collisions += (status & TCMD_MAXCOLLMASK); + } + else + { + p->stats.tx_errors++; + if(status & TCMD_LATECOLL) { + printk("%s: late collision detected.\n",dev->name); + p->stats.collisions++; + } + else if(status & TCMD_NOCARRIER) { + p->stats.tx_carrier_errors++; + printk("%s: no carrier detected.\n",dev->name); + } + else if(status & TCMD_LOSTCTS) + printk("%s: loss of CTS detected.\n",dev->name); + else if(status & TCMD_UNDERRUN) { + p->stats.tx_fifo_errors++; + printk("%s: DMA underrun detected.\n",dev->name); + } + else if(status & TCMD_MAXCOLL) { + printk("%s: Max. collisions exceeded.\n",dev->name); + p->stats.collisions += 16; + } + } + +#if (NUM_XMIT_BUFFS > 1) + if( (++p->xmit_last) == NUM_XMIT_BUFFS) + p->xmit_last = 0; +#endif + + dev->tbusy = 0; + mark_bh(NET_BH); +} + +/*********************************************************** + * (re)start the receiver + */ + +static void startrecv586(struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + + WAIT_4_SCB_CMD(); + WAIT_4_SCB_CMD_RUC(); + p->scb->rfa_offset = make16(p->rfd_first); + p->scb->cmd_ruc = RUC_START; + ni_attn586(); /* start cmd. */ + WAIT_4_SCB_CMD_RUC(); /* wait for accept cmd. (no timeout!!) */ +} + +/****************************************************** + * send frame + */ + +static int ni52_send_packet(struct sk_buff *skb, struct device *dev) +{ + int len,i; +#ifndef NO_NOPCOMMANDS + int next_nop; +#endif + struct priv *p = (struct priv *) dev->priv; + + if(dev->tbusy) + { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 5) + return 1; + +#ifndef NO_NOPCOMMANDS + if(p->scb->cus & CU_ACTIVE) /* COMMAND-UNIT active? */ + { + dev->tbusy = 0; +#ifdef DEBUG + printk("%s: strange ... timeout with CU active?!?\n",dev->name); + printk("%s: X0: %04x N0: %04x N1: %04x %d\n",dev->name,(int)p->xmit_cmds[0]->cmd_status,(int)p->nop_cmds[0]->cmd_status,(int)p->nop_cmds[1]->cmd_status,(int)p->nop_point); +#endif + p->scb->cmd_cuc = CUC_ABORT; + ni_attn586(); + WAIT_4_SCB_CMD(); + p->scb->cbl_offset = make16(p->nop_cmds[p->nop_point]); + p->scb->cmd_cuc = CUC_START; + ni_attn586(); + WAIT_4_SCB_CMD(); + dev->trans_start = jiffies; + dev_kfree_skb(skb, FREE_WRITE); + return 0; + } + else +#endif + { +#ifdef DEBUG + printk("%s: xmitter timed out, try to restart! stat: %02x\n",dev->name,p->scb->cus); + printk("%s: command-stats: %04x %04x\n",dev->name,p->xmit_cmds[0]->cmd_status,p->xmit_cmds[1]->cmd_status); + printk("%s: check, whether you set the right interrupt number!\n",dev->name); +#endif + ni52_close(dev); + ni52_open(dev); + } + dev->trans_start = jiffies; + return 0; + } + + if(skb == NULL) + { + dev_tint(dev); + return 0; + } + + if (skb->len <= 0) + return 0; + if(skb->len > XMIT_BUFF_SIZE) + { + printk("%s: Sorry, max. framelength is %d bytes. The length of your frame is %ld bytes.\n",dev->name,XMIT_BUFF_SIZE,skb->len); + return 0; + } + + if (set_bit(0, (void*)&dev->tbusy)) { + printk("%s: Transmitter access conflict.\n", dev->name); + return 1; + } +#if(NUM_XMIT_BUFFS > 1) + else if(set_bit(0,(void *) &p->lock)) { + printk("%s: Queue was locked\n",dev->name); + return 1; + } +#endif + else + { + memcpy((char *)p->xmit_cbuffs[p->xmit_count],(char *)(skb->data),skb->len); + len = (ETH_ZLEN < skb->len) ? skb->len : ETH_ZLEN; + +#if (NUM_XMIT_BUFFS == 1) +# ifdef NO_NOPCOMMANDS + +#ifdef DEBUG + if(p->scb->cus & CU_ACTIVE) + { + printk("%s: Hmmm .. CU is still running and we wanna send a new packet.\n",dev->name); + printk("%s: stat: %04x %04x\n",dev->name,p->scb->cus,p->xmit_cmds[0]->cmd_status); + } +#endif + + p->xmit_buffs[0]->size = TBD_LAST | len; + for(i=0;i<16;i++) + { + p->xmit_cmds[0]->cmd_status = 0; + WAIT_4_SCB_CMD(); + if( (p->scb->cus & CU_STATUS) == CU_SUSPEND) + p->scb->cmd_cuc = CUC_RESUME; + else + { + p->scb->cbl_offset = make16(p->xmit_cmds[0]); + p->scb->cmd_cuc = CUC_START; + } + + ni_attn586(); + dev->trans_start = jiffies; + if(!i) + dev_kfree_skb(skb,FREE_WRITE); + WAIT_4_SCB_CMD(); + if( (p->scb->cus & CU_ACTIVE)) /* test it, because CU sometimes doesn't start immediately */ + break; + if(p->xmit_cmds[0]->cmd_status) + break; + if(i==15) + printk("%s: Can't start transmit-command.\n",dev->name); + } +# else + next_nop = (p->nop_point + 1) & 0x1; + p->xmit_buffs[0]->size = TBD_LAST | len; + + p->xmit_cmds[0]->cmd_link = p->nop_cmds[next_nop]->cmd_link + = make16((p->nop_cmds[next_nop])); + p->xmit_cmds[0]->cmd_status = p->nop_cmds[next_nop]->cmd_status = 0; + + p->nop_cmds[p->nop_point]->cmd_link = make16((p->xmit_cmds[0])); + dev->trans_start = jiffies; + p->nop_point = next_nop; + dev_kfree_skb(skb,FREE_WRITE); +# endif +#else + p->xmit_buffs[p->xmit_count]->size = TBD_LAST | len; + if( (next_nop = p->xmit_count + 1) == NUM_XMIT_BUFFS ) + next_nop = 0; + + p->xmit_cmds[p->xmit_count]->cmd_status = 0; + /* linkpointer of xmit-command already points to next nop cmd */ + p->nop_cmds[next_nop]->cmd_link = make16((p->nop_cmds[next_nop])); + p->nop_cmds[next_nop]->cmd_status = 0; + + p->nop_cmds[p->xmit_count]->cmd_link = make16((p->xmit_cmds[p->xmit_count])); + dev->trans_start = jiffies; + p->xmit_count = next_nop; + + { + long flags; + save_flags(flags); + cli(); + if(p->xmit_count != p->xmit_last) + dev->tbusy = 0; + p->lock = 0; + restore_flags(flags); + } + dev_kfree_skb(skb,FREE_WRITE); +#endif + } + return 0; +} + +/******************************************* + * Someone wanna have the statistics + */ + +static struct enet_statistics *ni52_get_stats(struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + unsigned short crc,aln,rsc,ovrn; + + crc = p->scb->crc_errs; /* get error-statistic from the ni82586 */ + p->scb->crc_errs = 0; + aln = p->scb->aln_errs; + p->scb->aln_errs = 0; + rsc = p->scb->rsc_errs; + p->scb->rsc_errs = 0; + ovrn = p->scb->ovrn_errs; + p->scb->ovrn_errs = 0; + + p->stats.rx_crc_errors += crc; + p->stats.rx_fifo_errors += ovrn; + p->stats.rx_frame_errors += aln; + p->stats.rx_dropped += rsc; + + return &p->stats; +} + +/******************************************************** + * Set MC list .. + */ +static void set_multicast_list(struct device *dev) +{ + if(!dev->start) + { + printk("%s: Can't apply promiscuous/multicastmode to a not running interface.\n",dev->name); + return; + } + + dev->start = 0; + + ni_disint(); + alloc586(dev); + init586(dev); + startrecv586(dev); + ni_enaint(); + + dev->start = 1; +} + +#ifdef MODULE +static struct device dev_ni52 = { + " ", /* "ni5210": device name inserted by net_init.c */ + 0, 0, 0, 0, + 0x300, 9, /* I/O address, IRQ */ + 0, 0, 0, NULL, ni52_probe }; + +/* set: io,irq,memstart,memend or set it when calling insmod */ +int irq=9; +int io=0x300; +long memstart=0; /* e.g 0xd0000 */ +long memend=0; /* e.g 0xd4000 */ + +int init_module(void) +{ + if(io <= 0x0 || !memend || !memstart || irq < 2) { + printk("ni52: Autoprobing not allowed for modules.\nni52: Set symbols 'io' 'irq' 'memstart' and 'memend'\n"); + return -ENODEV; + } + dev_ni52.irq = irq; + dev_ni52.base_addr = io; + dev_ni52.mem_end = memend; + dev_ni52.mem_start = memstart; + if (register_netdev(&dev_ni52) != 0) + return -EIO; + return 0; +} + +void cleanup_module(void) +{ + release_region(dev_ni52.base_addr, NI52_TOTAL_SIZE); + kfree(dev_ni52.priv); + dev_ni52.priv = NULL; + unregister_netdev(&dev_ni52); +} +#endif /* MODULE */ + +#if 0 +/* + * DUMP .. we expect a not running CMD unit and enough space + */ +void ni52_dump(struct device *dev,void *ptr) +{ + struct priv *p = (struct priv *) dev->priv; + struct dump_cmd_struct *dump_cmd = (struct dump_cmd_struct *) ptr; + int i; + + p->scb->cmd_cuc = CUC_ABORT; + ni_attn586(); + WAIT_4_SCB_CMD(); + WAIT_4_SCB_CMD_RUC(); + + dump_cmd->cmd_status = 0; + dump_cmd->cmd_cmd = CMD_DUMP | CMD_LAST; + dump_cmd->dump_offset = make16((dump_cmd + 1)); + dump_cmd->cmd_link = 0xffff; + + p->scb->cbl_offset = make16(dump_cmd); + p->scb->cmd_cuc = CUC_START; + ni_attn586(); + WAIT_4_STAT_COMPL(dump_cmd); + + if( (dump_cmd->cmd_status & (STAT_COMPL|STAT_OK)) != (STAT_COMPL|STAT_OK) ) + printk("%s: Can't get dump information.\n",dev->name); + + for(i=0;i<170;i++) { + printk("%02x ",(int) ((unsigned char *) (dump_cmd + 1))[i]); + if(i % 24 == 23) + printk("\n"); + } + printk("\n"); +} +#endif + +/* + * END: linux/drivers/net/ni52.c + */ + + diff --git a/linux/src/drivers/net/ni52.h b/linux/src/drivers/net/ni52.h new file mode 100644 index 00000000..b3dfdd21 --- /dev/null +++ b/linux/src/drivers/net/ni52.h @@ -0,0 +1,310 @@ +/* + * Intel i82586 Ethernet definitions + * + * This is an extension to the Linux operating system, and is covered by the + * same Gnu Public License that covers that work. + * + * copyrights (c) 1994 by Michael Hipp (mhipp@student.uni-tuebingen.de) + * + * I have done a look in the following sources: + * crynwr-packet-driver by Russ Nelson + * Garret A. Wollman's i82586-driver for BSD + */ + + +#define NI52_RESET 0 /* writing to this address, resets the i82586 */ +#define NI52_ATTENTION 1 /* channel attention, kick the 586 */ +#define NI52_TENA 3 /* 2-5 possibly wrong, Xmit enable */ +#define NI52_TDIS 2 /* Xmit disable */ +#define NI52_INTENA 5 /* Interrupt enable */ +#define NI52_INTDIS 4 /* Interrupt disable */ +#define NI52_MAGIC1 6 /* dunno exact function */ +#define NI52_MAGIC2 7 /* dunno exact function */ + +#define NI52_MAGICVAL1 0x00 /* magic-values for ni5210 card */ +#define NI52_MAGICVAL2 0x55 + +/* + * where to find the System Configuration Pointer (SCP) + */ +#define SCP_DEFAULT_ADDRESS 0xfffff4 + + +/* + * System Configuration Pointer Struct + */ + +struct scp_struct +{ + unsigned short zero_dum0; /* has to be zero */ + unsigned char sysbus; /* 0=16Bit,1=8Bit */ + unsigned char zero_dum1; /* has to be zero for 586 */ + unsigned short zero_dum2; + unsigned short zero_dum3; + char *iscp; /* pointer to the iscp-block */ +}; + + +/* + * Intermediate System Configuration Pointer (ISCP) + */ +struct iscp_struct +{ + unsigned char busy; /* 586 clears after successful init */ + unsigned char zero_dummy; /* has to be zero */ + unsigned short scb_offset; /* pointeroffset to the scb_base */ + char *scb_base; /* base-address of all 16-bit offsets */ +}; + +/* + * System Control Block (SCB) + */ +struct scb_struct +{ + unsigned char rus; + unsigned char cus; + unsigned char cmd_ruc; /* command word: RU part */ + unsigned char cmd_cuc; /* command word: CU part & ACK */ + unsigned short cbl_offset; /* pointeroffset, command block list */ + unsigned short rfa_offset; /* pointeroffset, receive frame area */ + unsigned short crc_errs; /* CRC-Error counter */ + unsigned short aln_errs; /* allignmenterror counter */ + unsigned short rsc_errs; /* Resourceerror counter */ + unsigned short ovrn_errs; /* OVerrunerror counter */ +}; + +/* + * possible command values for the command word + */ +#define RUC_MASK 0x0070 /* mask for RU commands */ +#define RUC_NOP 0x0000 /* NOP-command */ +#define RUC_START 0x0010 /* start RU */ +#define RUC_RESUME 0x0020 /* resume RU after suspend */ +#define RUC_SUSPEND 0x0030 /* suspend RU */ +#define RUC_ABORT 0x0040 /* abort receiver operation immediately */ + +#define CUC_MASK 0x07 /* mask for CU command */ +#define CUC_NOP 0x00 /* NOP-command */ +#define CUC_START 0x01 /* start execution of 1. cmd on the CBL */ +#define CUC_RESUME 0x02 /* resume after suspend */ +#define CUC_SUSPEND 0x03 /* Suspend CU */ +#define CUC_ABORT 0x04 /* abort command operation immediately */ + +#define ACK_MASK 0xf0 /* mask for ACK command */ +#define ACK_CX 0x80 /* acknowledges STAT_CX */ +#define ACK_FR 0x40 /* ack. STAT_FR */ +#define ACK_CNA 0x20 /* ack. STAT_CNA */ +#define ACK_RNR 0x10 /* ack. STAT_RNR */ + +/* + * possible status values for the status word + */ +#define STAT_MASK 0xf0 /* mask for cause of interrupt */ +#define STAT_CX 0x80 /* CU finished cmd with its I bit set */ +#define STAT_FR 0x40 /* RU finished receiving a frame */ +#define STAT_CNA 0x20 /* CU left active state */ +#define STAT_RNR 0x10 /* RU left ready state */ + +#define CU_STATUS 0x7 /* CU status, 0=idle */ +#define CU_SUSPEND 0x1 /* CU is suspended */ +#define CU_ACTIVE 0x2 /* CU is active */ + +#define RU_STATUS 0x70 /* RU status, 0=idle */ +#define RU_SUSPEND 0x10 /* RU suspended */ +#define RU_NOSPACE 0x20 /* RU no resources */ +#define RU_READY 0x40 /* RU is ready */ + +/* + * Receive Frame Descriptor (RFD) + */ +struct rfd_struct +{ + unsigned char stat_low; /* status word */ + unsigned char stat_high; /* status word */ + unsigned char rfd_sf; /* 82596 mode only */ + unsigned char last; /* Bit15,Last Frame on List / Bit14,suspend */ + unsigned short next; /* linkoffset to next RFD */ + unsigned short rbd_offset; /* pointeroffset to RBD-buffer */ + unsigned char dest[6]; /* ethernet-address, destination */ + unsigned char source[6]; /* ethernet-address, source */ + unsigned short length; /* 802.3 frame-length */ + unsigned short zero_dummy; /* dummy */ +}; + +#define RFD_LAST 0x80 /* last: last rfd in the list */ +#define RFD_SUSP 0x40 /* last: suspend RU after */ +#define RFD_COMPL 0x80 +#define RFD_OK 0x20 +#define RFD_BUSY 0x40 +#define RFD_ERR_LEN 0x10 /* Length error (if enabled length-checking */ +#define RFD_ERR_CRC 0x08 /* CRC error */ +#define RFD_ERR_ALGN 0x04 /* Alignment error */ +#define RFD_ERR_RNR 0x02 /* status: receiver out of resources */ +#define RFD_ERR_OVR 0x01 /* DMA Overrun! */ + +#define RFD_ERR_FTS 0x0080 /* Frame to short */ +#define RFD_ERR_NEOP 0x0040 /* No EOP flag (for bitstuffing only) */ +#define RFD_ERR_TRUN 0x0020 /* (82596 only/SF mode) indicates truncated frame */ +#define RFD_MATCHADD 0x0002 /* status: Destinationaddress !matches IA (only 82596) */ +#define RFD_COLLDET 0x0001 /* Detected collision during reception */ + +/* + * Receive Buffer Descriptor (RBD) + */ +struct rbd_struct +{ + unsigned short status; /* status word,number of used bytes in buff */ + unsigned short next; /* pointeroffset to next RBD */ + char *buffer; /* receive buffer address pointer */ + unsigned short size; /* size of this buffer */ + unsigned short zero_dummy; /* dummy */ +}; + +#define RBD_LAST 0x8000 /* last buffer */ +#define RBD_USED 0x4000 /* this buffer has data */ +#define RBD_MASK 0x3fff /* size-mask for length */ + +/* + * Statusvalues for Commands/RFD + */ +#define STAT_COMPL 0x8000 /* status: frame/command is complete */ +#define STAT_BUSY 0x4000 /* status: frame/command is busy */ +#define STAT_OK 0x2000 /* status: frame/command is ok */ + +/* + * Action-Commands + */ +#define CMD_NOP 0x0000 /* NOP */ +#define CMD_IASETUP 0x0001 /* initial address setup command */ +#define CMD_CONFIGURE 0x0002 /* configure command */ +#define CMD_MCSETUP 0x0003 /* MC setup command */ +#define CMD_XMIT 0x0004 /* transmit command */ +#define CMD_TDR 0x0005 /* time domain reflectometer (TDR) command */ +#define CMD_DUMP 0x0006 /* dump command */ +#define CMD_DIAGNOSE 0x0007 /* diagnose command */ + +/* + * Action command bits + */ +#define CMD_LAST 0x8000 /* indicates last command in the CBL */ +#define CMD_SUSPEND 0x4000 /* suspend CU after this CB */ +#define CMD_INT 0x2000 /* generate interrupt after execution */ + +/* + * NOP - command + */ +struct nop_cmd_struct +{ + unsigned short cmd_status; /* status of this command */ + unsigned short cmd_cmd; /* the command itself (+bits) */ + unsigned short cmd_link; /* offsetpointer to next command */ +}; + +/* + * IA Setup command + */ +struct iasetup_cmd_struct +{ + unsigned short cmd_status; + unsigned short cmd_cmd; + unsigned short cmd_link; + unsigned char iaddr[6]; +}; + +/* + * Configure command + */ +struct configure_cmd_struct +{ + unsigned short cmd_status; + unsigned short cmd_cmd; + unsigned short cmd_link; + unsigned char byte_cnt; /* size of the config-cmd */ + unsigned char fifo; /* fifo/recv monitor */ + unsigned char sav_bf; /* save bad frames (bit7=1)*/ + unsigned char adr_len; /* adr_len(0-2),al_loc(3),pream(4-5),loopbak(6-7)*/ + unsigned char priority; /* lin_prio(0-2),exp_prio(4-6),bof_metd(7) */ + unsigned char ifs; /* inter frame spacing */ + unsigned char time_low; /* slot time low */ + unsigned char time_high; /* slot time high(0-2) and max. retries(4-7) */ + unsigned char promisc; /* promisc-mode(0) , et al (1-7) */ + unsigned char carr_coll; /* carrier(0-3)/collision(4-7) stuff */ + unsigned char fram_len; /* minimal frame len */ + unsigned char dummy; /* dummy */ +}; + +/* + * Multicast Setup command + */ +struct mcsetup_cmd_struct +{ + unsigned short cmd_status; + unsigned short cmd_cmd; + unsigned short cmd_link; + unsigned short mc_cnt; /* number of bytes in the MC-List */ + unsigned char mc_list[0][6]; /* pointer to 6 bytes entries */ +}; + +/* + * DUMP command + */ +struct dump_cmd_struct +{ + unsigned short cmd_status; + unsigned short cmd_cmd; + unsigned short cmd_link; + unsigned short dump_offset; /* pointeroffset to DUMP space */ +}; + +/* + * transmit command + */ +struct transmit_cmd_struct +{ + unsigned short cmd_status; + unsigned short cmd_cmd; + unsigned short cmd_link; + unsigned short tbd_offset; /* pointeroffset to TBD */ + unsigned char dest[6]; /* destination address of the frame */ + unsigned short length; /* user defined: 802.3 length / Ether type */ +}; + +#define TCMD_ERRMASK 0x0fa0 +#define TCMD_MAXCOLLMASK 0x000f +#define TCMD_MAXCOLL 0x0020 +#define TCMD_HEARTBEAT 0x0040 +#define TCMD_DEFERRED 0x0080 +#define TCMD_UNDERRUN 0x0100 +#define TCMD_LOSTCTS 0x0200 +#define TCMD_NOCARRIER 0x0400 +#define TCMD_LATECOLL 0x0800 + +struct tdr_cmd_struct +{ + unsigned short cmd_status; + unsigned short cmd_cmd; + unsigned short cmd_link; + unsigned short status; +}; + +#define TDR_LNK_OK 0x8000 /* No link problem identified */ +#define TDR_XCVR_PRB 0x4000 /* indicates a transceiver problem */ +#define TDR_ET_OPN 0x2000 /* open, no correct termination */ +#define TDR_ET_SRT 0x1000 /* TDR detected a short circuit */ +#define TDR_TIMEMASK 0x07ff /* mask for the time field */ + +/* + * Transmit Buffer Descriptor (TBD) + */ +struct tbd_struct +{ + unsigned short size; /* size + EOF-Flag(15) */ + unsigned short next; /* pointeroffset to next TBD */ + char *buffer; /* pointer to buffer */ +}; + +#define TBD_LAST 0x8000 /* EOF-Flag, indicates last buffer in list */ + + + + diff --git a/linux/src/drivers/net/ni65.c b/linux/src/drivers/net/ni65.c new file mode 100644 index 00000000..75e89147 --- /dev/null +++ b/linux/src/drivers/net/ni65.c @@ -0,0 +1,1228 @@ +/* + * ni6510 (am7990 'lance' chip) driver for Linux-net-3 + * BETAcode v0.71 (96/09/29) for 2.0.0 (or later) + * copyrights (c) 1994,1995,1996 by M.Hipp + * + * This driver can handle the old ni6510 board and the newer ni6510 + * EtherBlaster. (probably it also works with every full NE2100 + * compatible card) + * + * To compile as module, type: + * gcc -O2 -fomit-frame-pointer -m486 -D__KERNEL__ -DMODULE -c ni65.c + * driver probes: io: 0x360,0x300,0x320,0x340 / dma: 3,5,6,7 + * + * This is an extension to the Linux operating system, and is covered by the + * same Gnu Public License that covers the Linux-kernel. + * + * comments/bugs/suggestions can be sent to: + * Michael Hipp + * email: Michael.Hipp@student.uni-tuebingen.de + * + * sources: + * some things are from the 'ni6510-packet-driver for dos by Russ Nelson' + * and from the original drivers by D.Becker + * + * known problems: + * - on some PCI boards (including my own) the card/board/ISA-bridge has + * problems with bus master DMA. This results in lotsa overruns. + * It may help to '#define RCV_PARANOIA_CHECK' or try to #undef + * the XMT and RCV_VIA_SKB option .. this reduces driver performance. + * Or just play with your BIOS options to optimize ISA-DMA access. + * Maybe you also wanna play with the LOW_PERFORAMCE and MID_PERFORMANCE + * defines -> please report me your experience then + * - Harald reported for ASUS SP3G mainboards, that you should use + * the 'optimal settings' from the user's manual on page 3-12! + * + * credits: + * thanx to Jason Sullivan for sending me a ni6510 card! + * lot of debug runs with ASUS SP3G Boards (Intel Saturn) by Harald Koenig + * + * simple performance test: (486DX-33/Ni6510-EB receives from 486DX4-100/Ni6510-EB) + * average: FTP -> 8384421 bytes received in 8.5 seconds + * (no RCV_VIA_SKB,no XMT_VIA_SKB,PARANOIA_CHECK,4 XMIT BUFS, 8 RCV_BUFFS) + * peak: FTP -> 8384421 bytes received in 7.5 seconds + * (RCV_VIA_SKB,XMT_VIA_SKB,no PARANOIA_CHECK,1(!) XMIT BUF, 16 RCV BUFFS) + */ + +/* + * 96.Sept.29: virt_to_bus stuff added for new memory modell + * 96.April.29: Added Harald Koenig's Patches (MH) + * 96.April.13: enhanced error handling .. more tests (MH) + * 96.April.5/6: a lot of performance tests. Got it stable now (hopefully) (MH) + * 96.April.1: (no joke ;) .. added EtherBlaster and Module support (MH) + * 96.Feb.19: fixed a few bugs .. cleanups .. tested for 1.3.66 (MH) + * hopefully no more 16MB limit + * + * 95.Nov.18: multicast tweaked (AC). + * + * 94.Aug.22: changes in xmit_intr (ack more than one xmitted-packet), ni65_send_packet (p->lock) (MH) + * + * 94.July.16: fixed bugs in recv_skb and skb-alloc stuff (MH) + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <linux/version.h> +#include <linux/module.h> + +#include "ni65.h" + +/* + * the current setting allows an acceptable performance + * for 'RCV_PARANOIA_CHECK' read the 'known problems' part in + * the header of this file + * 'invert' the defines for max. performance. This may cause DMA problems + * on some boards (e.g on my ASUS SP3G) + */ +#undef XMT_VIA_SKB +#undef RCV_VIA_SKB +#define RCV_PARANOIA_CHECK + +#define MID_PERFORMANCE + +#if defined( LOW_PERFORMANCE ) + static int isa0=7,isa1=7,csr80=0x0c10; +#elif defined( MID_PERFORMANCE ) + static int isa0=5,isa1=5,csr80=0x2810; +#else /* high performance */ + static int isa0=4,isa1=4,csr80=0x0017; +#endif + +/* + * a few card/vendor specific defines + */ +#define NI65_ID0 0x00 +#define NI65_ID1 0x55 +#define NI65_EB_ID0 0x52 +#define NI65_EB_ID1 0x44 +#define NE2100_ID0 0x57 +#define NE2100_ID1 0x57 + +#define PORT p->cmdr_addr + +/* + * buffer configuration + */ +#if 1 +#define RMDNUM 16 +#define RMDNUMMASK 0x80000000 +#else +#define RMDNUM 8 +#define RMDNUMMASK 0x60000000 /* log2(RMDNUM)<<29 */ +#endif + +#if 0 +#define TMDNUM 1 +#define TMDNUMMASK 0x00000000 +#else +#define TMDNUM 4 +#define TMDNUMMASK 0x40000000 /* log2(TMDNUM)<<29 */ +#endif + +/* slightly oversized */ +#define R_BUF_SIZE 1544 +#define T_BUF_SIZE 1544 + +/* + * lance register defines + */ +#define L_DATAREG 0x00 +#define L_ADDRREG 0x02 +#define L_RESET 0x04 +#define L_CONFIG 0x05 +#define L_BUSIF 0x06 + +/* + * to access the lance/am7990-regs, you have to write + * reg-number into L_ADDRREG, then you can access it using L_DATAREG + */ +#define CSR0 0x00 +#define CSR1 0x01 +#define CSR2 0x02 +#define CSR3 0x03 + +#define INIT_RING_BEFORE_START 0x1 +#define FULL_RESET_ON_ERROR 0x2 + +#if 0 +#define writereg(val,reg) {outw(reg,PORT+L_ADDRREG);inw(PORT+L_ADDRREG); \ + outw(val,PORT+L_DATAREG);inw(PORT+L_DATAREG);} +#define readreg(reg) (outw(reg,PORT+L_ADDRREG),inw(PORT+L_ADDRREG),\ + inw(PORT+L_DATAREG)) +#if 0 +#define writedatareg(val) {outw(val,PORT+L_DATAREG);inw(PORT+L_DATAREG);} +#else +#define writedatareg(val) { writereg(val,CSR0); } +#endif +#else +#define writereg(val,reg) {outw(reg,PORT+L_ADDRREG);outw(val,PORT+L_DATAREG);} +#define readreg(reg) (outw(reg,PORT+L_ADDRREG),inw(PORT+L_DATAREG)) +#define writedatareg(val) { writereg(val,CSR0); } +#endif + +static unsigned char ni_vendor[] = { 0x02,0x07,0x01 }; + +static struct card { + unsigned char id0,id1; + short id_offset; + short total_size; + short cmd_offset; + short addr_offset; + unsigned char *vendor_id; + char *cardname; + unsigned char config; +} cards[] = { + { NI65_ID0,NI65_ID1,0x0e,0x10,0x0,0x8,ni_vendor,"ni6510", 0x1 } , + { NI65_EB_ID0,NI65_EB_ID1,0x0e,0x18,0x10,0x0,ni_vendor,"ni6510 EtherBlaster", 0x2 } , + { NE2100_ID0,NE2100_ID1,0x0e,0x18,0x10,0x0,NULL,"generic NE2100", 0x0 } +}; +#define NUM_CARDS 3 + +struct priv +{ + struct rmd rmdhead[RMDNUM]; + struct tmd tmdhead[TMDNUM]; + struct init_block ib; + int rmdnum; + int tmdnum,tmdlast; +#ifdef RCV_VIA_SKB + struct sk_buff *recv_skb[RMDNUM]; +#else + void *recvbounce[RMDNUM]; +#endif +#ifdef XMT_VIA_SKB + struct sk_buff *tmd_skb[TMDNUM]; +#endif + void *tmdbounce[TMDNUM]; + int tmdbouncenum; + int lock,xmit_queued; + struct enet_statistics stats; + void *self; + int cmdr_addr; + int cardno; + int features; +}; + +static int ni65_probe1(struct device *dev,int); +static void ni65_interrupt(int irq, void * dev_id, struct pt_regs *regs); +static void ni65_recv_intr(struct device *dev,int); +static void ni65_xmit_intr(struct device *dev,int); +static int ni65_open(struct device *dev); +static int ni65_lance_reinit(struct device *dev); +static void ni65_init_lance(struct priv *p,unsigned char*,int,int); +static int ni65_send_packet(struct sk_buff *skb, struct device *dev); +static int ni65_close(struct device *dev); +static int ni65_alloc_buffer(struct device *dev); +static void ni65_free_buffer(struct priv *p); +static struct enet_statistics *ni65_get_stats(struct device *); +static void set_multicast_list(struct device *dev); + +static int irqtab[] = { 9,12,15,5 }; /* irq config-translate */ +static int dmatab[] = { 0,3,5,6,7 }; /* dma config-translate and autodetect */ + +static int debuglevel = 1; + +/* + * set 'performance' registers .. we must STOP lance for that + */ +static void ni65_set_performance(struct priv *p) +{ + writereg(CSR0_STOP | CSR0_CLRALL,CSR0); /* STOP */ + + if( !(cards[p->cardno].config & 0x02) ) + return; + + outw(80,PORT+L_ADDRREG); + if(inw(PORT+L_ADDRREG) != 80) + return; + + writereg( (csr80 & 0x3fff) ,80); /* FIFO watermarks */ + outw(0,PORT+L_ADDRREG); + outw((short)isa0,PORT+L_BUSIF); /* write ISA 0: DMA_R : isa0 * 50ns */ + outw(1,PORT+L_ADDRREG); + outw((short)isa1,PORT+L_BUSIF); /* write ISA 1: DMA_W : isa1 * 50ns */ + + outw(CSR0,PORT+L_ADDRREG); /* switch back to CSR0 */ +} + +/* + * open interface (up) + */ +static int ni65_open(struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + int irqval = request_irq(dev->irq, &ni65_interrupt,0, + cards[p->cardno].cardname,NULL); + if (irqval) { + printk ("%s: unable to get IRQ %d (irqval=%d).\n", + dev->name,dev->irq, irqval); + return -EAGAIN; + } + irq2dev_map[dev->irq] = dev; + + if(ni65_lance_reinit(dev)) + { + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + MOD_INC_USE_COUNT; + return 0; + } + else + { + irq2dev_map[dev->irq] = NULL; + free_irq(dev->irq,NULL); + dev->start = 0; + return -EAGAIN; + } +} + +/* + * close interface (down) + */ +static int ni65_close(struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + + outw(inw(PORT+L_RESET),PORT+L_RESET); /* that's the hard way */ + +#ifdef XMT_VIA_SKB + { + int i; + for(i=0;i<TMDNUM;i++) + { + if(p->tmd_skb[i]) { + dev_kfree_skb(p->tmd_skb[i],FREE_WRITE); + p->tmd_skb[i] = NULL; + } + } + } +#endif + irq2dev_map[dev->irq] = NULL; + free_irq(dev->irq,NULL); + dev->tbusy = 1; + dev->start = 0; + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * Probe The Card (not the lance-chip) + */ +#ifdef MODULE +static +#endif +int ni65_probe(struct device *dev) +{ + int *port; + static int ports[] = {0x360,0x300,0x320,0x340, 0}; + + if (dev->base_addr > 0x1ff) /* Check a single specified location. */ + return ni65_probe1(dev, dev->base_addr); + else if (dev->base_addr > 0) /* Don't probe at all. */ + return -ENXIO; + + for (port = ports; *port; port++) + { + if (ni65_probe1(dev, *port) == 0) + return 0; + } + + return -ENODEV; +} + +/* + * this is the real card probe .. + */ +static int ni65_probe1(struct device *dev,int ioaddr) +{ + int i,j; + struct priv *p; + + for(i=0;i<NUM_CARDS;i++) { + if(check_region(ioaddr, cards[i].total_size)) + continue; + if(cards[i].id_offset >= 0) { + if(inb(ioaddr+cards[i].id_offset+0) != cards[i].id0 || + inb(ioaddr+cards[i].id_offset+1) != cards[i].id1) { + continue; + } + } + if(cards[i].vendor_id) { + for(j=0;j<3;j++) + if(inb(ioaddr+cards[i].addr_offset+j) != cards[i].vendor_id[j]) + continue; + } + break; + } + if(i == NUM_CARDS) + return -ENODEV; + + for(j=0;j<6;j++) + dev->dev_addr[j] = inb(ioaddr+cards[i].addr_offset+j); + + if( (j=ni65_alloc_buffer(dev)) < 0) + return j; + p = (struct priv *) dev->priv; + p->cmdr_addr = ioaddr + cards[i].cmd_offset; + p->cardno = i; + + printk("%s: %s found at %#3x, ", dev->name, cards[p->cardno].cardname , ioaddr); + + outw(inw(PORT+L_RESET),PORT+L_RESET); /* first: reset the card */ + if( (j=readreg(CSR0)) != 0x4) { + printk(KERN_ERR "can't RESET card: %04x\n",j); + ni65_free_buffer(p); + return -EAGAIN; + } + + outw(88,PORT+L_ADDRREG); + if(inw(PORT+L_ADDRREG) == 88) { + unsigned long v; + v = inw(PORT+L_DATAREG); + v <<= 16; + outw(89,PORT+L_ADDRREG); + v |= inw(PORT+L_DATAREG); + printk("Version %#08lx, ",v); + p->features = INIT_RING_BEFORE_START; + } + else { + printk("ancient LANCE, "); + p->features = 0x0; + } + + if(test_bit(0,&cards[i].config)) { + dev->irq = irqtab[(inw(ioaddr+L_CONFIG)>>2)&3]; + dev->dma = dmatab[inw(ioaddr+L_CONFIG)&3]; + printk("IRQ %d (from card), DMA %d (from card).\n",dev->irq,dev->dma); + } + else { + if(dev->dma == 0) { + /* 'stuck test' from lance.c */ + int dma_channels = ((inb(DMA1_STAT_REG) >> 4) & 0x0f) | (inb(DMA2_STAT_REG) & 0xf0); + for(i=1;i<5;i++) { + int dma = dmatab[i]; + if(test_bit(dma,&dma_channels) || request_dma(dma,"ni6510")) + continue; + disable_dma(dma); + set_dma_mode(dma,DMA_MODE_CASCADE); + enable_dma(dma); + ni65_init_lance(p,dev->dev_addr,0,0); /* trigger memory access */ + disable_dma(dma); + free_dma(dma); + if(readreg(CSR0) & CSR0_IDON) + break; + } + if(i == 5) { + printk("Can't detect DMA channel!\n"); + ni65_free_buffer(p); + return -EAGAIN; + } + dev->dma = dmatab[i]; + printk("DMA %d (autodetected), ",dev->dma); + } + else + printk("DMA %d (assigned), ",dev->dma); + + if(dev->irq < 2) + { + ni65_init_lance(p,dev->dev_addr,0,0); + autoirq_setup(0); + writereg(CSR0_INIT|CSR0_INEA,CSR0); /* trigger interrupt */ + + if(!(dev->irq = autoirq_report(2))) + { + printk("Failed to detect IRQ line!\n"); + ni65_free_buffer(p); + return -EAGAIN; + } + printk("IRQ %d (autodetected).\n",dev->irq); + } + else + printk("IRQ %d (assigned).\n",dev->irq); + } + + if(request_dma(dev->dma, cards[p->cardno].cardname ) != 0) + { + printk("%s: Can't request dma-channel %d\n",dev->name,(int) dev->dma); + ni65_free_buffer(p); + return -EAGAIN; + } + + /* + * Grab the region so we can find another board. + */ + request_region(ioaddr,cards[p->cardno].total_size,cards[p->cardno].cardname); + + dev->base_addr = ioaddr; + + dev->open = ni65_open; + dev->stop = ni65_close; + dev->hard_start_xmit = ni65_send_packet; + dev->get_stats = ni65_get_stats; + dev->set_multicast_list = set_multicast_list; + + ether_setup(dev); + + dev->interrupt = 0; + dev->tbusy = 0; + dev->start = 0; + + return 0; /* everything is OK */ +} + +/* + * set lance register and trigger init + */ +static void ni65_init_lance(struct priv *p,unsigned char *daddr,int filter,int mode) +{ + int i; + u32 pib; + + writereg(CSR0_CLRALL|CSR0_STOP,CSR0); + + for(i=0;i<6;i++) + p->ib.eaddr[i] = daddr[i]; + + for(i=0;i<8;i++) + p->ib.filter[i] = filter; + p->ib.mode = mode; + + p->ib.trp = (u32) virt_to_bus(p->tmdhead) | TMDNUMMASK; + p->ib.rrp = (u32) virt_to_bus(p->rmdhead) | RMDNUMMASK; + writereg(0,CSR3); /* busmaster/no word-swap */ + pib = (u32) virt_to_bus(&p->ib); + writereg(pib & 0xffff,CSR1); + writereg(pib >> 16,CSR2); + + writereg(CSR0_INIT,CSR0); /* this changes L_ADDRREG to CSR0 */ + + for(i=0;i<32;i++) + { + udelay(4000); + if(inw(PORT+L_DATAREG) & (CSR0_IDON | CSR0_MERR) ) + break; /* init ok ? */ + } +} + +/* + * allocate memory area and check the 16MB border + */ +static void *ni65_alloc_mem(struct device *dev,char *what,int size,int type) +{ + struct sk_buff *skb=NULL; + unsigned char *ptr; + void *ret; + + if(type) { + ret = skb = alloc_skb(2+16+size,GFP_KERNEL|GFP_DMA); + if(!skb) { + printk("%s: unable to allocate %s memory.\n",dev->name,what); + return NULL; + } + skb->dev = dev; + skb_reserve(skb,2+16); + skb_put(skb,R_BUF_SIZE); /* grab the whole space .. (not necessary) */ + ptr = skb->data; + } + else { + ret = ptr = kmalloc(T_BUF_SIZE,GFP_KERNEL | GFP_DMA); + if(!ret) { + printk("%s: unable to allocate %s memory.\n",dev->name,what); + return NULL; + } + } + if( (u32) virt_to_bus(ptr+size) > 0x1000000) { + printk("%s: unable to allocate %s memory in lower 16MB!\n",dev->name,what); + if(type) + kfree_skb(skb,FREE_WRITE); + else + kfree(ptr); + return NULL; + } + return ret; +} + +/* + * allocate all memory structures .. send/recv buffers etc ... + */ +static int ni65_alloc_buffer(struct device *dev) +{ + unsigned char *ptr; + struct priv *p; + int i; + + /* + * we need 8-aligned memory .. + */ + ptr = ni65_alloc_mem(dev,"BUFFER",sizeof(struct priv)+8,0); + if(!ptr) + return -ENOMEM; + + p = dev->priv = (struct priv *) (((unsigned long) ptr + 7) & ~0x7); + memset((char *) dev->priv,0,sizeof(struct priv)); + p->self = ptr; + + for(i=0;i<TMDNUM;i++) + { +#ifdef XMT_VIA_SKB + p->tmd_skb[i] = NULL; +#endif + p->tmdbounce[i] = ni65_alloc_mem(dev,"XMIT",T_BUF_SIZE,0); + if(!p->tmdbounce[i]) { + ni65_free_buffer(p); + return -ENOMEM; + } + } + + for(i=0;i<RMDNUM;i++) + { +#ifdef RCV_VIA_SKB + p->recv_skb[i] = ni65_alloc_mem(dev,"RECV",R_BUF_SIZE,1); + if(!p->recv_skb[i]) { + ni65_free_buffer(p); + return -ENOMEM; + } +#else + p->recvbounce[i] = ni65_alloc_mem(dev,"RECV",R_BUF_SIZE,0); + if(!p->recvbounce[i]) { + ni65_free_buffer(p); + return -ENOMEM; + } +#endif + } + + return 0; /* everything is OK */ +} + +/* + * free buffers and private struct + */ +static void ni65_free_buffer(struct priv *p) +{ + int i; + + if(!p) + return; + + for(i=0;i<TMDNUM;i++) { + if(p->tmdbounce[i]) + kfree(p->tmdbounce[i]); +#ifdef XMT_VIA_SKB + if(p->tmd_skb[i]) + dev_kfree_skb(p->tmd_skb[i],FREE_WRITE); +#endif + } + + for(i=0;i<RMDNUM;i++) + { +#ifdef RCV_VIA_SKB + if(p->recv_skb[i]) + dev_kfree_skb(p->recv_skb[i],FREE_WRITE); +#else + if(p->recvbounce[i]) + kfree(p->recvbounce[i]); +#endif + } + if(p->self) + kfree(p->self); +} + + +/* + * stop and (re)start lance .. e.g after an error + */ +static void ni65_stop_start(struct device *dev,struct priv *p) +{ + int csr0 = CSR0_INEA; + + writedatareg(CSR0_STOP); + + if(debuglevel > 1) + printk("ni65_stop_start\n"); + + if(p->features & INIT_RING_BEFORE_START) { + int i; +#ifdef XMT_VIA_SKB + struct sk_buff *skb_save[TMDNUM]; +#endif + unsigned long buffer[TMDNUM]; + short blen[TMDNUM]; + + if(p->xmit_queued) { + while(1) { + if((p->tmdhead[p->tmdlast].u.s.status & XMIT_OWN)) + break; + p->tmdlast = (p->tmdlast + 1) & (TMDNUM-1); + if(p->tmdlast == p->tmdnum) + break; + } + } + + for(i=0;i<TMDNUM;i++) { + struct tmd *tmdp = p->tmdhead + i; +#ifdef XMT_VIA_SKB + skb_save[i] = p->tmd_skb[i]; +#endif + buffer[i] = (u32) bus_to_virt(tmdp->u.buffer); + blen[i] = tmdp->blen; + tmdp->u.s.status = 0x0; + } + + for(i=0;i<RMDNUM;i++) { + struct rmd *rmdp = p->rmdhead + i; + rmdp->u.s.status = RCV_OWN; + } + p->tmdnum = p->xmit_queued = 0; + writedatareg(CSR0_STRT | csr0); + + for(i=0;i<TMDNUM;i++) { + int num = (i + p->tmdlast) & (TMDNUM-1); + p->tmdhead[i].u.buffer = (u32) virt_to_bus((char *)buffer[num]); /* status is part of buffer field */ + p->tmdhead[i].blen = blen[num]; + if(p->tmdhead[i].u.s.status & XMIT_OWN) { + p->tmdnum = (p->tmdnum + 1) & (TMDNUM-1); + p->xmit_queued = 1; + writedatareg(CSR0_TDMD | CSR0_INEA | csr0); + } +#ifdef XMT_VIA_SKB + p->tmd_skb[i] = skb_save[num]; +#endif + } + p->rmdnum = p->tmdlast = 0; + if(!p->lock) + dev->tbusy = (p->tmdnum || !p->xmit_queued) ? 0 : 1; + dev->trans_start = jiffies; + } + else + writedatareg(CSR0_STRT | csr0); +} + +/* + * init lance (write init-values .. init-buffers) (open-helper) + */ +static int ni65_lance_reinit(struct device *dev) +{ + int i; + struct priv *p = (struct priv *) dev->priv; + + p->lock = 0; + p->xmit_queued = 0; + + disable_dma(dev->dma); /* I've never worked with dma, but we do it like the packetdriver */ + set_dma_mode(dev->dma,DMA_MODE_CASCADE); + enable_dma(dev->dma); + + outw(inw(PORT+L_RESET),PORT+L_RESET); /* first: reset the card */ + if( (i=readreg(CSR0) ) != 0x4) + { + printk(KERN_ERR "%s: can't RESET %s card: %04x\n",dev->name, + cards[p->cardno].cardname,(int) i); + disable_dma(dev->dma); + return 0; + } + + p->rmdnum = p->tmdnum = p->tmdlast = p->tmdbouncenum = 0; + for(i=0;i<TMDNUM;i++) + { + struct tmd *tmdp = p->tmdhead + i; +#ifdef XMT_VIA_SKB + if(p->tmd_skb[i]) { + dev_kfree_skb(p->tmd_skb[i],FREE_WRITE); + p->tmd_skb[i] = NULL; + } +#endif + tmdp->u.buffer = 0x0; + tmdp->u.s.status = XMIT_START | XMIT_END; + tmdp->blen = tmdp->status2 = 0; + } + + for(i=0;i<RMDNUM;i++) + { + struct rmd *rmdp = p->rmdhead + i; +#ifdef RCV_VIA_SKB + rmdp->u.buffer = (u32) virt_to_bus(p->recv_skb[i]->data); +#else + rmdp->u.buffer = (u32) virt_to_bus(p->recvbounce[i]); +#endif + rmdp->blen = -(R_BUF_SIZE-8); + rmdp->mlen = 0; + rmdp->u.s.status = RCV_OWN; + } + + if(dev->flags & IFF_PROMISC) + ni65_init_lance(p,dev->dev_addr,0x00,M_PROM); + else if(dev->mc_count || dev->flags & IFF_ALLMULTI) + ni65_init_lance(p,dev->dev_addr,0xff,0x0); + else + ni65_init_lance(p,dev->dev_addr,0x00,0x00); + + /* + * ni65_set_lance_mem() sets L_ADDRREG to CSR0 + * NOW, WE WILL NEVER CHANGE THE L_ADDRREG, CSR0 IS ALWAYS SELECTED + */ + + if(inw(PORT+L_DATAREG) & CSR0_IDON) { + ni65_set_performance(p); + /* init OK: start lance , enable interrupts */ + writedatareg(CSR0_CLRALL | CSR0_INEA | CSR0_STRT); + return 1; /* ->OK */ + } + printk(KERN_ERR "%s: can't init lance, status: %04x\n",dev->name,(int) inw(PORT+L_DATAREG)); + disable_dma(dev->dma); + return 0; /* ->Error */ +} + +/* + * interrupt handler + */ +static void ni65_interrupt(int irq, void * dev_id, struct pt_regs * regs) +{ + int csr0; + struct device *dev = (struct device *) irq2dev_map[irq]; + struct priv *p; + int bcnt = 32; + + if (dev == NULL) { + printk (KERN_ERR "ni65_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + if(set_bit(0,(int *) &dev->interrupt)) { + printk("ni65: oops .. interrupt while proceeding interrupt\n"); + return; + } + p = (struct priv *) dev->priv; + + while(--bcnt) { + csr0 = inw(PORT+L_DATAREG); + +#if 0 + writedatareg( (csr0 & CSR0_CLRALL) ); /* ack interrupts, disable int. */ +#else + writedatareg( (csr0 & CSR0_CLRALL) | CSR0_INEA ); /* ack interrupts, interrupts enabled */ +#endif + + if(!(csr0 & (CSR0_ERR | CSR0_RINT | CSR0_TINT))) + break; + + if(csr0 & CSR0_RINT) /* RECV-int? */ + ni65_recv_intr(dev,csr0); + if(csr0 & CSR0_TINT) /* XMIT-int? */ + ni65_xmit_intr(dev,csr0); + + if(csr0 & CSR0_ERR) + { + struct priv *p = (struct priv *) dev->priv; + if(debuglevel > 1) + printk("%s: general error: %04x.\n",dev->name,csr0); + if(csr0 & CSR0_BABL) + p->stats.tx_errors++; + if(csr0 & CSR0_MISS) { + int i; + for(i=0;i<RMDNUM;i++) + printk("%02x ",p->rmdhead[i].u.s.status); + printk("\n"); + p->stats.rx_errors++; + } + if(csr0 & CSR0_MERR) { + if(debuglevel > 1) + printk("%s: Ooops .. memory error: %04x.\n",dev->name,csr0); + ni65_stop_start(dev,p); + } + } + } + +#ifdef RCV_PARANOIA_CHECK +{ + int j; + for(j=0;j<RMDNUM;j++) + { + struct priv *p = (struct priv *) dev->priv; + int i,k,num1,num2; + for(i=RMDNUM-1;i>0;i--) { + num2 = (p->rmdnum + i) & (RMDNUM-1); + if(!(p->rmdhead[num2].u.s.status & RCV_OWN)) + break; + } + + if(i) { + for(k=0;k<RMDNUM;k++) { + num1 = (p->rmdnum + k) & (RMDNUM-1); + if(!(p->rmdhead[num1].u.s.status & RCV_OWN)) + break; + } + if(!k) + break; + + if(debuglevel > 0) + { + char buf[256],*buf1; + int k; + buf1 = buf; + for(k=0;k<RMDNUM;k++) { + sprintf(buf1,"%02x ",(p->rmdhead[k].u.s.status)); /* & RCV_OWN) ); */ + buf1 += 3; + } + *buf1 = 0; + printk(KERN_ERR "%s: Ooops, receive ring corrupted %2d %2d | %s\n",dev->name,p->rmdnum,i,buf); + } + + p->rmdnum = num1; + ni65_recv_intr(dev,csr0); + if((p->rmdhead[num2].u.s.status & RCV_OWN)) + break; /* ok, we are 'in sync' again */ + } + else + break; + } +} +#endif + + if( (csr0 & (CSR0_RXON | CSR0_TXON)) != (CSR0_RXON | CSR0_TXON) ) { + printk("%s: RX or TX was offline -> restart\n",dev->name); + ni65_stop_start(dev,p); + } + else + writedatareg(CSR0_INEA); + + dev->interrupt = 0; + + return; +} + +/* + * We have received an Xmit-Interrupt .. + * send a new packet if necessary + */ +static void ni65_xmit_intr(struct device *dev,int csr0) +{ + struct priv *p = (struct priv *) dev->priv; + + while(p->xmit_queued) + { + struct tmd *tmdp = p->tmdhead + p->tmdlast; + int tmdstat = tmdp->u.s.status; + + if(tmdstat & XMIT_OWN) + break; + + if(tmdstat & XMIT_ERR) + { +#if 0 + if(tmdp->status2 & XMIT_TDRMASK && debuglevel > 3) + printk(KERN_ERR "%s: tdr-problems (e.g. no resistor)\n",dev->name); +#endif + /* checking some errors */ + if(tmdp->status2 & XMIT_RTRY) + p->stats.tx_aborted_errors++; + if(tmdp->status2 & XMIT_LCAR) + p->stats.tx_carrier_errors++; + if(tmdp->status2 & (XMIT_BUFF | XMIT_UFLO )) { + /* this stops the xmitter */ + p->stats.tx_fifo_errors++; + if(debuglevel > 0) + printk(KERN_ERR "%s: Xmit FIFO/BUFF error\n",dev->name); + if(p->features & INIT_RING_BEFORE_START) { + tmdp->u.s.status = XMIT_OWN | XMIT_START | XMIT_END; /* test: resend this frame */ + ni65_stop_start(dev,p); + break; /* no more Xmit processing .. */ + } + else + ni65_stop_start(dev,p); + } + if(debuglevel > 2) + printk(KERN_ERR "%s: xmit-error: %04x %02x-%04x\n",dev->name,csr0,(int) tmdstat,(int) tmdp->status2); + if(!(csr0 & CSR0_BABL)) /* don't count errors twice */ + p->stats.tx_errors++; + tmdp->status2 = 0; + } + else + p->stats.tx_packets++; + +#ifdef XMT_VIA_SKB + if(p->tmd_skb[p->tmdlast]) { + dev_kfree_skb(p->tmd_skb[p->tmdlast],FREE_WRITE); + p->tmd_skb[p->tmdlast] = NULL; + } +#endif + + p->tmdlast = (p->tmdlast + 1) & (TMDNUM-1); + if(p->tmdlast == p->tmdnum) + p->xmit_queued = 0; + } + dev->tbusy = 0; + mark_bh(NET_BH); +} + +/* + * We have received a packet + */ +static void ni65_recv_intr(struct device *dev,int csr0) +{ + struct rmd *rmdp; + int rmdstat,len; + int cnt=0; + struct priv *p = (struct priv *) dev->priv; + + rmdp = p->rmdhead + p->rmdnum; + while(!( (rmdstat = rmdp->u.s.status) & RCV_OWN)) + { + cnt++; + if( (rmdstat & (RCV_START | RCV_END | RCV_ERR)) != (RCV_START | RCV_END) ) /* error or oversized? */ + { + if(!(rmdstat & RCV_ERR)) { + if(rmdstat & RCV_START) + { + p->stats.rx_length_errors++; + printk(KERN_ERR "%s: recv, packet too long: %d\n",dev->name,rmdp->mlen & 0x0fff); + } + } + else { + if(debuglevel > 2) + printk(KERN_ERR "%s: receive-error: %04x, lance-status: %04x/%04x\n", + dev->name,(int) rmdstat,csr0,(int) inw(PORT+L_DATAREG) ); + if(rmdstat & RCV_FRAM) + p->stats.rx_frame_errors++; + if(rmdstat & RCV_OFLO) + p->stats.rx_over_errors++; + if(rmdstat & RCV_CRC) + p->stats.rx_crc_errors++; + if(rmdstat & RCV_BUF_ERR) + p->stats.rx_fifo_errors++; + } + if(!(csr0 & CSR0_MISS)) /* don't count errors twice */ + p->stats.rx_errors++; + } + else if( (len = (rmdp->mlen & 0x0fff) - 4) >= 60) + { +#ifdef RCV_VIA_SKB + struct sk_buff *skb = alloc_skb(R_BUF_SIZE+2+16,GFP_ATOMIC); + if (skb) + skb_reserve(skb,16); +#else + struct sk_buff *skb = dev_alloc_skb(len+2); +#endif + if(skb) + { + skb_reserve(skb,2); + skb->dev = dev; +#ifdef RCV_VIA_SKB + if( (unsigned long) (skb->data + R_BUF_SIZE) > 0x1000000) { + skb_put(skb,len); + eth_copy_and_sum(skb, (unsigned char *)(p->recv_skb[p->rmdnum]->data),len,0); + } + else { + struct sk_buff *skb1 = p->recv_skb[p->rmdnum]; + skb_put(skb,R_BUF_SIZE); + p->recv_skb[p->rmdnum] = skb; + rmdp->u.buffer = (u32) virt_to_bus(skb->data); + skb = skb1; + skb_trim(skb,len); + } +#else + skb_put(skb,len); + eth_copy_and_sum(skb, (unsigned char *) p->recvbounce[p->rmdnum],len,0); +#endif + p->stats.rx_packets++; + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + } + else + { + printk(KERN_ERR "%s: can't alloc new sk_buff\n",dev->name); + p->stats.rx_dropped++; + } + } + else { + printk(KERN_INFO "%s: received runt packet\n",dev->name); + p->stats.rx_errors++; + } + rmdp->blen = -(R_BUF_SIZE-8); + rmdp->mlen = 0; + rmdp->u.s.status = RCV_OWN; /* change owner */ + p->rmdnum = (p->rmdnum + 1) & (RMDNUM-1); + rmdp = p->rmdhead + p->rmdnum; + } +} + +/* + * kick xmitter .. + */ +static int ni65_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + + if(dev->tbusy) + { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 50) + return 1; + + printk(KERN_ERR "%s: xmitter timed out, try to restart!\n",dev->name); +{ + int i; + for(i=0;i<TMDNUM;i++) + printk("%02x ",p->tmdhead[i].u.s.status); + printk("\n"); +} + ni65_lance_reinit(dev); + dev->tbusy=0; + dev->trans_start = jiffies; + } + + if(skb == NULL) { + dev_tint(dev); + return 0; + } + + if (skb->len <= 0) + return 0; + + if (set_bit(0, (void*)&dev->tbusy) != 0) { + printk(KERN_ERR "%s: Transmitter access conflict.\n", dev->name); + return 1; + } + if (set_bit(0, (void*)&p->lock)) { + printk(KERN_ERR "%s: Queue was locked.\n", dev->name); + return 1; + } + + { + short len = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + struct tmd *tmdp; + long flags; + +#ifdef XMT_VIA_SKB + if( (unsigned long) (skb->data + skb->len) > 0x1000000) { +#endif + + memcpy((char *) p->tmdbounce[p->tmdbouncenum] ,(char *)skb->data, + (skb->len > T_BUF_SIZE) ? T_BUF_SIZE : skb->len); + dev_kfree_skb (skb, FREE_WRITE); + + save_flags(flags); + cli(); + + tmdp = p->tmdhead + p->tmdnum; + tmdp->u.buffer = (u32) virt_to_bus(p->tmdbounce[p->tmdbouncenum]); + p->tmdbouncenum = (p->tmdbouncenum + 1) & (TMDNUM - 1); + +#ifdef XMT_VIA_SKB + } + else { + save_flags(flags); + cli(); + + tmdp = p->tmdhead + p->tmdnum; + tmdp->u.buffer = (u32) virt_to_bus(skb->data); + p->tmd_skb[p->tmdnum] = skb; + } +#endif + tmdp->blen = -len; + + tmdp->u.s.status = XMIT_OWN | XMIT_START | XMIT_END; + writedatareg(CSR0_TDMD | CSR0_INEA); /* enable xmit & interrupt */ + + p->xmit_queued = 1; + p->tmdnum = (p->tmdnum + 1) & (TMDNUM-1); + + dev->tbusy = (p->tmdnum == p->tmdlast) ? 1 : 0; + p->lock = 0; + dev->trans_start = jiffies; + + restore_flags(flags); + } + + return 0; +} + +static struct enet_statistics *ni65_get_stats(struct device *dev) +{ + +#if 0 + int i; + struct priv *p = (struct priv *) dev->priv; + for(i=0;i<RMDNUM;i++) { + struct rmd *rmdp = p->rmdhead + ((p->rmdnum + i) & (RMDNUM-1)); + printk("%02x ",rmdp->u.s.status); + } + printk("\n"); +#endif + + return &((struct priv *) dev->priv)->stats; +} + +static void set_multicast_list(struct device *dev) +{ + if(!ni65_lance_reinit(dev)) + printk(KERN_ERR "%s: Can't switch card into MC mode!\n",dev->name); + dev->tbusy = 0; +} + +#ifdef MODULE +static struct device dev_ni65 = { + " ", /* "ni6510": device name inserted by net_init.c */ + 0, 0, 0, 0, + 0x360, 9, /* I/O address, IRQ */ + 0, 0, 0, NULL, ni65_probe }; + +/* set: io,irq,dma or set it when calling insmod */ +static int irq=0; +static int io=0; +static int dma=0; + +int init_module(void) +{ +#if 0 + if(io <= 0x0 || irq < 2) { + printk("ni65: Autoprobing not allowed for modules.\n"); + printk("ni65: Set symbols 'io' 'irq' and 'dma'\n"); + return -ENODEV; + } +#endif + dev_ni65.irq = irq; + dev_ni65.dma = dma; + dev_ni65.base_addr = io; + if (register_netdev(&dev_ni65) != 0) + return -EIO; + return 0; +} + +void cleanup_module(void) +{ + struct priv *p; + p = (struct priv *) dev_ni65.priv; + if(!p) { + printk("Ooops .. no privat struct\n"); + return; + } + disable_dma(dev_ni65.dma); + free_dma(dev_ni65.dma); + release_region(dev_ni65.base_addr,cards[p->cardno].total_size); + ni65_free_buffer(p); + dev_ni65.priv = NULL; + unregister_netdev(&dev_ni65); +} +#endif /* MODULE */ + +/* + * END of ni65.c + */ + + diff --git a/linux/src/drivers/net/ni65.h b/linux/src/drivers/net/ni65.h new file mode 100644 index 00000000..64380951 --- /dev/null +++ b/linux/src/drivers/net/ni65.h @@ -0,0 +1,130 @@ +/* am7990 (lance) definitions + * + * This is an extension to the Linux operating system, and is covered by + * same Gnu Public License that covers that work. + * + * Michael Hipp + * email: mhipp@student.uni-tuebingen.de + * + * sources: (mail me or ask archie if you need them) + * crynwr-packet-driver + */ + +/* + * Control and Status Register 0 (CSR0) bit definitions + * (R=Readable) (W=Writeable) (S=Set on write) (C-Clear on write) + * + */ + +#define CSR0_ERR 0x8000 /* Error summary (R) */ +#define CSR0_BABL 0x4000 /* Babble transmitter timeout error (RC) */ +#define CSR0_CERR 0x2000 /* Collision Error (RC) */ +#define CSR0_MISS 0x1000 /* Missed packet (RC) */ +#define CSR0_MERR 0x0800 /* Memory Error (RC) */ +#define CSR0_RINT 0x0400 /* Receiver Interrupt (RC) */ +#define CSR0_TINT 0x0200 /* Transmit Interrupt (RC) */ +#define CSR0_IDON 0x0100 /* Initialization Done (RC) */ +#define CSR0_INTR 0x0080 /* Interrupt Flag (R) */ +#define CSR0_INEA 0x0040 /* Interrupt Enable (RW) */ +#define CSR0_RXON 0x0020 /* Receiver on (R) */ +#define CSR0_TXON 0x0010 /* Transmitter on (R) */ +#define CSR0_TDMD 0x0008 /* Transmit Demand (RS) */ +#define CSR0_STOP 0x0004 /* Stop (RS) */ +#define CSR0_STRT 0x0002 /* Start (RS) */ +#define CSR0_INIT 0x0001 /* Initialize (RS) */ + +#define CSR0_CLRALL 0x7f00 /* mask for all clearable bits */ +/* + * Initialization Block Mode operation Bit Definitions. + */ + +#define M_PROM 0x8000 /* Promiscuous Mode */ +#define M_INTL 0x0040 /* Internal Loopback */ +#define M_DRTY 0x0020 /* Disable Retry */ +#define M_COLL 0x0010 /* Force Collision */ +#define M_DTCR 0x0008 /* Disable Transmit CRC) */ +#define M_LOOP 0x0004 /* Loopback */ +#define M_DTX 0x0002 /* Disable the Transmitter */ +#define M_DRX 0x0001 /* Disable the Receiver */ + + +/* + * Receive message descriptor bit definitions. + */ + +#define RCV_OWN 0x80 /* owner bit 0 = host, 1 = lance */ +#define RCV_ERR 0x40 /* Error Summary */ +#define RCV_FRAM 0x20 /* Framing Error */ +#define RCV_OFLO 0x10 /* Overflow Error */ +#define RCV_CRC 0x08 /* CRC Error */ +#define RCV_BUF_ERR 0x04 /* Buffer Error */ +#define RCV_START 0x02 /* Start of Packet */ +#define RCV_END 0x01 /* End of Packet */ + + +/* + * Transmit message descriptor bit definitions. + */ + +#define XMIT_OWN 0x80 /* owner bit 0 = host, 1 = lance */ +#define XMIT_ERR 0x40 /* Error Summary */ +#define XMIT_RETRY 0x10 /* more the 1 retry needed to Xmit */ +#define XMIT_1_RETRY 0x08 /* one retry needed to Xmit */ +#define XMIT_DEF 0x04 /* Deferred */ +#define XMIT_START 0x02 /* Start of Packet */ +#define XMIT_END 0x01 /* End of Packet */ + +/* + * transmit status (2) (valid if XMIT_ERR == 1) + */ + +#define XMIT_TDRMASK 0x03ff /* time-domain-reflectometer-value */ +#define XMIT_RTRY 0x0400 /* Failed after 16 retransmissions */ +#define XMIT_LCAR 0x0800 /* Loss of Carrier */ +#define XMIT_LCOL 0x1000 /* Late collision */ +#define XMIT_RESERV 0x2000 /* Reserved */ +#define XMIT_UFLO 0x4000 /* Underflow (late memory) */ +#define XMIT_BUFF 0x8000 /* Buffering error (no ENP) */ + +struct init_block +{ + unsigned short mode; + unsigned char eaddr[6]; + unsigned char filter[8]; + /* bit 29-31: number of rmd's (power of 2) */ + u32 rrp; /* receive ring pointer (align 8) */ + /* bit 29-31: number of tmd's (power of 2) */ + u32 trp; /* transmit ring pointer (align 8) */ +}; + +struct rmd /* Receive Message Descriptor */ +{ + union + { + volatile u32 buffer; + struct + { + volatile unsigned char dummy[3]; + volatile unsigned char status; + } s; + } u; + volatile short blen; + volatile unsigned short mlen; +}; + +struct tmd +{ + union + { + volatile u32 buffer; + struct + { + volatile unsigned char dummy[3]; + volatile unsigned char status; + } s; + } u; + volatile unsigned short blen; + volatile unsigned short status2; +}; + + diff --git a/linux/src/drivers/net/pcnet32.c b/linux/src/drivers/net/pcnet32.c new file mode 100644 index 00000000..acc5ce74 --- /dev/null +++ b/linux/src/drivers/net/pcnet32.c @@ -0,0 +1,970 @@ +/* pcnet32.c: An AMD PCnet32 ethernet driver for linux. */ +/* + * Copyright 1996,97 Thomas Bogendoerfer, 1993-1995,1998 Donald Becker + * Copyright 1993 United States Government as represented by the + * Director, National Security Agency. + * + * Derived from the lance driver written 1993-1995 by Donald Becker. + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * + * This driver is for AMD PCnet-PCI based ethercards + */ + +static const char *version = "pcnet32.c:v0.99B 4/4/98 DJBecker/TSBogend.\n"; + +/* A few user-configurable values. */ + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 20; + +/* + * Set the number of Tx and Rx buffers, using Log_2(# buffers). + * Reasonable default values are 4 Tx buffers, and 16 Rx buffers. + * That translates to 2 (4 == 2^^2) and 4 (16 == 2^^4). + */ +#define PCNET_LOG_TX_BUFFERS 4 +#define PCNET_LOG_RX_BUFFERS 4 + +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* Driver verbosity level. 0 = no messages, 7 = wordy death. + Modify here, or when loading as a module. */ +static int pcnet32_debug = 1; + +/* + * Theory of Operation + * + * This driver uses the same software structure as the normal lance + * driver. So look for a verbose description in lance.c. The differences + * to the normal lance driver is the use of the 32bit mode of PCnet32 + * and PCnetPCI chips. Because these chips are 32bit chips, there is no + * 16MB limitation and we don't need bounce buffers. + */ + +/* + * History: + * v0.01: Initial version + * only tested on Alpha Noname Board + * v0.02: changed IRQ handling for new interrupt scheme (dev_id) + * tested on a ASUS SP3G + * v0.10: fixed an odd problem with the 79C794 in a Compaq Deskpro XL + * looks like the 974 doesn't like stopping and restarting in a + * short period of time; now we do a reinit of the lance; the + * bug was triggered by doing ifconfig eth0 <ip> broadcast <addr> + * and hangs the machine (thanks to Klaus Liedl for debugging) + * v0.12: by suggestion from Donald Becker: Renamed driver to pcnet32, + * made it standalone (no need for lance.c) + * v0.13: added additional PCI detecting for special PCI devices (Compaq) + * v0.14: stripped down additional PCI probe (thanks to David C Niemi + * and sveneric@xs4all.nl for testing this on their Compaq boxes) + * v0.15: added 79C965 (VLB) probe + * added interrupt sharing for PCI chips + * v0.16: fixed set_multicast_list on Alpha machines + * v0.17: removed hack from dev.c; now pcnet32 uses ethif_probe in Space.c + * v0.19: changed setting of autoselect bit + * v0.20: removed additional Compaq PCI probe; there is now a working one + * in arch/i386/bios32.c + * v0.21: added endian conversion for ppc, from work by cort@cs.nmt.edu + * v0.22: added printing of status to ring dump + * v0.23: changed enet_statistics to net_devive_stats + * v0.99: Changes for 2.0.34 final release. -djb + */ + + +#ifndef __powerpc__ +#define le16_to_cpu(val) (val) +#define le32_to_cpu(val) (val) +#endif +#if (LINUX_VERSION_CODE < 0x20123) +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif + +#define TX_RING_SIZE (1 << (PCNET_LOG_TX_BUFFERS)) +#define TX_RING_MOD_MASK (TX_RING_SIZE - 1) +#define TX_RING_LEN_BITS ((PCNET_LOG_TX_BUFFERS) << 12) + +#define RX_RING_SIZE (1 << (PCNET_LOG_RX_BUFFERS)) +#define RX_RING_MOD_MASK (RX_RING_SIZE - 1) +#define RX_RING_LEN_BITS ((PCNET_LOG_RX_BUFFERS) << 4) + +#define PKT_BUF_SZ 1544 + +/* Offsets from base I/O address. */ +enum pcnet_offsets { PCNET32_DATA=0x10, PCNET32_ADDR=0x12, PCNET32_RESET=0x14, + PCNET32_BUS_IF=0x16,}; +#define PCNET32_TOTAL_SIZE 0x20 + +/* The PCNET32 Rx and Tx ring descriptors. */ +struct pcnet32_rx_head { + u32 base; + s16 buf_length; + s16 status; + u32 msg_length; + u32 reserved; +}; + +struct pcnet32_tx_head { + u32 base; + s16 length; + s16 status; + u32 misc; + u32 reserved; +}; + +/* The PCNET32 32-Bit initialization block, described in databook. */ +struct pcnet32_init_block { + u16 mode; + u16 tlen_rlen; + u8 phys_addr[6]; + u16 reserved; + u32 filter[2]; + /* Receive and transmit ring base, along with extra bits. */ + u32 rx_ring; + u32 tx_ring; +}; + +struct pcnet32_private { + /* The Tx and Rx ring entries must be aligned on 16-byte boundaries + in 32bit mode. */ + struct pcnet32_rx_head rx_ring[RX_RING_SIZE]; + struct pcnet32_tx_head tx_ring[TX_RING_SIZE]; + struct pcnet32_init_block init_block; + const char *name; + struct device *next_module; + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + unsigned long rx_buffs; /* Address of Rx and Tx buffers. */ + int cur_rx, cur_tx; /* The next free ring entry */ + int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + struct enet_statistics stats; + char tx_full; + unsigned long lock; +}; + +static struct pcnet_chip_type { + int id_number; + const char *name; + int flags; +} chip_table[] = { + {0x2420, "PCnet/PCI 79C970", 0}, + {0x2430, "PCnet32", 0}, + {0x2621, "PCnet/PCI II 79C970A", 0}, + {0x2623, "PCnet/FAST 79C971", 0}, + {0x2624, "PCnet/FAST+ 79C972", 0}, + {0x0, "PCnet32 (unknown)", 0}, +}; + +/* Index of functions. */ +int pcnet32_probe(struct device *dev); +static int pcnet32_probe1(struct device *dev, unsigned int ioaddr, unsigned char irq_line); +static int pcnet32_open(struct device *dev); +static void pcnet32_init_ring(struct device *dev); +static int pcnet32_start_xmit(struct sk_buff *skb, struct device *dev); +static int pcnet32_rx(struct device *dev); +static void pcnet32_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int pcnet32_close(struct device *dev); +static struct enet_statistics *pcnet32_get_stats(struct device *dev); +static void pcnet32_set_multicast_list(struct device *dev); + + +/* A list of all installed PCnet32 devices, for removing the driver module. */ +static struct device *root_pcnet32_dev = NULL; + +int pcnet32_probe (struct device *dev) +{ + static int pci_index = 0; /* Static, for multiple probe calls. */ + int cards_found = 0; + + if ( ! pcibios_present()) + return ENODEV; + + for (;pci_index < 0xff; pci_index++) { + u8 irq_line; + u16 pci_command, new_command; + unsigned char pci_bus, pci_device_fn; + u32 pci_ioaddr; + + if (pcibios_find_device (PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LANCE, + pci_index, &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + break; + + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &irq_line); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + + /* Avoid already found cards from previous pcnet32_probe() calls */ + if (check_region(pci_ioaddr, PCNET32_TOTAL_SIZE)) + continue; + + /* Activate the card: fix for brain-damaged Win98 BIOSes. */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled the AMD Ethernet" + " device at %2x-%2x." + " Updating PCI command %4.4x->%4.4x.\n", + pci_bus, pci_device_fn, pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + + if (pcnet32_probe1(dev, pci_ioaddr, irq_line) != 0) { + /* Should never happen. */ + printk(KERN_ERR "pcnet32.c: Probe of PCI card at %#x failed.\n", + pci_ioaddr); + } else + dev = 0; + cards_found++; + } + + return cards_found ? 0 : -ENODEV; +} + + +/* pcnet32_probe1 */ +static int pcnet32_probe1(struct device *dev, unsigned int ioaddr, unsigned char irq_line) +{ + struct pcnet32_private *lp; + int i; + const char *chipname; + + /* check if there is really a pcnet chip on that ioaddr */ + if ((inb(ioaddr + 14) != 0x57) || (inb(ioaddr + 15) != 0x57)) + return ENODEV; + + inw(ioaddr+PCNET32_RESET); /* Reset the PCNET32 */ + + outw(0x0000, ioaddr+PCNET32_ADDR); /* Switch to window 0 */ + if (inw(ioaddr+PCNET32_DATA) != 0x0004) + return ENODEV; + + /* Get the version of the chip. */ + outw(88, ioaddr+PCNET32_ADDR); + if (inw(ioaddr+PCNET32_ADDR) != 88) { + /* should never happen */ + return ENODEV; + } else { /* Good, it's a newer chip. */ + int chip_version = inw(ioaddr+PCNET32_DATA); + outw(89, ioaddr+PCNET32_ADDR); + chip_version |= inw(ioaddr+PCNET32_DATA) << 16; + if (pcnet32_debug > 2) + printk(" PCnet chip version is %#x.\n", chip_version); + if ((chip_version & 0xfff) != 0x003) + return ENODEV; + chip_version = (chip_version >> 12) & 0xffff; + for (i = 0; chip_table[i].id_number; i++) + if (chip_table[i].id_number == chip_version) + break; + chipname = chip_table[i].name; + } + + dev = init_etherdev(dev, 0); + + printk("%s: %s at %#3x,", dev->name, chipname, ioaddr); + + /* There is a 16 byte station address PROM at the base address. + The first six bytes are the station address. */ + for (i = 0; i < 6; i++) + printk(" %2.2x", dev->dev_addr[i] = inb(ioaddr + i)); + + printk("\n"); + + dev->base_addr = ioaddr; + request_region(ioaddr, PCNET32_TOTAL_SIZE, dev->name); + + /* Data structures used by the PCnet32 are 16byte aligned and DMAble. */ + lp = (struct pcnet32_private *) + (((unsigned long)kmalloc(sizeof(*lp)+15, GFP_DMA | GFP_KERNEL)+15) & ~15); + + memset(lp, 0, sizeof(*lp)); + dev->priv = lp; + + lp->next_module = root_pcnet32_dev; + root_pcnet32_dev = dev; + + lp->name = chipname; + lp->rx_buffs = (unsigned long) kmalloc(PKT_BUF_SZ*RX_RING_SIZE, GFP_DMA | GFP_KERNEL); + + lp->init_block.mode = le16_to_cpu(0x0003); /* Disable Rx and Tx. */ + lp->init_block.tlen_rlen = le16_to_cpu(TX_RING_LEN_BITS | RX_RING_LEN_BITS); + for (i = 0; i < 6; i++) + lp->init_block.phys_addr[i] = dev->dev_addr[i]; + lp->init_block.filter[0] = 0x00000000; + lp->init_block.filter[1] = 0x00000000; + lp->init_block.rx_ring = (u32)le32_to_cpu(virt_to_bus(lp->rx_ring)); + lp->init_block.tx_ring = (u32)le32_to_cpu(virt_to_bus(lp->tx_ring)); + + /* switch pcnet32 to 32bit mode */ + outw(0x0014, ioaddr+PCNET32_ADDR); + outw(0x0002, ioaddr+PCNET32_BUS_IF); + + outw(0x0001, ioaddr+PCNET32_ADDR); + inw(ioaddr+PCNET32_ADDR); + outw(virt_to_bus(&lp->init_block) & 0xffff, ioaddr+PCNET32_DATA); + outw(0x0002, ioaddr+PCNET32_ADDR); + inw(ioaddr+PCNET32_ADDR); + outw(virt_to_bus(&lp->init_block) >> 16, ioaddr+PCNET32_DATA); + outw(0x0000, ioaddr+PCNET32_ADDR); + inw(ioaddr+PCNET32_ADDR); + + dev->irq = irq_line; + + if (pcnet32_debug > 0) + printk(version); + + /* The PCNET32-specific entries in the device structure. */ + dev->open = &pcnet32_open; + dev->hard_start_xmit = &pcnet32_start_xmit; + dev->stop = &pcnet32_close; + dev->get_stats = &pcnet32_get_stats; + dev->set_multicast_list = &pcnet32_set_multicast_list; + + /* Fill in the generic fields of the device structure. */ + ether_setup(dev); + return 0; +} + + +static int +pcnet32_open(struct device *dev) +{ + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + unsigned int ioaddr = dev->base_addr; + int i; + + if (dev->irq == 0 || + request_irq(dev->irq, &pcnet32_interrupt, SA_SHIRQ, + dev->name, (void *)dev)) { + return -EAGAIN; + } + MOD_INC_USE_COUNT; + + /* Reset the PCNET32 */ + inw(ioaddr+PCNET32_RESET); + + /* switch pcnet32 to 32bit mode */ + outw(0x0014, ioaddr+PCNET32_ADDR); + outw(0x0002, ioaddr+PCNET32_BUS_IF); + + /* Turn on auto-select of media (AUI, BNC). */ + outw(0x0002, ioaddr+PCNET32_ADDR); + /* only touch autoselect bit */ + outw(inw(ioaddr+PCNET32_BUS_IF) | 0x0002, ioaddr+PCNET32_BUS_IF); + + if (pcnet32_debug > 1) + printk("%s: pcnet32_open() irq %d tx/rx rings %#x/%#x init %#x.\n", + dev->name, dev->irq, + (u32) virt_to_bus(lp->tx_ring), + (u32) virt_to_bus(lp->rx_ring), + (u32) virt_to_bus(&lp->init_block)); + + /* check for ATLAS T1/E1 LAW card */ + if (dev->dev_addr[0] == 0x00 && dev->dev_addr[1] == 0xe0 && dev->dev_addr[2] == 0x75) { + /* select GPSI mode */ + lp->init_block.mode = 0x0100; + outw(0x0002, ioaddr+PCNET32_ADDR); + outw(inw(ioaddr+PCNET32_BUS_IF) & ~2, ioaddr+PCNET32_BUS_IF); + /* switch full duplex on */ + outw(0x0009, ioaddr+PCNET32_ADDR); + outw(inw(ioaddr+PCNET32_BUS_IF) | 1, ioaddr+PCNET32_BUS_IF); + } else + lp->init_block.mode = 0x0000; + lp->init_block.filter[0] = 0x00000000; + lp->init_block.filter[1] = 0x00000000; + pcnet32_init_ring(dev); + + /* Re-initialize the PCNET32, and start it when done. */ + outw(0x0001, ioaddr+PCNET32_ADDR); + outw(virt_to_bus(&lp->init_block) &0xffff, ioaddr+PCNET32_DATA); + outw(0x0002, ioaddr+PCNET32_ADDR); + outw(virt_to_bus(&lp->init_block) >> 16, ioaddr+PCNET32_DATA); + + outw(0x0004, ioaddr+PCNET32_ADDR); + outw(0x0915, ioaddr+PCNET32_DATA); + + outw(0x0000, ioaddr+PCNET32_ADDR); + outw(0x0001, ioaddr+PCNET32_DATA); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + i = 0; + while (i++ < 100) + if (inw(ioaddr+PCNET32_DATA) & 0x0100) + break; + /* + * We used to clear the InitDone bit, 0x0100, here but Mark Stockton + * reports that doing so triggers a bug in the '974. + */ + outw(0x0042, ioaddr+PCNET32_DATA); + + if (pcnet32_debug > 2) + printk("%s: PCNET32 open after %d ticks, init block %#x csr0 %4.4x.\n", + dev->name, i, (u32) virt_to_bus(&lp->init_block), inw(ioaddr+PCNET32_DATA)); + + return 0; /* Always succeed */ +} + +/* + * The LANCE has been halted for one reason or another (busmaster memory + * arbitration error, Tx FIFO underflow, driver stopped it to reconfigure, + * etc.). Modern LANCE variants always reload their ring-buffer + * configuration when restarted, so we must reinitialize our ring + * context before restarting. As part of this reinitialization, + * find all packets still on the Tx ring and pretend that they had been + * sent (in effect, drop the packets on the floor) - the higher-level + * protocols will time out and retransmit. It'd be better to shuffle + * these skbs to a temp list and then actually re-Tx them after + * restarting the chip, but I'm too lazy to do so right now. dplatt@3do.com + */ + +static void +pcnet32_purge_tx_ring(struct device *dev) +{ + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + int i; + + for (i = 0; i < TX_RING_SIZE; i++) { + if (lp->tx_skbuff[i]) { + dev_kfree_skb(lp->tx_skbuff[i], FREE_WRITE); + lp->tx_skbuff[i] = NULL; + } + } +} + + +/* Initialize the PCNET32 Rx and Tx rings. */ +static void +pcnet32_init_ring(struct device *dev) +{ + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + int i; + + lp->lock = 0, lp->tx_full = 0; + lp->cur_rx = lp->cur_tx = 0; + lp->dirty_rx = lp->dirty_tx = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + lp->rx_ring[i].base = (u32)le32_to_cpu(virt_to_bus((char *)lp->rx_buffs + i*PKT_BUF_SZ)); + lp->rx_ring[i].buf_length = le16_to_cpu(-PKT_BUF_SZ); + lp->rx_ring[i].status = le16_to_cpu(0x8000); + } + /* The Tx buffer address is filled in as needed, but we do need to clear + the upper ownership bit. */ + for (i = 0; i < TX_RING_SIZE; i++) { + lp->tx_ring[i].base = 0; + lp->tx_ring[i].status = 0; + } + + lp->init_block.tlen_rlen = TX_RING_LEN_BITS | RX_RING_LEN_BITS; + for (i = 0; i < 6; i++) + lp->init_block.phys_addr[i] = dev->dev_addr[i]; + lp->init_block.rx_ring = (u32)le32_to_cpu(virt_to_bus(lp->rx_ring)); + lp->init_block.tx_ring = (u32)le32_to_cpu(virt_to_bus(lp->tx_ring)); +} + +static void +pcnet32_restart(struct device *dev, unsigned int csr0_bits, int must_reinit) +{ + int i; + unsigned int ioaddr = dev->base_addr; + + pcnet32_purge_tx_ring(dev); + pcnet32_init_ring(dev); + + outw(0x0000, ioaddr + PCNET32_ADDR); + /* ReInit Ring */ + outw(0x0001, ioaddr + PCNET32_DATA); + i = 0; + while (i++ < 100) + if (inw(ioaddr+PCNET32_DATA) & 0x0100) + break; + + outw(csr0_bits, ioaddr + PCNET32_DATA); +} + +static int +pcnet32_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + unsigned int ioaddr = dev->base_addr; + int entry; + unsigned long flags; + + /* Transmitter timeout, serious problems. */ + if (dev->tbusy) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 20) + return 1; + outw(0, ioaddr+PCNET32_ADDR); + printk("%s: transmit timed out, status %4.4x, resetting.\n", + dev->name, inw(ioaddr+PCNET32_DATA)); + outw(0x0004, ioaddr+PCNET32_DATA); + lp->stats.tx_errors++; +#ifndef final_version + { + int i; + printk(" Ring data dump: dirty_tx %d cur_tx %d%s cur_rx %d.", + lp->dirty_tx, lp->cur_tx, lp->tx_full ? " (full)" : "", + lp->cur_rx); + for (i = 0 ; i < RX_RING_SIZE; i++) + printk("%s %08x %04x %08x %04x", i & 1 ? "" : "\n ", + lp->rx_ring[i].base, -lp->rx_ring[i].buf_length, + lp->rx_ring[i].msg_length, (unsigned)lp->rx_ring[i].status); + for (i = 0 ; i < TX_RING_SIZE; i++) + printk("%s %08x %04x %08x %04x", i & 1 ? "" : "\n ", + lp->tx_ring[i].base, -lp->tx_ring[i].length, + lp->tx_ring[i].misc, (unsigned)lp->tx_ring[i].status); + printk("\n"); + } +#endif + pcnet32_restart(dev, 0x0042, 1); + + dev->tbusy = 0; + dev->trans_start = jiffies; + + return 0; + } + + if (pcnet32_debug > 3) { + outw(0x0000, ioaddr+PCNET32_ADDR); + printk("%s: pcnet32_start_xmit() called, csr0 %4.4x.\n", dev->name, + inw(ioaddr+PCNET32_DATA)); + outw(0x0000, ioaddr+PCNET32_DATA); + } + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + printk("%s: Transmitter access conflict.\n", dev->name); + return 1; + } + + if (test_and_set_bit(0, (void*)&lp->lock) != 0) { + if (pcnet32_debug > 0) + printk("%s: tx queue lock!.\n", dev->name); + /* don't clear dev->tbusy flag. */ + return 1; + } + + /* Fill in a Tx ring entry */ + + /* Mask to ring buffer boundary. */ + entry = lp->cur_tx & TX_RING_MOD_MASK; + + /* Caution: the write order is important here, set the base address + with the "ownership" bits last. */ + + lp->tx_ring[entry].length = le16_to_cpu(-skb->len); + + lp->tx_ring[entry].misc = 0x00000000; + + lp->tx_skbuff[entry] = skb; + lp->tx_ring[entry].base = (u32)le32_to_cpu(virt_to_bus(skb->data)); + lp->tx_ring[entry].status = le16_to_cpu(0x8300); + + lp->cur_tx++; + + /* Trigger an immediate send poll. */ + outw(0x0000, ioaddr+PCNET32_ADDR); + outw(0x0048, ioaddr+PCNET32_DATA); + + dev->trans_start = jiffies; + + save_flags(flags); + cli(); + lp->lock = 0; + if (lp->tx_ring[(entry+1) & TX_RING_MOD_MASK].base == 0) + clear_bit(0, (void*)&dev->tbusy); + else + lp->tx_full = 1; + restore_flags(flags); + + return 0; +} + +/* The PCNET32 interrupt handler. */ +static void +pcnet32_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = (struct device *)dev_id; + struct pcnet32_private *lp; + unsigned int csr0, ioaddr; + int boguscnt = max_interrupt_work; + int must_restart; + + if (dev == NULL) { + printk ("pcnet32_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + ioaddr = dev->base_addr; + lp = (struct pcnet32_private *)dev->priv; + if (dev->interrupt) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + + dev->interrupt = 1; + + outw(0x00, dev->base_addr + PCNET32_ADDR); + while ((csr0 = inw(dev->base_addr + PCNET32_DATA)) & 0x8600 + && --boguscnt >= 0) { + /* Acknowledge all of the current interrupt sources ASAP. */ + outw(csr0 & ~0x004f, dev->base_addr + PCNET32_DATA); + + must_restart = 0; + + if (pcnet32_debug > 5) + printk("%s: interrupt csr0=%#2.2x new csr=%#2.2x.\n", + dev->name, csr0, inw(dev->base_addr + PCNET32_DATA)); + + if (csr0 & 0x0400) /* Rx interrupt */ + pcnet32_rx(dev); + + if (csr0 & 0x0200) { /* Tx-done interrupt */ + int dirty_tx = lp->dirty_tx; + + while (dirty_tx < lp->cur_tx) { + int entry = dirty_tx & TX_RING_MOD_MASK; + int status = (short)le16_to_cpu(lp->tx_ring[entry].status); + + if (status < 0) + break; /* It still hasn't been Txed */ + + lp->tx_ring[entry].base = 0; + + if (status & 0x4000) { + /* There was an major error, log it. */ + int err_status = le16_to_cpu(lp->tx_ring[entry].misc); + lp->stats.tx_errors++; + if (err_status & 0x04000000) lp->stats.tx_aborted_errors++; + if (err_status & 0x08000000) lp->stats.tx_carrier_errors++; + if (err_status & 0x10000000) lp->stats.tx_window_errors++; + if (err_status & 0x40000000) { + /* Ackk! On FIFO errors the Tx unit is turned off! */ + lp->stats.tx_fifo_errors++; + /* Remove this verbosity later! */ + printk("%s: Tx FIFO error! Status %4.4x.\n", + dev->name, csr0); + /* Restart the chip. */ + must_restart = 1; + } + } else { + if (status & 0x1800) + lp->stats.collisions++; + lp->stats.tx_packets++; + } + + /* We must free the original skb */ + if (lp->tx_skbuff[entry]) { + dev_kfree_skb(lp->tx_skbuff[entry], FREE_WRITE); + lp->tx_skbuff[entry] = 0; + } + dirty_tx++; + } + +#ifndef final_version + if (lp->cur_tx - dirty_tx >= TX_RING_SIZE) { + printk("out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dirty_tx, lp->cur_tx, lp->tx_full); + dirty_tx += TX_RING_SIZE; + } +#endif + + if (lp->tx_full && dev->tbusy + && dirty_tx > lp->cur_tx - TX_RING_SIZE + 2) { + /* The ring is no longer full, clear tbusy. */ + lp->tx_full = 0; + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + + lp->dirty_tx = dirty_tx; + } + + /* Log misc errors. */ + if (csr0 & 0x4000) lp->stats.tx_errors++; /* Tx babble. */ + if (csr0 & 0x1000) { + /* + * this happens when our receive ring is full. This + * shouldn't be a problem as we will see normal rx + * interrupts for the frames in the receive ring. But + * there are some PCI chipsets (I can reproduce this + * on SP3G with Intel saturn chipset) which have some- + * times problems and will fill up the receive ring + * with error descriptors. In this situation we don't + * get a rx interrupt, but a missed frame interrupt + * sooner or later. So we try to clean up our receive + * ring here. + */ + pcnet32_rx(dev); + lp->stats.rx_errors++; /* Missed a Rx frame. */ + } + if (csr0 & 0x0800) { + printk("%s: Bus master arbitration failure, status %4.4x.\n", + dev->name, csr0); + /* Restart the chip. */ + must_restart = 1; + } + + if (must_restart) { + /* stop the chip to clear the error condition, then restart */ + outw(0x0000, dev->base_addr + PCNET32_ADDR); + outw(0x0004, dev->base_addr + PCNET32_DATA); + pcnet32_restart(dev, 0x0002, 0); + } + } + + /* Clear any other interrupt, and set interrupt enable. */ + outw(0x0000, dev->base_addr + PCNET32_ADDR); + outw(0x7940, dev->base_addr + PCNET32_DATA); + + if (pcnet32_debug > 4) + printk("%s: exiting interrupt, csr%d=%#4.4x.\n", + dev->name, inw(ioaddr + PCNET32_ADDR), + inw(dev->base_addr + PCNET32_DATA)); + + dev->interrupt = 0; + return; +} + +static int +pcnet32_rx(struct device *dev) +{ + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + int entry = lp->cur_rx & RX_RING_MOD_MASK; + int i; + + /* If we own the next entry, it's a new packet. Send it up. */ + while ((short)le16_to_cpu(lp->rx_ring[entry].status) >= 0) { + int status = (short)le16_to_cpu(lp->rx_ring[entry].status) >> 8; + + if (status != 0x03) { /* There was an error. */ + /* There is a tricky error noted by John Murphy, + <murf@perftech.com> to Russ Nelson: Even with full-sized + buffers it's possible for a jabber packet to use two + buffers, with only the last correctly noting the error. */ + if (status & 0x01) /* Only count a general error at the */ + lp->stats.rx_errors++; /* end of a packet.*/ + if (status & 0x20) lp->stats.rx_frame_errors++; + if (status & 0x10) lp->stats.rx_over_errors++; + if (status & 0x08) lp->stats.rx_crc_errors++; + if (status & 0x04) lp->stats.rx_fifo_errors++; + lp->rx_ring[entry].status &= le16_to_cpu(0x03ff); + } + else + { + /* Malloc up new buffer, compatible with net-2e. */ + short pkt_len = (le32_to_cpu(lp->rx_ring[entry].msg_length) & 0xfff)-4; + struct sk_buff *skb; + + if(pkt_len < 60) { + printk("%s: Runt packet!\n",dev->name); + lp->stats.rx_errors++; + } else { + skb = dev_alloc_skb(pkt_len+2); + if (skb == NULL) { + printk("%s: Memory squeeze, deferring packet.\n", + dev->name); + for (i=0; i < RX_RING_SIZE; i++) + if ((short)le16_to_cpu(lp->rx_ring[(entry+i) & RX_RING_MOD_MASK].status) < 0) + break; + + if (i > RX_RING_SIZE -2) + { + lp->stats.rx_dropped++; + lp->rx_ring[entry].status |= le16_to_cpu(0x8000); + lp->cur_rx++; + } + break; + } + skb->dev = dev; + skb_reserve(skb,2); /* 16 byte align */ + skb_put(skb,pkt_len); /* Make room */ + eth_copy_and_sum(skb, + (unsigned char *)bus_to_virt(le32_to_cpu(lp->rx_ring[entry].base)), + pkt_len,0); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + } + /* The docs say that the buffer length isn't touched, but Andrew Boyd + of QNX reports that some revs of the 79C965 clear it. */ + lp->rx_ring[entry].buf_length = le16_to_cpu(-PKT_BUF_SZ); + lp->rx_ring[entry].status |= le16_to_cpu(0x8000); + entry = (++lp->cur_rx) & RX_RING_MOD_MASK; + } + + /* We should check that at least two ring entries are free. If not, + we should free one and mark stats->rx_dropped++. */ + + return 0; +} + +static int +pcnet32_close(struct device *dev) +{ + unsigned int ioaddr = dev->base_addr; + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + + dev->start = 0; + set_bit(0, (void*)&dev->tbusy); + + outw(112, ioaddr+PCNET32_ADDR); + lp->stats.rx_missed_errors = inw(ioaddr+PCNET32_DATA); + + outw(0, ioaddr+PCNET32_ADDR); + + if (pcnet32_debug > 1) + printk("%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, inw(ioaddr+PCNET32_DATA)); + + /* We stop the PCNET32 here -- it occasionally polls + memory if we don't. */ + outw(0x0004, ioaddr+PCNET32_DATA); + + free_irq(dev->irq, dev); + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics *pcnet32_get_stats(struct device *dev) +{ + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + unsigned int ioaddr = dev->base_addr; + unsigned short saved_addr; + unsigned long flags; + + save_flags(flags); + cli(); + saved_addr = inw(ioaddr+PCNET32_ADDR); + outw(112, ioaddr+PCNET32_ADDR); + lp->stats.rx_missed_errors = inw(ioaddr+PCNET32_DATA); + outw(saved_addr, ioaddr+PCNET32_ADDR); + restore_flags(flags); + + return &lp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + */ + +static void pcnet32_set_multicast_list(struct device *dev) +{ + unsigned int ioaddr = dev->base_addr; + struct pcnet32_private *lp = (struct pcnet32_private *)dev->priv; + + if (dev->flags&IFF_PROMISC) { + /* Log any net taps. */ + printk("%s: Promiscuous mode enabled.\n", dev->name); + lp->init_block.mode |= 0x8000; + } else { + int num_addrs=dev->mc_count; + if(dev->flags&IFF_ALLMULTI) + num_addrs=1; + /* FIXIT: We don't use the multicast table, but rely on upper-layer filtering. */ + memset(lp->init_block.filter , (num_addrs == 0) ? 0 : -1, sizeof(lp->init_block.filter)); + lp->init_block.mode &= ~0x8000; + } + + outw(0, ioaddr+PCNET32_ADDR); + outw(0x0004, ioaddr+PCNET32_DATA); /* Temporarily stop the lance. */ + + pcnet32_restart(dev, 0x0042, 0); /* Resume normal operation */ + +} + + +#ifdef MODULE +#if LINUX_VERSION_CODE > 0x20118 +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("AMD PCnet/PCI ethernet driver"); +MODULE_PARM(debug, "i"); +MODULE_PARM(max_interrupt_work, "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(MAX_UNITS) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(MAX_UNITS) "i"); +#endif + +/* An additional parameter that may be passed in... */ +static int debug = -1; + +int +init_module(void) +{ + if (debug >= 0) + pcnet32_debug = debug; + +#ifdef CARDBUS + register_driver(&pcnet32_ops); + return 0; +#else + return pcnet32_probe(NULL); +#endif +} + +void +cleanup_module(void) +{ + struct device *next_dev; + +#ifdef CARDBUS + unregister_driver(&pcnet32_ops); +#endif + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_pcnet32_dev) { + next_dev = ((struct pcnet32_private *)root_pcnet32_dev->priv)->next_module; + unregister_netdev(root_pcnet32_dev); + release_region(root_pcnet32_dev->base_addr, PCNET32_TOTAL_SIZE); + kfree(root_pcnet32_dev); + root_pcnet32_dev = next_dev; + } +} + +#endif /* MODULE */ + + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c pcnet32.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/rtl8139.c b/linux/src/drivers/net/rtl8139.c new file mode 100644 index 00000000..b5c355bd --- /dev/null +++ b/linux/src/drivers/net/rtl8139.c @@ -0,0 +1,1300 @@ +/* rtl8139.c: A RealTek RTL8129/8139 Fast Ethernet driver for Linux. */ +/* + Written 1997-1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + All other rights reserved. + + This driver is for boards based on the RTL8129 and RTL8139 PCI ethernet + chips. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Support and updates available at + http://cesdis.gsfc.nasa.gov/linux/drivers/rtl8139.html + + Twister-tuning code contributed by Kinston <shangh@realtek.com.tw>. +*/ + +static const char *version = +"rtl8139.c:v0.99B 4/7/98 Donald Becker http://cesdis.gsfc.nasa.gov/linux/drivers/rtl8139.html\n"; + +/* A few user-configurable values. */ +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 10; + +/* Size of the in-memory receive ring. */ +#define RX_BUF_LEN_IDX 3 /* 0==8K, 1==16K, 2==32K, 3==64K */ +#define RX_BUF_LEN (8192 << RX_BUF_LEN_IDX) +/* Size of the Tx bounce buffers -- must be at least (dev->mtu+14+4). */ +#define TX_BUF_SIZE 1536 + +/* PCI Tuning Parameters + Threshold is bytes transferred to chip before transmission starts. */ +#define TX_FIFO_THRESH 256 /* In bytes, rounded down to 32 byte units. */ + +/* The following settings are log_2(bytes)-4: 0 == 16 bytes .. 6==1024. */ +#define RX_FIFO_THRESH 4 /* Rx buffer level before first PCI xfer. */ +#define RX_DMA_BURST 4 /* Maximum PCI burst, '4' is 256 bytes */ +#define TX_DMA_BURST 4 + +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((4000*HZ)/1000) + +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#define RUN_AT(x) (jiffies + (x)) + +#include <linux/delay.h> + +#if (LINUX_VERSION_CODE < 0x20123) +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif + +/* The I/O extent. */ +#define RTL8129_TOTAL_SIZE 0x80 + +#ifdef HAVE_DEVLIST +struct netdev_entry rtl8139_drv = +{"RTL8139", rtl8139_probe, RTL8129_TOTAL_SIZE, NULL}; +#endif + +static int rtl8129_debug = 1; + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the RealTek RTL8129, the RealTek Fast +Ethernet controllers for PCI. This chip is used on a few clone boards. + + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board. The system BIOS will assign the +PCI INTA signal to a (preferably otherwise unused) system IRQ line. +Note: Kernel versions earlier than 1.3.73 do not support shared PCI +interrupt lines. + +III. Driver operation + +IIIa. Rx Ring buffers + +The receive unit uses a single linear ring buffer rather than the more +common (and more efficient) descriptor-based architecture. Incoming frames +are sequentially stored into the Rx region, and the host copies them into +skbuffs. + +Comment: While it is theoretically possible to process many frames in place, +any delay in Rx processing would cause us to drop frames. More importantly, +the Linux protocol stack is not designed to operate in this manner. + +IIIb. Tx operation + +The RTL8129 uses a fixed set of four Tx descriptors in register space. +In a stunningly bad design choice, Tx frames must be 32 bit aligned. Linux +aligns the IP header on word boundaries, and 14 byte ethernet header means +that almost all frames will need to be copied to an alignment buffer. + +IVb. References + +http://www.realtek.com.tw/cn/cn.html +http://cesdis.gsfc.nasa.gov/linux/misc/NWay.html + +IVc. Errata + +*/ + +#ifndef PCI_VENDOR_ID_REALTEK +#define PCI_VENDOR_ID_REALTEK 0x10ec +#endif +#ifndef PCI_DEVICE_ID_REALTEK_8129 +#define PCI_DEVICE_ID_REALTEK_8129 0x8129 +#endif +#ifndef PCI_DEVICE_ID_REALTEK_8139 +#define PCI_DEVICE_ID_REALTEK_8139 0x8139 +#endif + +/* The rest of these values should never change. */ +#define NUM_TX_DESC 4 /* Number of Tx descriptor registers. */ + +/* Symbolic offsets to registers. */ +enum RTL8129_registers { + MAC0=0, /* Ethernet hardware address. */ + MAR0=8, /* Multicast filter. */ + TxStat0=0x10, /* Transmit status (Four 32bit registers). */ + TxAddr0=0x20, /* Tx descriptors (also four 32bit). */ + RxBuf=0x30, RxEarlyCnt=0x34, RxEarlyStatus=0x36, + ChipCmd=0x37, RxBufPtr=0x38, RxBufAddr=0x3A, + IntrMask=0x3C, IntrStatus=0x3E, + TxConfig=0x40, RxConfig=0x44, + Timer=0x48, /* A general-purpose counter. */ + RxMissed=0x4C, /* 24 bits valid, write clears. */ + Cfg9346=0x50, Config0=0x51, Config1=0x52, + FlashReg=0x54, GPPinData=0x58, GPPinDir=0x59, MII_SMI=0x5A, HltClk=0x5B, + MultiIntr=0x5C, TxSummary=0x60, + BMCR=0x62, BMSR=0x64, NWayAdvert=0x66, NWayLPAR=0x68, NWayExpansion=0x6A, + /* Undocumented registers, but required for proper operation. */ + FIFOTMS=0x70, /* FIFO Test Mode Select */ + CSCR=0x74, /* Chip Status and Configuration Register. */ + PARA78=0x78, PARA7c=0x7c, /* Magic transceiver parameter register. */ +}; + +enum ChipCmdBits { + CmdReset=0x10, CmdRxEnb=0x08, CmdTxEnb=0x04, RxBufEmpty=0x01, }; + +/* Interrupt register bits, using my own meaningful names. */ +enum IntrStatusBits { + PCIErr=0x8000, PCSTimeout=0x4000, + RxFIFOOver=0x40, RxUnderrun=0x20, RxOverflow=0x10, + TxErr=0x08, TxOK=0x04, RxErr=0x02, RxOK=0x01, +}; +enum TxStatusBits { + TxHostOwns=0x2000, TxUnderrun=0x4000, TxStatOK=0x8000, + TxOutOfWindow=0x20000000, TxAborted=0x40000000, TxCarrierLost=0x80000000, +}; +enum RxStatusBits { + RxMulticast=0x8000, RxPhysical=0x4000, RxBroadcast=0x2000, + RxBadSymbol=0x0020, RxRunt=0x0010, RxTooLong=0x0008, RxCRCErr=0x0004, + RxBadAlign=0x0002, RxStatusOK=0x0001, +}; + +enum CSCRBits { + CSCR_LinkOKBit=0x0400, CSCR_LinkChangeBit=0x0800, + CSCR_LinkStatusBits=0x0f000, CSCR_LinkDownOffCmd=0x003c0, + CSCR_LinkDownCmd=0x0f3c0, +}; + +/* Twister tuning parameters from RealTek. Completely undocumented. */ +unsigned long param[4][4]={ + {0x0cb39de43,0x0cb39ce43,0x0fb38de03,0x0cb38de43}, + {0x0cb39de43,0x0cb39ce43,0x0cb39ce83,0x0cb39ce83}, + {0x0cb39de43,0x0cb39ce43,0x0cb39ce83,0x0cb39ce83}, + {0x0bb39de43,0x0bb39ce43,0x0bb39ce83,0x0bb39ce83} +}; + +struct rtl8129_private { + char devname[8]; /* Used only for kernel debugging. */ + const char *product_name; + struct device *next_module; + int chip_id; + int chip_revision; +#if LINUX_VERSION_CODE > 0x20139 + struct net_device_stats stats; +#else + struct enet_statistics stats; +#endif + struct timer_list timer; /* Media selection timer. */ + unsigned int cur_rx, cur_tx; /* The next free and used entries */ + unsigned int dirty_rx, dirty_tx; + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[NUM_TX_DESC]; + unsigned char *tx_buf[NUM_TX_DESC]; /* Tx bounce buffers */ + unsigned char *rx_ring; + unsigned char *tx_bufs; /* Tx bounce buffer region. */ + unsigned char mc_filter[8]; /* Current multicast filter. */ + char phys[4]; /* MII device addresses. */ + int in_interrupt; /* Alpha needs word-wide lock. */ + unsigned int tx_full:1; /* The Tx queue is full. */ + unsigned int full_duplex:1; /* Full-duplex operation requested. */ + unsigned int default_port:4; /* Last dev->if_port value. */ + unsigned int media2:4; /* Secondary monitored media port. */ + unsigned int medialock:1; /* Don't sense media type. */ + unsigned int mediasense:1; /* Media sensing in progress. */ +}; + +#ifdef MODULE +/* Used to pass the full-duplex flag, etc. */ +static int options[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +static int full_duplex[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +#if LINUX_VERSION_CODE > 0x20118 +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("RealTek RTL8129/8139 Fast Ethernet driver"); +MODULE_PARM(debug, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(8) "i"); +MODULE_PARM(max_interrupt_work, "i"); +#endif +#endif + +static struct device *rtl8129_probe1(struct device *dev, int ioaddr, int irq, + int chip_id, int options, int card_idx); +static int rtl8129_open(struct device *dev); +static int read_eeprom(int ioaddr, int location); +static int mdio_read(int ioaddr, int phy_id, int location); +static void rtl8129_timer(unsigned long data); +static void rtl8129_tx_timeout(struct device *dev); +static void rtl8129_init_ring(struct device *dev); +static int rtl8129_start_xmit(struct sk_buff *skb, struct device *dev); +static int rtl8129_rx(struct device *dev); +static void rtl8129_interrupt(int irq, void *dev_instance, struct pt_regs *regs); +static int rtl8129_close(struct device *dev); +static struct enet_statistics *rtl8129_get_stats(struct device *dev); +static void set_rx_mode(struct device *dev); + + +#ifdef MODULE +/* A list of all installed RTL8129 devices, for removing the driver module. */ +static struct device *root_rtl8129_dev = NULL; +#endif + +int rtl8139_probe(struct device *dev) +{ + int cards_found = 0; + static int pci_index = 0; /* Static, for multiple probe calls. */ + + /* Ideally we would detect all network cards in slot order. That would + be best done a central PCI probe dispatch, which wouldn't work + well with the current structure. So instead we detect just the + Rtl81*9 cards in slot order. */ + + if (pcibios_present()) { + unsigned char pci_bus, pci_device_fn; + + for (;pci_index < 0xff; pci_index++) { + u8 pci_irq_line, pci_latency; + u16 pci_command, new_command, vendor, device; + u32 pci_ioaddr; + + if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8, +#ifdef REVERSE_PROBE_ORDER + 0xff - pci_index, +#else + pci_index, +#endif + &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + break; + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + if (vendor != PCI_VENDOR_ID_REALTEK) + continue; + + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + + if (device != PCI_DEVICE_ID_REALTEK_8129 + && device != PCI_DEVICE_ID_REALTEK_8139) { + printk(KERN_NOTICE "Unknown RealTek PCI ethernet chip type " + "%4.4x detected: not configured.\n", device); + continue; + } + if (check_region(pci_ioaddr, RTL8129_TOTAL_SIZE)) + continue; + + /* Activate the card: fix for brain-damaged Win98 BIOSes. */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled this" + " device! Updating PCI config %4.4x->%4.4x.\n", + pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + +#ifdef MODULE + dev = rtl8129_probe1(dev, pci_ioaddr, pci_irq_line, device, + options[cards_found], cards_found); +#else + dev = rtl8129_probe1(dev, pci_ioaddr, pci_irq_line, device, + dev ? dev->mem_start : 0, -1); +#endif + + if (dev) { + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < 32) { + printk(KERN_NOTICE" PCI latency timer (CFLT) is " + "unreasonably low at %d. Setting to 64 clocks.\n", + pci_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, 64); + } else if (rtl8129_debug > 1) + printk(KERN_INFO" PCI latency timer (CFLT) is %#x.\n", + pci_latency); + dev = 0; + cards_found++; + } + } + } + +#if defined (MODULE) + return cards_found; +#else + return cards_found ? 0 : -ENODEV; +#endif +} + +static struct device *rtl8129_probe1(struct device *dev, int ioaddr, int irq, + int chip_id, int options, int card_idx) +{ + static int did_version = 0; /* Already printed version info. */ + struct rtl8129_private *tp; + int i; + + if (rtl8129_debug > 0 && did_version++ == 0) + printk(KERN_INFO "%s", version); + + dev = init_etherdev(dev, 0); + + printk(KERN_INFO "%s: RealTek RTL%x at %#3x, IRQ %d, ", + dev->name, chip_id, ioaddr, irq); + + /* Bring the chip out of low-power mode. */ + outb(0x00, ioaddr + Config1); + + /* Perhaps this should be read from the EEPROM? */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb(ioaddr + MAC0 + i); + + for (i = 0; i < 5; i++) + printk("%2.2x:", dev->dev_addr[i]); + printk("%2.2x.\n", dev->dev_addr[i]); + + if (rtl8129_debug > 1) { + printk(KERN_INFO "%s: EEPROM contents\n", dev->name); + for (i = 0; i < 64; i++) + printk(" %4.4x%s", read_eeprom(ioaddr, i), + i%16 == 15 ? "\n"KERN_INFO : ""); + } + + /* We do a request_region() to register /proc/ioports info. */ + request_region(ioaddr, RTL8129_TOTAL_SIZE, "RealTek RTL8129/39 Fast Ethernet"); + + dev->base_addr = ioaddr; + dev->irq = irq; + + /* Some data structures must be quadword aligned. */ + tp = kmalloc(sizeof(*tp), GFP_KERNEL | GFP_DMA); + memset(tp, 0, sizeof(*tp)); + dev->priv = tp; + +#ifdef MODULE + tp->next_module = root_rtl8129_dev; + root_rtl8129_dev = dev; +#endif + + tp->chip_id = chip_id; + + /* Find the connected MII xcvrs. + Doing this in open() would allow detecting external xcvrs later, but + takes too much time. */ + if (chip_id == 0x8129) { + int phy, phy_idx; + for (phy = 0, phy_idx = 0; phy < 32 && phy_idx < sizeof(tp->phys); + phy++) { + int mii_status = mdio_read(ioaddr, phy, 1); + + if (mii_status != 0xffff && mii_status != 0x0000) { + tp->phys[phy_idx++] = phy; + printk(KERN_INFO "%s: MII transceiver found at address %d.\n", + dev->name, phy); + } + } + if (phy_idx == 0) { + printk(KERN_INFO "%s: No MII transceivers found! Assuming SYM " + "transceiver.\n", + dev->name); + tp->phys[0] = -1; + } + } else { + tp->phys[0] = -1; + } + + /* Put the chip into low-power mode. */ + outb(0xC0, ioaddr + Cfg9346); + outb(0x03, ioaddr + Config1); + outb('H', ioaddr + HltClk); /* 'R' would leave the clock running. */ + + /* The lower four bits are the media type. */ + if (options > 0) { + tp->full_duplex = (options & 16) ? 1 : 0; + tp->default_port = options & 15; + if (tp->default_port) + tp->medialock = 1; + } +#ifdef MODULE + if (card_idx >= 0) { + if (full_duplex[card_idx] >= 0) + tp->full_duplex = full_duplex[card_idx]; + } +#endif + + /* The Rtl8129-specific entries in the device structure. */ + dev->open = &rtl8129_open; + dev->hard_start_xmit = &rtl8129_start_xmit; + dev->stop = &rtl8129_close; + dev->get_stats = &rtl8129_get_stats; + dev->set_multicast_list = &set_rx_mode; + + return dev; +} + +/* Serial EEPROM section. */ + +/* EEPROM_Ctrl bits. */ +#define EE_SHIFT_CLK 0x04 /* EEPROM shift clock. */ +#define EE_CS 0x08 /* EEPROM chip select. */ +#define EE_DATA_WRITE 0x02 /* EEPROM chip data in. */ +#define EE_WRITE_0 0x00 +#define EE_WRITE_1 0x02 +#define EE_DATA_READ 0x01 /* EEPROM chip data out. */ +#define EE_ENB (0x80 | EE_CS) + +/* Delay between EEPROM clock transitions. + No extra delay is needed with 33Mhz PCI, but 66Mhz is untested. + */ + +#ifdef _LINUX_DELAY_H +#define eeprom_delay(nanosec) udelay(1) +#else +#define eeprom_delay(nanosec) do { ; } while (0) +#endif + +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD (5 << 6) +#define EE_READ_CMD (6 << 6) +#define EE_ERASE_CMD (7 << 6) + +static int read_eeprom(int ioaddr, int location) +{ + int i; + unsigned retval = 0; + int ee_addr = ioaddr + Cfg9346; + int read_cmd = location | EE_READ_CMD; + + outb(EE_ENB & ~EE_CS, ee_addr); + outb(EE_ENB, ee_addr); + + /* Shift the read command bits out. */ + for (i = 10; i >= 0; i--) { + int dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0; + outb(EE_ENB | dataval, ee_addr); + eeprom_delay(100); + outb(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr); + eeprom_delay(150); + outb(EE_ENB | dataval, ee_addr); /* Finish EEPROM a clock tick. */ + eeprom_delay(250); + } + outb(EE_ENB, ee_addr); + + for (i = 16; i > 0; i--) { + outb(EE_ENB | EE_SHIFT_CLK, ee_addr); + eeprom_delay(100); + retval = (retval << 1) | ((inb(ee_addr) & EE_DATA_READ) ? 1 : 0); + outb(EE_ENB, ee_addr); + eeprom_delay(100); + } + + /* Terminate the EEPROM access. */ + outb(~EE_CS, ee_addr); + return retval; +} + +/* MII serial management: mostly bogus for now. */ +/* Read and write the MII management registers using software-generated + serial MDIO protocol. + The maximum data clock rate is 2.5 Mhz. The minimum timing is usually + met by back-to-back PCI I/O cycles, but we insert a delay to avoid + "overclocking" issues. */ +#define MDIO_DIR 0x80 +#define MDIO_DATA_OUT 0x04 +#define MDIO_DATA_IN 0x02 +#define MDIO_CLK 0x01 +#ifdef _LINUX_DELAY_H +#define mdio_delay() udelay(1) /* Really 400ns. */ +#else +#define mdio_delay() do { ; } while (0) +#endif + +/* Syncronize the MII management interface by shifting 32 one bits out. */ +static void mdio_sync(int ioaddr) +{ + int i; + int mdio_addr = ioaddr + MII_SMI; + + for (i = 32; i >= 0; i--) { + outb(MDIO_DIR | MDIO_DATA_OUT, mdio_addr); + mdio_delay(); + outb(MDIO_DIR | MDIO_DATA_OUT | MDIO_CLK, mdio_addr); + mdio_delay(); + } + return; +} +static int mdio_read(int ioaddr, int phy_id, int location) +{ + int i; + int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; + int retval = 0; + int mdio_addr = ioaddr + MII_SMI; + + mdio_sync(ioaddr); + /* Shift the read command bits out. */ + for (i = 15; i >= 0; i--) { + int dataval = + (read_cmd & (1 << i)) ? MDIO_DATA_OUT : 0; + + outb(MDIO_DIR | dataval, mdio_addr); + mdio_delay(); + outb(MDIO_DIR | dataval | MDIO_CLK, mdio_addr); + mdio_delay(); + } + + /* Read the two transition, 16 data, and wire-idle bits. */ + for (i = 19; i > 0; i--) { + outb(0, mdio_addr); + mdio_delay(); + retval = (retval << 1) | ((inb(mdio_addr) & MDIO_DATA_IN) ? 1 : 0); + outb(MDIO_CLK, mdio_addr); + mdio_delay(); + } + return (retval>>1) & 0xffff; +} + +static int +rtl8129_open(struct device *dev) +{ + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + int full_duplex = 0; + + /* Soft reset the chip. */ + outb(CmdReset, ioaddr + ChipCmd); + + if (request_irq(dev->irq, &rtl8129_interrupt, SA_SHIRQ, dev->name, dev)) { + return -EAGAIN; + } + + MOD_INC_USE_COUNT; + + tp->tx_bufs = kmalloc(TX_BUF_SIZE * NUM_TX_DESC, GFP_KERNEL); + tp->rx_ring = kmalloc(RX_BUF_LEN + 16, GFP_KERNEL); + if (tp->tx_bufs == NULL || tp->rx_ring == NULL) { + if (tp->tx_bufs) + kfree(tp->tx_bufs); + if (rtl8129_debug > 0) + printk(KERN_ERR "%s: Couldn't allocate a %d byte receive ring.\n", + dev->name, RX_BUF_LEN); + return -ENOMEM; + } + rtl8129_init_ring(dev); + + /* Check that the chip has finished the reset. */ + for (i = 1000; i > 0; i--) + if ((inb(ioaddr + ChipCmd) & CmdReset) == 0) + break; + + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + MAC0 + i); + + /* Must enable Tx/Rx before setting transfer thresholds! */ + outb(CmdRxEnb | CmdTxEnb, ioaddr + ChipCmd); + outl((RX_FIFO_THRESH << 13) | (RX_BUF_LEN_IDX << 11) | (RX_DMA_BURST<<8), + ioaddr + RxConfig); + outl((TX_DMA_BURST<<8)|0x03000000, ioaddr + TxConfig); + + full_duplex = tp->full_duplex; + if (tp->phys[0] >= 0 || tp->chip_id == 0x8139) { + u16 mii_reg5; + if (tp->chip_id == 0x8139) + mii_reg5 = inw(ioaddr + NWayLPAR); + else + mii_reg5 = mdio_read(ioaddr, tp->phys[0], 5); + if (mii_reg5 == 0xffff) + ; /* Not there */ + else if ((mii_reg5 & 0x0100) == 0x0100 + || (mii_reg5 & 0x00C0) == 0x0040) + full_duplex = 1; + if (rtl8129_debug > 1) + printk(KERN_INFO"%s: Setting %s%s-duplex based on" + " auto-negotiated partner ability %4.4x.\n", dev->name, + mii_reg5 == 0 ? "" : + (mii_reg5 & 0x0180) ? "100mbps " : "10mbps ", + full_duplex ? "full" : "half", mii_reg5); + } + + outb(0xC0, ioaddr + Cfg9346); + outb(full_duplex ? 0x60 : 0x20, ioaddr + Config1); + outb(0x00, ioaddr + Cfg9346); + + outl(virt_to_bus(tp->rx_ring), ioaddr + RxBuf); + + /* Start the chip's Tx and Rx process. */ + outl(0, ioaddr + RxMissed); + set_rx_mode(dev); + + outb(CmdRxEnb | CmdTxEnb, ioaddr + ChipCmd); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* Enable all known interrupts by setting the interrupt mask. */ + outw(PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver + | TxErr | TxOK | RxErr | RxOK, ioaddr + IntrMask); + + if (rtl8129_debug > 1) + printk(KERN_DEBUG"%s: rtl8129_open() ioaddr %4.4x IRQ %d" + " GP Pins %2.2x %s-duplex.\n", + dev->name, ioaddr, dev->irq, inb(ioaddr + GPPinData), + full_duplex ? "full" : "half"); + + /* Set the timer to switch to check for link beat and perhaps switch + to an alternate media type. */ + init_timer(&tp->timer); + tp->timer.expires = RUN_AT((24*HZ)/10); /* 2.4 sec. */ + tp->timer.data = (unsigned long)dev; + tp->timer.function = &rtl8129_timer; /* timer handler */ + add_timer(&tp->timer); + + return 0; +} + +static void rtl8129_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int ioaddr = dev->base_addr; + int next_tick = 0; + + if (tp->chip_id == 0x8139) { + u16 mii_reg5 = inw(ioaddr + NWayLPAR); + if ((mii_reg5 & 0x0100) == 0x0100 + || (mii_reg5 & 0x00C0) == 0x0040) + if ( ! tp->full_duplex) { + tp->full_duplex = 1; + if (rtl8129_debug > 0) + printk(KERN_INFO "%s: Switching to full-duplex based on " + "link partner ability of %4.4x.\n", + dev->name, mii_reg5); + outb(0xC0, ioaddr + Cfg9346); + outb(tp->full_duplex ? 0x60 : 0x20, ioaddr + Config1); + outb(0x00, ioaddr + Cfg9346); + } + } + if (rtl8129_debug > 2) { + if (tp->chip_id == 0x8129) + printk(KERN_DEBUG"%s: Media selection tick, GP pins %2.2x.\n", + dev->name, inb(ioaddr + GPPinData)); + else + printk(KERN_DEBUG"%s: Media selection tick, Link partner %4.4x.\n", + dev->name, inw(ioaddr + NWayLPAR)); + printk(KERN_DEBUG"%s: Other registers are IntMask %4.4x IntStatus %4.4x" + " RxStatus %4.4x.\n", + dev->name, inw(ioaddr + IntrMask), inw(ioaddr + IntrStatus), + inl(ioaddr + RxEarlyStatus)); + printk(KERN_DEBUG"%s: Chip config %2.2x %2.2x.\n", + dev->name, inb(ioaddr + Config0), inb(ioaddr + Config1)); + } + + if (next_tick) { + tp->timer.expires = RUN_AT(next_tick); + add_timer(&tp->timer); + } +} + +static void rtl8129_tx_timeout(struct device *dev) +{ + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + /* Disable interrupts by clearing the interrupt mask. */ + outw(0x0000, ioaddr + IntrMask); + + if (rtl8129_debug > 0) + printk(KERN_WARNING "%s: Transmit timeout, status %2.2x %4.4x.\n", + dev->name, inb(ioaddr + ChipCmd), inw(ioaddr + IntrStatus)); + /* Emit info to figure out what went wrong. */ + for (i = 0; i < NUM_TX_DESC; i++) + printk(KERN_DEBUG"%s: Tx descriptor %d is %8.8x.%s\n", + dev->name, i, inl(ioaddr + TxStat0 + i*4), + i == tp->dirty_tx % NUM_TX_DESC ? " (queue head)" : ""); + if (tp->chip_id == 0x8129) { + int mii_reg; + printk(KERN_DEBUG"%s: MII #%d registers are:", dev->name, tp->phys[0]); + for (mii_reg = 0; mii_reg < 8; mii_reg++) + printk(" %4.4x", mdio_read(ioaddr, tp->phys[0], mii_reg)); + printk(".\n"); + } else { + printk(KERN_DEBUG"%s: MII status register is %4.4x.\n", + dev->name, inw(ioaddr + BMSR)); + } + + /* Soft reset the chip. */ + outb(CmdReset, ioaddr + ChipCmd); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + MAC0 + i); + + { /* Save the unsent Tx packets. */ + struct sk_buff *saved_skb[NUM_TX_DESC], *skb; + int j = 0; + for (; tp->cur_tx - tp->dirty_tx > 0 ; tp->dirty_tx++) + saved_skb[j++] = tp->tx_skbuff[tp->dirty_tx % NUM_TX_DESC]; + tp->dirty_tx = tp->cur_tx = 0; + + for (i = 0; i < j; i++) { + skb = tp->tx_skbuff[i] = saved_skb[i]; + if ((long)skb->data & 3) { /* Must use alignment buffer. */ + memcpy(tp->tx_buf[i], skb->data, skb->len); + outl(virt_to_bus(tp->tx_buf[i]), ioaddr + TxAddr0 + i*4); + } else + outl(virt_to_bus(skb->data), ioaddr + TxAddr0 + i*4); + /* Note: the chip doesn't have auto-pad! */ + outl(((TX_FIFO_THRESH<<11) & 0x003f0000) | + (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN), + ioaddr + TxStat0 + i*4); + } + tp->cur_tx = i; + while (i < NUM_TX_DESC) + tp->tx_skbuff[i] = 0; + if (tp->cur_tx - tp->dirty_tx < NUM_TX_DESC) {/* Typical path */ + dev->tbusy = 0; + } else { + tp->tx_full = 1; + } + } + + /* Must enable Tx/Rx before setting transfer thresholds! */ + set_rx_mode(dev); + outb(CmdRxEnb | CmdTxEnb, ioaddr + ChipCmd); + outl((RX_FIFO_THRESH << 13) | (RX_BUF_LEN_IDX << 11) | (RX_DMA_BURST<<8), + ioaddr + RxConfig); + outl((TX_DMA_BURST<<8), ioaddr + TxConfig); + + dev->trans_start = jiffies; + tp->stats.tx_errors++; + /* Enable all known interrupts by setting the interrupt mask. */ + outw(PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver + | TxErr | TxOK | RxErr | RxOK, ioaddr + IntrMask); + return; +} + + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void +rtl8129_init_ring(struct device *dev) +{ + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int i; + + tp->tx_full = 0; + tp->cur_rx = tp->cur_tx = 0; + tp->dirty_rx = tp->dirty_tx = 0; + + for (i = 0; i < NUM_TX_DESC; i++) { + tp->tx_skbuff[i] = 0; + tp->tx_buf[i] = &tp->tx_bufs[i*TX_BUF_SIZE]; + } +} + +static int +rtl8129_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int ioaddr = dev->base_addr; + int entry; + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start < TX_TIMEOUT) + return 1; + rtl8129_tx_timeout(dev); + return 1; + } + + /* Calculate the next Tx descriptor entry. */ + entry = tp->cur_tx % NUM_TX_DESC; + + tp->tx_skbuff[entry] = skb; + if ((long)skb->data & 3) { /* Must use alignment buffer. */ + memcpy(tp->tx_buf[entry], skb->data, skb->len); + outl(virt_to_bus(tp->tx_buf[entry]), ioaddr + TxAddr0 + entry*4); + } else + outl(virt_to_bus(skb->data), ioaddr + TxAddr0 + entry*4); + /* Note: the chip doesn't have auto-pad! */ + outl(((TX_FIFO_THRESH<<11) & 0x003f0000) | + (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN), + ioaddr + TxStat0 + entry*4); + + if (++tp->cur_tx - tp->dirty_tx < NUM_TX_DESC) {/* Typical path */ + dev->tbusy = 0; + } else { + tp->tx_full = 1; + } + + dev->trans_start = jiffies; + if (rtl8129_debug > 4) + printk(KERN_DEBUG"%s: Queued Tx packet at %p size %ld to slot %d.\n", + dev->name, skb->data, skb->len, entry); + + return 0; +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void rtl8129_interrupt(int irq, void *dev_instance, struct pt_regs *regs) +{ + struct device *dev = (struct device *)dev_instance; + struct rtl8129_private *tp; + int ioaddr, boguscnt = max_interrupt_work; + int status; + + if (dev == NULL) { + printk (KERN_ERR"rtl8139_interrupt(): IRQ %d for unknown device.\n", + irq); + return; + } + + ioaddr = dev->base_addr; + tp = (struct rtl8129_private *)dev->priv; + if (test_and_set_bit(0, (void*)&tp->in_interrupt)) { + printk(KERN_ERR "%s: Re-entering the interrupt handler.\n", dev->name); + return; + } + dev->interrupt = 1; + + do { + status = inw(ioaddr + IntrStatus); + /* Acknowledge all of the current interrupt sources ASAP. */ + outw(status, ioaddr + IntrStatus); + + if (rtl8129_debug > 4) + printk(KERN_DEBUG"%s: interrupt status=%#4.4x new intstat=%#4.4x.\n", + dev->name, status, inw(ioaddr + IntrStatus)); + + if ((status & (PCIErr|PCSTimeout|RxUnderrun|RxOverflow|RxFIFOOver + |TxErr|TxOK|RxErr|RxOK)) == 0) + break; + + if (status & (RxOK|RxUnderrun|RxOverflow|RxFIFOOver))/* Rx interrupt */ + rtl8129_rx(dev); + + if (status & (TxOK | TxErr)) { + unsigned int dirty_tx; + + for (dirty_tx = tp->dirty_tx; dirty_tx < tp->cur_tx; dirty_tx++) { + int entry = dirty_tx % NUM_TX_DESC; + int txstatus = inl(ioaddr + TxStat0 + entry*4); + + if ( ! (txstatus & TxHostOwns)) + break; /* It still hasn't been Txed */ + + /* Note: TxCarrierLost is always asserted at 100mbps. */ + if (txstatus & (TxOutOfWindow | TxAborted)) { + /* There was an major error, log it. */ +#ifndef final_version + if (rtl8129_debug > 1) + printk(KERN_NOTICE"%s: Transmit error, Tx status %8.8x.\n", + dev->name, txstatus); +#endif + tp->stats.tx_errors++; + if (txstatus&TxAborted) { + tp->stats.tx_aborted_errors++; + outl((TX_DMA_BURST<<8)|0x03000001, ioaddr + TxConfig); + } + if (txstatus&TxCarrierLost) tp->stats.tx_carrier_errors++; + if (txstatus&TxOutOfWindow) tp->stats.tx_window_errors++; +#ifdef ETHER_STATS + if ((txstatus & 0x0f000000) == 0x0f000000) + tp->stats.collisions16++; +#endif + } else { +#ifdef ETHER_STATS + /* No count for tp->stats.tx_deferred */ +#endif + if (txstatus & TxUnderrun) { + /* Todo: increase the Tx FIFO threshold. */ + tp->stats.tx_fifo_errors++; + } + tp->stats.collisions += (txstatus >> 24) & 15; +#if LINUX_VERSION_CODE > 0x20119 + tp->stats.tx_bytes += txstatus & 0x7ff; +#endif + tp->stats.tx_packets++; + } + + /* Free the original skb. */ + dev_kfree_skb(tp->tx_skbuff[entry], FREE_WRITE); + tp->tx_skbuff[entry] = 0; + } + +#ifndef final_version + if (tp->cur_tx - dirty_tx > NUM_TX_DESC) { + printk(KERN_ERR"%s: Out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dev->name, dirty_tx, tp->cur_tx, tp->tx_full); + dirty_tx += NUM_TX_DESC; + } +#endif + + if (tp->tx_full && dev->tbusy + && dirty_tx > tp->cur_tx - NUM_TX_DESC) { + /* The ring is no longer full, clear tbusy. */ + tp->tx_full = 0; + dev->tbusy = 0; + mark_bh(NET_BH); + } + + tp->dirty_tx = dirty_tx; + } + + /* Check uncommon events with one test. */ + if (status & (PCIErr|PCSTimeout |RxUnderrun|RxOverflow|RxFIFOOver + |TxErr|RxErr)) { + /* Update the error count. */ + tp->stats.rx_missed_errors += inl(ioaddr + RxMissed); + outl(0, ioaddr + RxMissed); + + if (status & (RxUnderrun | RxOverflow | RxErr | RxFIFOOver)) + tp->stats.rx_errors++; + + if (status & (PCSTimeout)) tp->stats.rx_length_errors++; + if (status & (RxUnderrun|RxFIFOOver)) tp->stats.rx_fifo_errors++; + if (status & RxOverflow) { + tp->stats.rx_over_errors++; + tp->cur_rx = inw(ioaddr + RxBufAddr) % RX_BUF_LEN; + outw(tp->cur_rx - 16, ioaddr + RxBufPtr); + } + /* Error sources cleared above. */ + } + if (--boguscnt < 0) { + printk(KERN_WARNING"%s: Too much work at interrupt, " + "IntrStatus=0x%4.4x.\n", + dev->name, status); + /* Clear all interrupt sources. */ + outw(0xffff, ioaddr + IntrStatus); + break; + } + } while (1); + + if (rtl8129_debug > 3) + printk(KERN_DEBUG"%s: exiting interrupt, intr_status=%#4.4x.\n", + dev->name, inl(ioaddr + IntrStatus)); + + dev->interrupt = 0; + clear_bit(0, (void*)&tp->in_interrupt); + return; +} + +/* The data sheet doesn't describe the Rx ring at all, so I'm guessing at the + field alignments and semantics. */ +static int +rtl8129_rx(struct device *dev) +{ + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int ioaddr = dev->base_addr; + unsigned char *rx_ring = tp->rx_ring; + u16 cur_rx = tp->cur_rx; + + if (rtl8129_debug > 4) + printk(KERN_DEBUG"%s: In rtl8129_rx(), current %4.4x BufAddr %4.4x," + " free to %4.4x, Cmd %2.2x.\n", + dev->name, cur_rx, inw(ioaddr + RxBufAddr), + inw(ioaddr + RxBufPtr), inb(ioaddr + ChipCmd)); + + while ((inb(ioaddr + ChipCmd) & 1) == 0) { + int ring_offset = cur_rx % RX_BUF_LEN; + u32 rx_status = *(u32*)(rx_ring + ring_offset); + int rx_size = rx_status >> 16; + + if (rtl8129_debug > 4) { + int i; + printk(KERN_DEBUG"%s: rtl8129_rx() status %4.4x, size %4.4x, cur %4.4x.\n", + dev->name, rx_status, rx_size, cur_rx); + printk(KERN_DEBUG"%s: Frame contents ", dev->name); + for (i = 0; i < 70; i++) + printk(" %2.2x", rx_ring[ring_offset + i]); + printk(".\n"); + } + if (rx_status & RxTooLong) { + if (rtl8129_debug > 0) + printk(KERN_NOTICE"%s: Oversized Ethernet frame, status %4.4x!\n", + dev->name, rx_status); + tp->stats.rx_length_errors++; + } else if (rx_status & + (RxBadSymbol|RxRunt|RxTooLong|RxCRCErr|RxBadAlign)) { + if (rtl8129_debug > 1) + printk(KERN_DEBUG"%s: Ethernet frame had errors," + " status %4.4x.\n", dev->name, rx_status); + tp->stats.rx_errors++; + if (rx_status & (RxBadSymbol|RxBadAlign)) + tp->stats.rx_frame_errors++; + if (rx_status & (RxRunt|RxTooLong)) tp->stats.rx_length_errors++; + if (rx_status & RxCRCErr) tp->stats.rx_crc_errors++; + /* Reset the receiver, based on RealTek recommendation. (Bug?) */ + tp->cur_rx = 0; + outb(CmdTxEnb, ioaddr + ChipCmd); + outb(CmdRxEnb | CmdTxEnb, ioaddr + ChipCmd); + outl((RX_FIFO_THRESH << 13) | (RX_BUF_LEN_IDX << 11) | + (RX_DMA_BURST<<8), ioaddr + RxConfig); + } else { + /* Malloc up new buffer, compatible with net-2e. */ + /* Omit the four octet CRC from the length. */ + struct sk_buff *skb; + + skb = dev_alloc_skb(rx_size + 2); + if (skb == NULL) { + printk(KERN_WARNING"%s: Memory squeeze, deferring packet.\n", + dev->name); + /* We should check that some rx space is free. + If not, free one and mark stats->rx_dropped++. */ + tp->stats.rx_dropped++; + break; + } + skb->dev = dev; + skb_reserve(skb, 2); /* 16 byte align the IP fields. */ + if (ring_offset+rx_size+4 > RX_BUF_LEN) { + int semi_count = RX_BUF_LEN - ring_offset - 4; + memcpy(skb_put(skb, semi_count), &rx_ring[ring_offset + 4], + semi_count); + memcpy(skb_put(skb, rx_size-semi_count), rx_ring, + rx_size-semi_count); + if (rtl8129_debug > 4) { + int i; + printk(KERN_DEBUG"%s: Frame wrap @%d", + dev->name, semi_count); + for (i = 0; i < 16; i++) + printk(" %2.2x", rx_ring[i]); + printk(".\n"); + memset(rx_ring, 0xcc, 16); + } + } else + memcpy(skb_put(skb, rx_size), &rx_ring[ring_offset + 4], + rx_size); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); +#if LINUX_VERSION_CODE > 0x20119 + tp->stats.rx_bytes += rx_size; +#endif + tp->stats.rx_packets++; + } + + cur_rx += rx_size + 4; + cur_rx = (cur_rx + 3) & ~3; + outw(cur_rx - 16, ioaddr + RxBufPtr); + } + if (rtl8129_debug > 4) + printk(KERN_DEBUG"%s: Done rtl8129_rx(), current %4.4x BufAddr %4.4x," + " free to %4.4x, Cmd %2.2x.\n", + dev->name, cur_rx, inw(ioaddr + RxBufAddr), + inw(ioaddr + RxBufPtr), inb(ioaddr + ChipCmd)); + tp->cur_rx = cur_rx; + return 0; +} + +static int +rtl8129_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (rtl8129_debug > 1) + printk(KERN_DEBUG"%s: Shutting down ethercard, status was 0x%4.4x.\n", + dev->name, inw(ioaddr + IntrStatus)); + + /* Disable interrupts by clearing the interrupt mask. */ + outw(0x0000, ioaddr + IntrMask); + + /* Stop the chip's Tx and Rx DMA processes. */ + outb(0x00, ioaddr + ChipCmd); + + /* Update the error counts. */ + tp->stats.rx_missed_errors += inl(ioaddr + RxMissed); + outl(0, ioaddr + RxMissed); + + del_timer(&tp->timer); + + free_irq(dev->irq, dev); + + for (i = 0; i < NUM_TX_DESC; i++) { + if (tp->tx_skbuff[i]) + dev_kfree_skb(tp->tx_skbuff[i], FREE_WRITE); + tp->tx_skbuff[i] = 0; + } + kfree(tp->rx_ring); + kfree(tp->tx_bufs); + + /* Green! Put the chip in low-power mode. */ + outb(0xC0, ioaddr + Cfg9346); + outb(0x03, ioaddr + Config1); + outb('H', ioaddr + HltClk); /* 'R' would leave the clock running. */ + + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics * +rtl8129_get_stats(struct device *dev) +{ + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + int ioaddr = dev->base_addr; + + if (dev->start) { + tp->stats.rx_missed_errors += inl(ioaddr + RxMissed); + outl(0, ioaddr + RxMissed); + } + + return &tp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + Note that we only use exclusion around actually queueing the + new frame, not around filling tp->setup_frame. This is non-deterministic + when re-entered but still correct. */ + +/* The little-endian AUTODIN II ethernet CRC calculation. + N.B. Do not use for bulk data, use a table-based routine instead. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial_le = 0xedb88320U; +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + +static void set_rx_mode(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv; + unsigned char mc_filter[8]; /* Multicast hash filter */ + int i; + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + /* Unconditionally log net taps. */ + printk(KERN_NOTICE"%s: Promiscuous mode enabled.\n", dev->name); + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(0x0F, ioaddr + RxConfig); + } else if ((dev->mc_count > 1000) || (dev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(0x0E, ioaddr + RxConfig); + } else if (dev->mc_count == 0) { + outb(0x0A, ioaddr + RxConfig); + return; + } else { + struct dev_mc_list *mclist; + + memset(mc_filter, 0, sizeof(mc_filter)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + set_bit(ether_crc_le(ETH_ALEN, mclist->dmi_addr) & 0x3f, + mc_filter); + } + /* ToDo: perhaps we need to stop the Tx and Rx process here? */ + if (memcmp(mc_filter, tp->mc_filter, sizeof(mc_filter))) { + for (i = 0; i < 2; i++) + outl(((u32 *)mc_filter)[i], ioaddr + MAR0 + i*4); + memcpy(tp->mc_filter, mc_filter, sizeof(mc_filter)); + } + if (rtl8129_debug > 3) + printk(KERN_DEBUG"%s: set_rx_mode(%4.4x) done -- Rx config %8.8x.\n", + dev->name, dev->flags, inl(ioaddr + RxConfig)); + return; +} + +#ifdef MODULE + +/* An additional parameter that may be passed in... */ +static int debug = -1; + +int +init_module(void) +{ + int cards_found; + + if (debug >= 0) + rtl8129_debug = debug; + + root_rtl8129_dev = NULL; + cards_found = rtl8139_probe(0); + + return cards_found ? 0 : -ENODEV; +} + +void +cleanup_module(void) +{ + struct device *next_dev; + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_rtl8129_dev) { + next_dev = ((struct rtl8129_private *)root_rtl8129_dev->priv)->next_module; + unregister_netdev(root_rtl8129_dev); + release_region(root_rtl8129_dev->base_addr, RTL8129_TOTAL_SIZE); + kfree(root_rtl8129_dev); + root_rtl8129_dev = next_dev; + } +} + +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c rtl8139.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * SMP-compile-command: "gcc -D__SMP__ -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c rtl8139.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/seeq8005.c b/linux/src/drivers/net/seeq8005.c new file mode 100644 index 00000000..c4d48521 --- /dev/null +++ b/linux/src/drivers/net/seeq8005.c @@ -0,0 +1,760 @@ +/* seeq8005.c: A network driver for linux. */ +/* + Based on skeleton.c, + Written 1993-94 by Donald Becker. + See the skeleton.c file for further copyright information. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as hamish@zot.apana.org.au + + This file is a network device driver for the SEEQ 8005 chipset and + the Linux operating system. + +*/ + +static const char *version = + "seeq8005.c:v1.00 8/07/95 Hamish Coleman (hamish@zot.apana.org.au)\n"; + +/* + Sources: + SEEQ 8005 databook + + Version history: + 1.00 Public release. cosmetic changes (no warnings now) + 0.68 Turning per- packet,interrupt debug messages off - testing for release. + 0.67 timing problems/bad buffer reads seem to be fixed now + 0.63 *!@$ protocol=eth_type_trans -- now packets flow + 0.56 Send working + 0.48 Receive working +*/ + +#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/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include "seeq8005.h" + +/* First, a few definitions that the brave might change. */ +/* A zero-terminated list of I/O addresses to be probed. */ +static unsigned int seeq8005_portlist[] = + { 0x300, 0x320, 0x340, 0x360, 0}; + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 1 +#endif +static unsigned int net_debug = NET_DEBUG; + +/* Information that need to be kept for each board. */ +struct net_local { + struct enet_statistics stats; + unsigned short receive_ptr; /* What address in packet memory do we expect a recv_pkt_header? */ + long open_time; /* Useless example local info. */ +}; + +/* The station (ethernet) address prefix, used for IDing the board. */ +#define SA_ADDR0 0x00 +#define SA_ADDR1 0x80 +#define SA_ADDR2 0x4b + +/* Index to functions, as function prototypes. */ + +extern int seeq8005_probe(struct device *dev); + +static int seeq8005_probe1(struct device *dev, int ioaddr); +static int seeq8005_open(struct device *dev); +static int seeq8005_send_packet(struct sk_buff *skb, struct device *dev); +static void seeq8005_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void seeq8005_rx(struct device *dev); +static int seeq8005_close(struct device *dev); +static struct enet_statistics *seeq8005_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev); + +/* Example routines you must write ;->. */ +#define tx_done(dev) (inw(SEEQ_STATUS) & SEEQSTAT_TX_ON) +extern void hardware_send_packet(struct device *dev, char *buf, int length); +extern void seeq8005_init(struct device *dev, int startp); +inline void wait_for_buffer(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, allocate space for the device and return success + (detachable devices only). + */ +#ifdef HAVE_DEVLIST +/* Support for a alternate probe manager, which will eliminate the + boilerplate below. */ +struct netdev_entry seeq8005_drv = +{"seeq8005", seeq8005_probe1, SEEQ8005_IO_EXTENT, seeq8005_portlist}; +#else +int +seeq8005_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return seeq8005_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; seeq8005_portlist[i]; i++) { + int ioaddr = seeq8005_portlist[i]; + if (check_region(ioaddr, SEEQ8005_IO_EXTENT)) + continue; + if (seeq8005_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +/* This is the real probe routine. Linux has a history of friendly device + probes on the ISA bus. A good device probes avoids doing writes, and + verifies that the correct device exists and functions. */ + +static int seeq8005_probe1(struct device *dev, int ioaddr) +{ + static unsigned version_printed = 0; + int i,j; + unsigned char SA_prom[32]; + int old_cfg1; + int old_cfg2; + int old_stat; + int old_dmaar; + int old_rear; + + if (net_debug>1) + printk("seeq8005: probing at 0x%x\n",ioaddr); + + old_stat = inw(SEEQ_STATUS); /* read status register */ + if (old_stat == 0xffff) + return ENODEV; /* assume that 0xffff == no device */ + if ( (old_stat & 0x1800) != 0x1800 ) { /* assume that unused bits are 1, as my manual says */ + if (net_debug>1) { + printk("seeq8005: reserved stat bits != 0x1800\n"); + printk(" == 0x%04x\n",old_stat); + } + return ENODEV; + } + + old_rear = inw(SEEQ_REA); + if (old_rear == 0xffff) { + outw(0,SEEQ_REA); + if (inw(SEEQ_REA) == 0xffff) { /* assume that 0xffff == no device */ + return ENODEV; + } + } else if ((old_rear & 0xff00) != 0xff00) { /* assume that unused bits are 1 */ + if (net_debug>1) { + printk("seeq8005: unused rear bits != 0xff00\n"); + printk(" == 0x%04x\n",old_rear); + } + return ENODEV; + } + + old_cfg2 = inw(SEEQ_CFG2); /* read CFG2 register */ + old_cfg1 = inw(SEEQ_CFG1); + old_dmaar = inw(SEEQ_DMAAR); + + if (net_debug>4) { + printk("seeq8005: stat = 0x%04x\n",old_stat); + printk("seeq8005: cfg1 = 0x%04x\n",old_cfg1); + printk("seeq8005: cfg2 = 0x%04x\n",old_cfg2); + printk("seeq8005: raer = 0x%04x\n",old_rear); + printk("seeq8005: dmaar= 0x%04x\n",old_dmaar); + } + + outw( SEEQCMD_FIFO_WRITE | SEEQCMD_SET_ALL_OFF, SEEQ_CMD); /* setup for reading PROM */ + outw( 0, SEEQ_DMAAR); /* set starting PROM address */ + outw( SEEQCFG1_BUFFER_PROM, SEEQ_CFG1); /* set buffer to look at PROM */ + + + j=0; + for(i=0; i <32; i++) { + j+= SA_prom[i] = inw(SEEQ_BUFFER) & 0xff; + } + +#if 0 + /* untested because I only have the one card */ + if ( (j&0xff) != 0 ) { /* checksum appears to be 8bit = 0 */ + if (net_debug>1) { /* check this before deciding that we have a card */ + printk("seeq8005: prom sum error\n"); + } + outw( old_stat, SEEQ_STATUS); + outw( old_dmaar, SEEQ_DMAAR); + outw( old_cfg1, SEEQ_CFG1); + return ENODEV; + } +#endif + + outw( SEEQCFG2_RESET, SEEQ_CFG2); /* reset the card */ + SLOW_DOWN_IO; /* have to wait 4us after a reset - should be fixed */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + SLOW_DOWN_IO; + outw( SEEQCMD_SET_ALL_OFF, SEEQ_CMD); + + if (net_debug) { + printk("seeq8005: prom sum = 0x%08x\n",j); + for(j=0; j<32; j+=16) { + printk("seeq8005: prom %02x: ",j); + for(i=0;i<16;i++) { + printk("%02x ",SA_prom[j|i]); + } + printk(" "); + for(i=0;i<16;i++) { + if ((SA_prom[j|i]>31)&&(SA_prom[j|i]<127)) { + printk("%c", SA_prom[j|i]); + } else { + printk(" "); + } + } + printk("\n"); + } + } + +#if 0 + /* + * testing the packet buffer memory doesn't work yet + * but all other buffer accesses do + * - fixing is not a priority + */ + if (net_debug>1) { /* test packet buffer memory */ + printk("seeq8005: testing packet buffer ... "); + outw( SEEQCFG1_BUFFER_BUFFER, SEEQ_CFG1); + outw( SEEQCMD_FIFO_WRITE | SEEQCMD_SET_ALL_OFF, SEEQ_CMD); + outw( 0 , SEEQ_DMAAR); + for(i=0;i<32768;i++) { + outw(0x5a5a, SEEQ_BUFFER); + } + j=jiffies+HZ; + while ( ((inw(SEEQ_STATUS) & SEEQSTAT_FIFO_EMPTY) != SEEQSTAT_FIFO_EMPTY) && jiffies < j ) + mb(); + outw( 0 , SEEQ_DMAAR); + while ( ((inw(SEEQ_STATUS) & SEEQSTAT_WINDOW_INT) != SEEQSTAT_WINDOW_INT) && jiffies < j+HZ) + mb(); + if ( (inw(SEEQ_STATUS) & SEEQSTAT_WINDOW_INT) == SEEQSTAT_WINDOW_INT) + outw( SEEQCMD_WINDOW_INT_ACK | (inw(SEEQ_STATUS)& SEEQCMD_INT_MASK), SEEQ_CMD); + outw( SEEQCMD_FIFO_READ | SEEQCMD_SET_ALL_OFF, SEEQ_CMD); + j=0; + for(i=0;i<32768;i++) { + if (inw(SEEQ_BUFFER) != 0x5a5a) + j++; + } + if (j) { + printk("%i\n",j); + } else { + printk("ok.\n"); + } + } +#endif + + /* Allocate a new 'dev' if needed. */ + if (dev == NULL) + dev = init_etherdev(0, sizeof(struct net_local)); + + if (net_debug && version_printed++ == 0) + printk(version); + + printk("%s: %s found at %#3x, ", dev->name, "seeq8005", ioaddr); + + /* Fill in the 'dev' fields. */ + dev->base_addr = ioaddr; + + /* Retrieve and print the ethernet address. */ + for (i = 0; i < 6; i++) + printk(" %2.2x", dev->dev_addr[i] = SA_prom[i+6]); + + if (dev->irq == 0xff) + ; /* Do nothing: a user-level program will set it. */ + else if (dev->irq < 2) { /* "Auto-IRQ" */ + autoirq_setup(0); + + outw( SEEQCMD_RX_INT_EN | SEEQCMD_SET_RX_ON | SEEQCMD_SET_RX_OFF, SEEQ_CMD ); + + dev->irq = autoirq_report(0); + + if (net_debug >= 2) + printk(" autoirq is %d\n", dev->irq); + } else if (dev->irq == 2) + /* Fixup for users that don't know that IRQ 2 is really IRQ 9, + * or don't know which one to set. + */ + dev->irq = 9; + +#if 0 + { + int irqval = request_irq(dev->irq, &seeq8005_interrupt, 0, "seeq8005", NULL); + if (irqval) { + printk ("%s: unable to get IRQ %d (irqval=%d).\n", dev->name, + dev->irq, irqval); + return EAGAIN; + } + } +#endif + + /* Grab the region so we can find another board if autoIRQ fails. */ + request_region(ioaddr, SEEQ8005_IO_EXTENT,"seeq8005"); + + /* 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)); + + dev->open = seeq8005_open; + dev->stop = seeq8005_close; + dev->hard_start_xmit = seeq8005_send_packet; + dev->get_stats = seeq8005_get_stats; + dev->set_multicast_list = &set_multicast_list; + + /* Fill in the fields of the device structure with ethernet values. */ + ether_setup(dev); + + dev->flags &= ~IFF_MULTICAST; + + return 0; +} + + +/* Open/initialize the board. This is called (in the current kernel) + sometime after booting when the 'ifconfig' program is run. + + This routine should set everything up anew at each open, even + registers that "should" only need to be set once at boot, so that + there is non-reboot way to recover if something goes wrong. + */ +static int +seeq8005_open(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + + { + int irqval = request_irq(dev->irq, &seeq8005_interrupt, 0, "seeq8005", NULL); + if (irqval) { + printk ("%s: unable to get IRQ %d (irqval=%d).\n", dev->name, + dev->irq, irqval); + return EAGAIN; + } + } + irq2dev_map[dev->irq] = dev; + + /* Reset the hardware here. Don't forget to set the station address. */ + seeq8005_init(dev, 1); + + lp->open_time = jiffies; + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + return 0; +} + +static int +seeq8005_send_packet(struct sk_buff *skb, struct device *dev) +{ + int ioaddr = dev->base_addr; + + 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; + printk("%s: transmit timed out, %s?\n", dev->name, + tx_done(dev) ? "IRQ conflict" : "network cable problem"); + /* Try to restart the adaptor. */ + seeq8005_init(dev, 1); + 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) { + dev_tint(dev); + return 0; + } + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + 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; + + hardware_send_packet(dev, buf, length); + dev->trans_start = jiffies; + } + dev_kfree_skb (skb, FREE_WRITE); + + /* You might need to clean up and record Tx statistics here. */ + + return 0; +} + +/* The typical workload of the driver: + Handle the network interface interrupts. */ +static void +seeq8005_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct net_local *lp; + int ioaddr, status, boguscount = 0; + + if (dev == NULL) { + printk ("net_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + if (dev->interrupt) + printk ("%s: Re-entering the interrupt handler.\n", dev->name); + dev->interrupt = 1; + + ioaddr = dev->base_addr; + lp = (struct net_local *)dev->priv; + + status = inw(SEEQ_STATUS); + do { + if (net_debug >2) { + printk("%s: int, status=0x%04x\n",dev->name,status); + } + + if (status & SEEQSTAT_WINDOW_INT) { + outw( SEEQCMD_WINDOW_INT_ACK | (status & SEEQCMD_INT_MASK), SEEQ_CMD); + if (net_debug) { + printk("%s: window int!\n",dev->name); + } + } + if (status & SEEQSTAT_TX_INT) { + outw( SEEQCMD_TX_INT_ACK | (status & SEEQCMD_INT_MASK), SEEQ_CMD); + lp->stats.tx_packets++; + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } + if (status & SEEQSTAT_RX_INT) { + /* Got a packet(s). */ + seeq8005_rx(dev); + } + status = inw(SEEQ_STATUS); + } while ( (++boguscount < 10) && (status & SEEQSTAT_ANY_INT)) ; + + if(net_debug>2) { + printk("%s: eoi\n",dev->name); + } + dev->interrupt = 0; + return; +} + +/* We have a good packet(s), get it/them out of the buffers. */ +static void +seeq8005_rx(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int boguscount = 10; + int pkt_hdr; + int ioaddr = dev->base_addr; + + do { + int next_packet; + int pkt_len; + int i; + int status; + + status = inw(SEEQ_STATUS); + outw( lp->receive_ptr, SEEQ_DMAAR); + outw(SEEQCMD_FIFO_READ | SEEQCMD_RX_INT_ACK | (status & SEEQCMD_INT_MASK), SEEQ_CMD); + wait_for_buffer(dev); + next_packet = ntohs(inw(SEEQ_BUFFER)); + pkt_hdr = inw(SEEQ_BUFFER); + + if (net_debug>2) { + printk("%s: 0x%04x recv next=0x%04x, hdr=0x%04x\n",dev->name,lp->receive_ptr,next_packet,pkt_hdr); + } + + if ((next_packet == 0) || ((pkt_hdr & SEEQPKTH_CHAIN)==0)) { /* Read all the frames? */ + return; /* Done for now */ + } + + if ((pkt_hdr & SEEQPKTS_DONE)==0) + break; + + if (next_packet < lp->receive_ptr) { + pkt_len = (next_packet + 0x10000 - ((DEFAULT_TEA+1)<<8)) - lp->receive_ptr - 4; + } else { + pkt_len = next_packet - lp->receive_ptr - 4; + } + + if (next_packet < ((DEFAULT_TEA+1)<<8)) { /* is the next_packet address sane? */ + printk("%s: recv packet ring corrupt, resetting board\n",dev->name); + seeq8005_init(dev,1); + return; + } + + lp->receive_ptr = next_packet; + + if (net_debug>2) { + printk("%s: recv len=0x%04x\n",dev->name,pkt_len); + } + + if (pkt_hdr & SEEQPKTS_ANY_ERROR) { /* There was an error. */ + lp->stats.rx_errors++; + if (pkt_hdr & SEEQPKTS_SHORT) lp->stats.rx_frame_errors++; + if (pkt_hdr & SEEQPKTS_DRIB) lp->stats.rx_frame_errors++; + if (pkt_hdr & SEEQPKTS_OVERSIZE) lp->stats.rx_over_errors++; + if (pkt_hdr & SEEQPKTS_CRC_ERR) lp->stats.rx_crc_errors++; + /* skip over this packet */ + outw( SEEQCMD_FIFO_WRITE | SEEQCMD_DMA_INT_ACK | (status & SEEQCMD_INT_MASK), SEEQ_CMD); + outw( (lp->receive_ptr & 0xff00)>>8, SEEQ_REA); + } else { + /* Malloc up new buffer. */ + struct sk_buff *skb; + unsigned char *buf; + + skb = dev_alloc_skb(pkt_len); + if (skb == NULL) { + printk("%s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + break; + } + skb->dev = dev; + skb_reserve(skb, 2); /* align data on 16 byte */ + buf = skb_put(skb,pkt_len); + + insw(SEEQ_BUFFER, buf, (pkt_len + 1) >> 1); + + if (net_debug>2) { + char * p = buf; + printk("%s: recv ",dev->name); + for(i=0;i<14;i++) { + printk("%02x ",*(p++)&0xff); + } + printk("\n"); + } + + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + } while ((--boguscount) && (pkt_hdr & SEEQPKTH_CHAIN)); + + /* If any worth-while packets have been received, netif_rx() + has done a mark_bh(NET_BH) for us and will work on them + when we get to the bottom-half routine. */ + return; +} + +/* The inverse routine to net_open(). */ +static int +seeq8005_close(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + + lp->open_time = 0; + + dev->tbusy = 1; + dev->start = 0; + + /* Flush the Tx and disable Rx here. */ + outw( SEEQCMD_SET_ALL_OFF, SEEQ_CMD); + + free_irq(dev->irq, NULL); + + irq2dev_map[dev->irq] = 0; + + /* Update the statistics here. */ + + return 0; + +} + +/* Get the current statistics. This may be called with the card open or + closed. */ +static struct enet_statistics * +seeq8005_get_stats(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + + return &lp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + num_addrs == -1 Promiscuous mode, receive all packets + num_addrs == 0 Normal mode, clear multicast list + num_addrs > 0 Multicast mode, receive normal and MC packets, and do + best-effort filtering. + */ +static void +set_multicast_list(struct device *dev) +{ +/* + * I _could_ do up to 6 addresses here, but won't (yet?) + */ + +#if 0 + int ioaddr = dev->base_addr; +/* + * hmm, not even sure if my matching works _anyway_ - seem to be receiving + * _everything_ . . . + */ + + if (num_addrs) { /* Enable promiscuous mode */ + outw( (inw(SEEQ_CFG1) & ~SEEQCFG1_MATCH_MASK)| SEEQCFG1_MATCH_ALL, SEEQ_CFG1); + dev->flags|=IFF_PROMISC; + } else { /* Disable promiscuous mode, use normal mode */ + outw( (inw(SEEQ_CFG1) & ~SEEQCFG1_MATCH_MASK)| SEEQCFG1_MATCH_BROAD, SEEQ_CFG1); + } +#endif +} + +void seeq8005_init(struct device *dev, int startp) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + outw(SEEQCFG2_RESET, SEEQ_CFG2); /* reset device */ + SLOW_DOWN_IO; /* have to wait 4us after a reset - should be fixed */ + SLOW_DOWN_IO; + SLOW_DOWN_IO; + SLOW_DOWN_IO; + + outw( SEEQCMD_FIFO_WRITE | SEEQCMD_SET_ALL_OFF, SEEQ_CMD); + outw( 0, SEEQ_DMAAR); /* load start address into both low and high byte */ +/* wait_for_buffer(dev); */ /* I think that you only need a wait for memory buffer */ + outw( SEEQCFG1_BUFFER_MAC0, SEEQ_CFG1); + + for(i=0;i<6;i++) { /* set Station address */ + outb(dev->dev_addr[i], SEEQ_BUFFER); + SLOW_DOWN_IO; + } + + outw( SEEQCFG1_BUFFER_TEA, SEEQ_CFG1); /* set xmit end area pointer to 16K */ + outb( DEFAULT_TEA, SEEQ_BUFFER); /* this gives us 16K of send buffer and 48K of recv buffer */ + + lp->receive_ptr = (DEFAULT_TEA+1)<<8; /* so we can find our packet_header */ + outw( lp->receive_ptr, SEEQ_RPR); /* Receive Pointer Register is set to recv buffer memory */ + + outw( 0x00ff, SEEQ_REA); /* Receive Area End */ + + if (net_debug>4) { + printk("%s: SA0 = ",dev->name); + + outw( SEEQCMD_FIFO_READ | SEEQCMD_SET_ALL_OFF, SEEQ_CMD); + outw( 0, SEEQ_DMAAR); + outw( SEEQCFG1_BUFFER_MAC0, SEEQ_CFG1); + + for(i=0;i<6;i++) { + printk("%02x ",inb(SEEQ_BUFFER)); + } + printk("\n"); + } + + outw( SEEQCFG1_MAC0_EN | SEEQCFG1_MATCH_BROAD | SEEQCFG1_BUFFER_BUFFER, SEEQ_CFG1); + outw( SEEQCFG2_AUTO_REA | SEEQCFG2_CTRLO, SEEQ_CFG2); + outw( SEEQCMD_SET_RX_ON | SEEQCMD_TX_INT_EN | SEEQCMD_RX_INT_EN, SEEQ_CMD); + + if (net_debug>4) { + int old_cfg1; + old_cfg1 = inw(SEEQ_CFG1); + printk("%s: stat = 0x%04x\n",dev->name,inw(SEEQ_STATUS)); + printk("%s: cfg1 = 0x%04x\n",dev->name,old_cfg1); + printk("%s: cfg2 = 0x%04x\n",dev->name,inw(SEEQ_CFG2)); + printk("%s: raer = 0x%04x\n",dev->name,inw(SEEQ_REA)); + printk("%s: dmaar= 0x%04x\n",dev->name,inw(SEEQ_DMAAR)); + + } +} + + +void hardware_send_packet(struct device * dev, char *buf, int length) +{ + int ioaddr = dev->base_addr; + int status = inw(SEEQ_STATUS); + int transmit_ptr = 0; + int tmp; + + if (net_debug>4) { + printk("%s: send 0x%04x\n",dev->name,length); + } + + /* Set FIFO to writemode and set packet-buffer address */ + outw( SEEQCMD_FIFO_WRITE | (status & SEEQCMD_INT_MASK), SEEQ_CMD); + outw( transmit_ptr, SEEQ_DMAAR); + + /* output SEEQ Packet header barfage */ + outw( htons(length + 4), SEEQ_BUFFER); + outw( SEEQPKTH_XMIT | SEEQPKTH_DATA_FOLLOWS | SEEQPKTH_XMIT_INT_EN, SEEQ_BUFFER ); + + /* blat the buffer */ + outsw( SEEQ_BUFFER, buf, (length +1) >> 1); + /* paranoia !! */ + outw( 0, SEEQ_BUFFER); + outw( 0, SEEQ_BUFFER); + + /* set address of start of transmit chain */ + outw( transmit_ptr, SEEQ_TPR); + + /* drain FIFO */ + tmp = jiffies; + while ( (((status=inw(SEEQ_STATUS)) & SEEQSTAT_FIFO_EMPTY) == 0) && (jiffies < tmp + HZ)) + mb(); + + /* doit ! */ + outw( SEEQCMD_WINDOW_INT_ACK | SEEQCMD_SET_TX_ON | (status & SEEQCMD_INT_MASK), SEEQ_CMD); + +} + + +/* + * wait_for_buffer + * + * This routine waits for the SEEQ chip to assert that the FIFO is ready + * by checking for a window interrupt, and then clearing it + */ +inline void wait_for_buffer(struct device * dev) +{ + int ioaddr = dev->base_addr; + int tmp; + int status; + + tmp = jiffies + HZ; + while ( ( ((status=inw(SEEQ_STATUS)) & SEEQSTAT_WINDOW_INT) != SEEQSTAT_WINDOW_INT) && jiffies < tmp) + mb(); + + if ( (status & SEEQSTAT_WINDOW_INT) == SEEQSTAT_WINDOW_INT) + outw( SEEQCMD_WINDOW_INT_ACK | (status & SEEQCMD_INT_MASK), SEEQ_CMD); +} + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c skeleton.c" + * version-control: t + * kept-new-versions: 5 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/seeq8005.h b/linux/src/drivers/net/seeq8005.h new file mode 100644 index 00000000..809ba6dc --- /dev/null +++ b/linux/src/drivers/net/seeq8005.h @@ -0,0 +1,156 @@ +/* + * defines, etc for the seeq8005 + */ + +/* + * This file is distributed under GPL. + * + * This style and layout of this file is also copied + * from many of the other linux network device drivers. + */ + +/* The number of low I/O ports used by the ethercard. */ +#define SEEQ8005_IO_EXTENT 16 + +#define SEEQ_B (ioaddr) + +#define SEEQ_CMD (SEEQ_B) /* Write only */ +#define SEEQ_STATUS (SEEQ_B) /* Read only */ +#define SEEQ_CFG1 (SEEQ_B + 2) +#define SEEQ_CFG2 (SEEQ_B + 4) +#define SEEQ_REA (SEEQ_B + 6) /* Receive End Area Register */ +#define SEEQ_RPR (SEEQ_B + 10) /* Receive Pointer Register */ +#define SEEQ_TPR (SEEQ_B + 12) /* Transmit Pointer Register */ +#define SEEQ_DMAAR (SEEQ_B + 14) /* DMA Address Register */ +#define SEEQ_BUFFER (SEEQ_B + 8) /* Buffer Window Register */ + +#define DEFAULT_TEA (0x3f) + +#define SEEQCMD_DMA_INT_EN (0x0001) /* DMA Interrupt Enable */ +#define SEEQCMD_RX_INT_EN (0x0002) /* Receive Interrupt Enable */ +#define SEEQCMD_TX_INT_EN (0x0004) /* Transmit Interrupt Enable */ +#define SEEQCMD_WINDOW_INT_EN (0x0008) /* What the hell is this for?? */ +#define SEEQCMD_INT_MASK (0x000f) + +#define SEEQCMD_DMA_INT_ACK (0x0010) /* DMA ack */ +#define SEEQCMD_RX_INT_ACK (0x0020) +#define SEEQCMD_TX_INT_ACK (0x0040) +#define SEEQCMD_WINDOW_INT_ACK (0x0080) +#define SEEQCMD_ACK_ALL (0x00f0) + +#define SEEQCMD_SET_DMA_ON (0x0100) /* Enables DMA Request logic */ +#define SEEQCMD_SET_RX_ON (0x0200) /* Enables Packet RX */ +#define SEEQCMD_SET_TX_ON (0x0400) /* Starts TX run */ +#define SEEQCMD_SET_DMA_OFF (0x0800) +#define SEEQCMD_SET_RX_OFF (0x1000) +#define SEEQCMD_SET_TX_OFF (0x2000) +#define SEEQCMD_SET_ALL_OFF (0x3800) /* set all logic off */ + +#define SEEQCMD_FIFO_READ (0x4000) /* Set FIFO to read mode (read from Buffer) */ +#define SEEQCMD_FIFO_WRITE (0x8000) /* Set FIFO to write mode */ + +#define SEEQSTAT_DMA_INT_EN (0x0001) /* Status of interrupt enable */ +#define SEEQSTAT_RX_INT_EN (0x0002) +#define SEEQSTAT_TX_INT_EN (0x0004) +#define SEEQSTAT_WINDOW_INT_EN (0x0008) + +#define SEEQSTAT_DMA_INT (0x0010) /* Interrupt flagged */ +#define SEEQSTAT_RX_INT (0x0020) +#define SEEQSTAT_TX_INT (0x0040) +#define SEEQSTAT_WINDOW_INT (0x0080) +#define SEEQSTAT_ANY_INT (0x00f0) + +#define SEEQSTAT_DMA_ON (0x0100) /* DMA logic on */ +#define SEEQSTAT_RX_ON (0x0200) /* Packet RX on */ +#define SEEQSTAT_TX_ON (0x0400) /* TX running */ + +#define SEEQSTAT_FIFO_FULL (0x2000) +#define SEEQSTAT_FIFO_EMPTY (0x4000) +#define SEEQSTAT_FIFO_DIR (0x8000) /* 1=read, 0=write */ + +#define SEEQCFG1_BUFFER_MASK (0x000f) /* define what maps into the BUFFER register */ +#define SEEQCFG1_BUFFER_MAC0 (0x0000) /* MAC station addresses 0-5 */ +#define SEEQCFG1_BUFFER_MAC1 (0x0001) +#define SEEQCFG1_BUFFER_MAC2 (0x0002) +#define SEEQCFG1_BUFFER_MAC3 (0x0003) +#define SEEQCFG1_BUFFER_MAC4 (0x0004) +#define SEEQCFG1_BUFFER_MAC5 (0x0005) +#define SEEQCFG1_BUFFER_PROM (0x0006) /* The Address/CFG PROM */ +#define SEEQCFG1_BUFFER_TEA (0x0007) /* Transmit end area */ +#define SEEQCFG1_BUFFER_BUFFER (0x0008) /* Packet buffer memory */ +#define SEEQCFG1_BUFFER_INT_VEC (0x0009) /* Interrupt Vector */ + +#define SEEQCFG1_DMA_INTVL_MASK (0x0030) +#define SEEQCFG1_DMA_CONT (0x0000) +#define SEEQCFG1_DMA_800ns (0x0010) +#define SEEQCFG1_DMA_1600ns (0x0020) +#define SEEQCFG1_DMA_3200ns (0x0030) + +#define SEEQCFG1_DMA_LEN_MASK (0x00c0) +#define SEEQCFG1_DMA_LEN1 (0x0000) +#define SEEQCFG1_DMA_LEN2 (0x0040) +#define SEEQCFG1_DMA_LEN4 (0x0080) +#define SEEQCFG1_DMA_LEN8 (0x00c0) + +#define SEEQCFG1_MAC_MASK (0x3f00) /* Dis/enable bits for MAC addresses */ +#define SEEQCFG1_MAC0_EN (0x0100) +#define SEEQCFG1_MAC1_EN (0x0200) +#define SEEQCFG1_MAC2_EN (0x0400) +#define SEEQCFG1_MAC3_EN (0x0800) +#define SEEQCFG1_MAC4_EN (0x1000) +#define SEEQCFG1_MAC5_EN (0x2000) + +#define SEEQCFG1_MATCH_MASK (0xc000) /* Packet matching logic cfg bits */ +#define SEEQCFG1_MATCH_SPECIFIC (0x0000) /* only matching MAC addresses */ +#define SEEQCFG1_MATCH_BROAD (0x4000) /* matching and broadcast addresses */ +#define SEEQCFG1_MATCH_MULTI (0x8000) /* matching, broadcast and multicast */ +#define SEEQCFG1_MATCH_ALL (0xc000) /* Promiscuous mode */ + +#define SEEQCFG1_DEFAULT (SEEQCFG1_BUFFER_BUFFER | SEEQCFG1_MAC0_EN | SEEQCFG1_MATCH_BROAD) + +#define SEEQCFG2_BYTE_SWAP (0x0001) /* 0=Intel byte-order */ +#define SEEQCFG2_AUTO_REA (0x0002) /* if set, Receive End Area will be updated when reading from Buffer */ + +#define SEEQCFG2_CRC_ERR_EN (0x0008) /* enables receiving of packets with CRC errors */ +#define SEEQCFG2_DRIBBLE_EN (0x0010) /* enables receiving of non-aligned packets */ +#define SEEQCFG2_SHORT_EN (0x0020) /* enables receiving of short packets */ + +#define SEEQCFG2_SLOTSEL (0x0040) /* 0= standard IEEE802.3, 1= smaller,faster, non-standard */ +#define SEEQCFG2_NO_PREAM (0x0080) /* 1= user supplies Xmit preamble bytes */ +#define SEEQCFG2_ADDR_LEN (0x0100) /* 1= 2byte addresses */ +#define SEEQCFG2_REC_CRC (0x0200) /* 0= received packets will have CRC stripped from them */ +#define SEEQCFG2_XMIT_NO_CRC (0x0400) /* don't xmit CRC with each packet (user supplies it) */ +#define SEEQCFG2_LOOPBACK (0x0800) +#define SEEQCFG2_CTRLO (0x1000) +#define SEEQCFG2_RESET (0x8000) /* software Hard-reset bit */ + +struct seeq_pkt_hdr { + unsigned short next; /* address of next packet header */ + unsigned char babble_int:1, /* enable int on >1514 byte packet */ + coll_int:1, /* enable int on collision */ + coll_16_int:1, /* enable int on >15 collision */ + xmit_int:1, /* enable int on success (or xmit with <15 collision) */ + unused:1, + data_follows:1, /* if not set, process this as a header and pointer only */ + chain_cont:1, /* if set, more headers in chain only cmd bit valid in recv header */ + xmit_recv:1; /* if set, a xmit packet, else a receive packet.*/ + unsigned char status; +}; + +#define SEEQPKTH_BAB_INT_EN (0x01) /* xmit only */ +#define SEEQPKTH_COL_INT_EN (0x02) /* xmit only */ +#define SEEQPKTH_COL16_INT_EN (0x04) /* xmit only */ +#define SEEQPKTH_XMIT_INT_EN (0x08) /* xmit only */ +#define SEEQPKTH_DATA_FOLLOWS (0x20) /* supposedly in xmit only */ +#define SEEQPKTH_CHAIN (0x40) /* more headers follow */ +#define SEEQPKTH_XMIT (0x80) + +#define SEEQPKTS_BABBLE (0x0100) /* xmit only */ +#define SEEQPKTS_OVERSIZE (0x0100) /* recv only */ +#define SEEQPKTS_COLLISION (0x0200) /* xmit only */ +#define SEEQPKTS_CRC_ERR (0x0200) /* recv only */ +#define SEEQPKTS_COLL16 (0x0400) /* xmit only */ +#define SEEQPKTS_DRIB (0x0400) /* recv only */ +#define SEEQPKTS_SHORT (0x0800) /* recv only */ +#define SEEQPKTS_DONE (0x8000) +#define SEEQPKTS_ANY_ERROR (0x0f00) diff --git a/linux/src/drivers/net/sk_g16.c b/linux/src/drivers/net/sk_g16.c new file mode 100644 index 00000000..13ebb3eb --- /dev/null +++ b/linux/src/drivers/net/sk_g16.c @@ -0,0 +1,2110 @@ +/*- + * Copyright (C) 1994 by PJD Weichmann & SWS Bern, Switzerland + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * + * Module : sk_g16.c + * + * Version : $Revision: 1.1 $ + * + * Author : Patrick J.D. Weichmann + * + * Date Created : 94/05/26 + * Last Updated : $Date: 1999/04/26 05:52:37 $ + * + * Description : Schneider & Koch G16 Ethernet Device Driver for + * Linux Kernel >= 1.1.22 + * Update History : + * +-*/ + +static const char *rcsid = "$Id: sk_g16.c,v 1.1 1999/04/26 05:52:37 tb Exp $"; + +/* + * The Schneider & Koch (SK) G16 Network device driver is based + * on the 'ni6510' driver from Michael Hipp which can be found at + * ftp://sunsite.unc.edu/pub/Linux/system/Network/drivers/nidrivers.tar.gz + * + * Sources: 1) ni6510.c by M. Hipp + * 2) depca.c by D.C. Davies + * 3) skeleton.c by D. Becker + * 4) Am7990 Local Area Network Controller for Ethernet (LANCE), + * AMD, Pub. #05698, June 1989 + * + * Many Thanks for helping me to get things working to: + * + * A. Cox (A.Cox@swansea.ac.uk) + * M. Hipp (mhipp@student.uni-tuebingen.de) + * R. Bolz (Schneider & Koch, Germany) + * + * See README.sk_g16 for details about limitations and bugs for the + * current version. + * + * To Do: + * - Support of SK_G8 and other SK Network Cards. + * - Autoset memory mapped RAM. Check for free memory and then + * configure RAM correctly. + * - SK_close should really set card in to initial state. + * - Test if IRQ 3 is not switched off. Use autoirq() functionality. + * (as in /drivers/net/skeleton.c) + * - Implement Multicast addressing. At minimum something like + * in depca.c. + * - Redo the statistics part. + * - Try to find out if the board is in 8 Bit or 16 Bit slot. + * If in 8 Bit mode don't use IRQ 11. + * - (Try to make it slightly faster.) + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/fcntl.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/bitops.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "sk_g16.h" + +/* + * Schneider & Koch Card Definitions + * ================================= + */ + +#define SK_NAME "SK_G16" + +/* + * SK_G16 Configuration + * -------------------- + */ + +/* + * Abbreviations + * ------------- + * + * RAM - used for the 16KB shared memory + * Boot_ROM, ROM - are used for referencing the BootEPROM + * + * SK_BOOT_ROM and SK_ADDR are symbolic constants used to configure + * the behaviour of the driver and the SK_G16. + * + * ! See sk_g16.install on how to install and configure the driver ! + * + * SK_BOOT_ROM defines if the Boot_ROM should be switched off or not. + * + * SK_ADDR defines the address where the RAM will be mapped into the real + * host memory. + * valid addresses are from 0xa0000 to 0xfc000 in 16Kbyte steps. + */ + +#define SK_BOOT_ROM 1 /* 1=BootROM on 0=off */ + +#define SK_ADDR 0xcc000 + +/* + * In POS3 are bits A14-A19 of the address bus. These bits can be set + * to choose the RAM address. That's why we only can choose the RAM address + * in 16KB steps. + */ + +#define POS_ADDR (rom_addr>>14) /* Do not change this line */ + +/* + * SK_G16 I/O PORT's + IRQ's + Boot_ROM locations + * ---------------------------------------------- + */ + +/* + * As nearly every card has also SK_G16 a specified I/O Port region and + * only a few possible IRQ's. + * In the Installation Guide from Schneider & Koch is listed a possible + * Interrupt IRQ2. IRQ2 is always IRQ9 in boards with two cascaded interrupt + * controllers. So we use in SK_IRQS IRQ9. + */ + +/* Don't touch any of the following #defines. */ + +#define SK_IO_PORTS { 0x100, 0x180, 0x208, 0x220, 0x288, 0x320, 0x328, 0x390, 0 } + +#define SK_IRQS { 3, 5, 9, 11, 0 } + +#define SK_BOOT_ROM_LOCATIONS { 0xc0000, 0xc4000, 0xc8000, 0xcc000, 0xd0000, 0xd4000, 0xd8000, 0xdc000, 0 } + +#define SK_BOOT_ROM_ID { 0x55, 0xaa, 0x10, 0x50, 0x06, 0x33 } + +/* + * SK_G16 POS REGISTERS + * -------------------- + */ + +/* + * SK_G16 has a Programmable Option Select (POS) Register. + * The POS is composed of 8 separate registers (POS0-7) which + * are I/O mapped on an address set by the W1 switch. + * + */ + +#define SK_POS_SIZE 8 /* 8 I/O Ports are used by SK_G16 */ + +#define SK_POS0 ioaddr /* Card-ID Low (R) */ +#define SK_POS1 ioaddr+1 /* Card-ID High (R) */ +#define SK_POS2 ioaddr+2 /* Card-Enable, Boot-ROM Disable (RW) */ +#define SK_POS3 ioaddr+3 /* Base address of RAM */ +#define SK_POS4 ioaddr+4 /* IRQ */ + +/* POS5 - POS7 are unused */ + +/* + * SK_G16 MAC PREFIX + * ----------------- + */ + +/* + * Scheider & Koch manufacturer code (00:00:a5). + * This must be checked, that we are sure it is a SK card. + */ + +#define SK_MAC0 0x00 +#define SK_MAC1 0x00 +#define SK_MAC2 0x5a + +/* + * SK_G16 ID + * --------- + */ + +/* + * If POS0,POS1 contain the following ID, then we know + * at which I/O Port Address we are. + */ + +#define SK_IDLOW 0xfd +#define SK_IDHIGH 0x6a + + +/* + * LANCE POS Bit definitions + * ------------------------- + */ + +#define SK_ROM_RAM_ON (POS2_CARD) +#define SK_ROM_RAM_OFF (POS2_EPROM) +#define SK_ROM_ON (inb(SK_POS2) & POS2_CARD) +#define SK_ROM_OFF (inb(SK_POS2) | POS2_EPROM) +#define SK_RAM_ON (inb(SK_POS2) | POS2_CARD) +#define SK_RAM_OFF (inb(SK_POS2) & POS2_EPROM) + +#define POS2_CARD 0x0001 /* 1 = SK_G16 on 0 = off */ +#define POS2_EPROM 0x0002 /* 1 = Boot EPROM off 0 = on */ + +/* + * SK_G16 Memory mapped Registers + * ------------------------------ + * + */ + +#define SK_IOREG (board->ioreg) /* LANCE data registers. */ +#define SK_PORT (board->port) /* Control, Status register */ +#define SK_IOCOM (board->iocom) /* I/O Command */ + +/* + * SK_G16 Status/Control Register bits + * ----------------------------------- + * + * (C) Controlreg (S) Statusreg + */ + +/* + * Register transfer: 0 = no transfer + * 1 = transferring data between LANCE and I/O reg + */ +#define SK_IORUN 0x20 + +/* + * LANCE interrupt: 0 = LANCE interrupt occurred + * 1 = no LANCE interrupt occurred + */ +#define SK_IRQ 0x10 + +#define SK_RESET 0x08 /* Reset SK_CARD: 0 = RESET 1 = normal */ +#define SK_RW 0x02 /* 0 = write to 1 = read from */ +#define SK_ADR 0x01 /* 0 = REG DataPort 1 = RAP Reg addr port */ + + +#define SK_RREG SK_RW /* Transferdirection to read from lance */ +#define SK_WREG 0 /* Transferdirection to write to lance */ +#define SK_RAP SK_ADR /* Destination Register RAP */ +#define SK_RDATA 0 /* Destination Register REG DataPort */ + +/* + * SK_G16 I/O Command + * ------------------ + */ + +/* + * Any bitcombination sets the internal I/O bit (transfer will start) + * when written to I/O Command + */ + +#define SK_DOIO 0x80 /* Do Transfer */ + +/* + * LANCE RAP (Register Address Port). + * --------------------------------- + */ + +/* + * The LANCE internal registers are selected through the RAP. + * The Registers are: + * + * CSR0 - Status and Control flags + * CSR1 - Low order bits of initialize block (bits 15:00) + * CSR2 - High order bits of initialize block (bits 07:00, 15:08 are reserved) + * CSR3 - Allows redefinition of the Bus Master Interface. + * This register must be set to 0x0002, which means BSWAP = 0, + * ACON = 1, BCON = 0; + * + */ + +#define CSR0 0x00 +#define CSR1 0x01 +#define CSR2 0x02 +#define CSR3 0x03 + +/* + * General Definitions + * =================== + */ + +/* + * Set the number of Tx and Rx buffers, using Log_2(# buffers). + * We have 16KB RAM which can be accessed by the LANCE. In the + * memory are not only the buffers but also the ring descriptors and + * the initialize block. + * Don't change anything unless you really know what you do. + */ + +#define LC_LOG_TX_BUFFERS 1 /* (2 == 2^^1) 2 Transmit buffers */ +#define LC_LOG_RX_BUFFERS 3 /* (8 == 2^^3) 8 Receive buffers */ + +/* Descriptor ring sizes */ + +#define TMDNUM (1 << (LC_LOG_TX_BUFFERS)) /* 2 Transmit descriptor rings */ +#define RMDNUM (1 << (LC_LOG_RX_BUFFERS)) /* 8 Receive Buffers */ + +/* Define Mask for setting RMD, TMD length in the LANCE init_block */ + +#define TMDNUMMASK (LC_LOG_TX_BUFFERS << 29) +#define RMDNUMMASK (LC_LOG_RX_BUFFERS << 29) + +/* + * Data Buffer size is set to maximum packet length. + */ + +#define PKT_BUF_SZ 1518 + +/* + * The number of low I/O ports used by the ethercard. + */ + +#define ETHERCARD_TOTAL_SIZE SK_POS_SIZE + +/* + * Portreserve is there to mark the Card I/O Port region as used. + * Check_region is to check if the region at ioaddr with the size "size" + * is free or not. + * Snarf_region allocates the I/O Port region. + */ + +#ifndef HAVE_PORTRESERVE + +#define check_region(ioaddr, size) 0 +#define request_region(ioaddr, size,name) do ; while (0) + +#endif + +/* + * SK_DEBUG + * + * Here you can choose what level of debugging wanted. + * + * If SK_DEBUG and SK_DEBUG2 are undefined, then only the + * necessary messages will be printed. + * + * If SK_DEBUG is defined, there will be many debugging prints + * which can help to find some mistakes in configuration or even + * in the driver code. + * + * If SK_DEBUG2 is defined, many many messages will be printed + * which normally you don't need. I used this to check the interrupt + * routine. + * + * (If you define only SK_DEBUG2 then only the messages for + * checking interrupts will be printed!) + * + * Normal way of live is: + * + * For the whole thing get going let both symbolic constants + * undefined. If you face any problems and you know what's going + * on (you know something about the card and you can interpret some + * hex LANCE register output) then define SK_DEBUG + * + */ + +#undef SK_DEBUG /* debugging */ +#undef SK_DEBUG2 /* debugging with more verbose report */ + +#ifdef SK_DEBUG +#define PRINTK(x) printk x +#else +#define PRINTK(x) /**/ +#endif + +#ifdef SK_DEBUG2 +#define PRINTK2(x) printk x +#else +#define PRINTK2(x) /**/ +#endif + +/* + * SK_G16 RAM + * + * The components are memory mapped and can be set in a region from + * 0x00000 through 0xfc000 in 16KB steps. + * + * The Network components are: dual ported RAM, Prom, I/O Reg, Status-, + * Controlregister and I/O Command. + * + * dual ported RAM: This is the only memory region which the LANCE chip + * has access to. From the Lance it is addressed from 0x0000 to + * 0x3fbf. The host accesses it normally. + * + * PROM: The PROM obtains the ETHERNET-MAC-Address. It is realised as a + * 8-Bit PROM, this means only the 16 even addresses are used of the + * 32 Byte Address region. Access to a odd address results in invalid + * data. + * + * LANCE I/O Reg: The I/O Reg is build of 4 single Registers, Low-Byte Write, + * Hi-Byte Write, Low-Byte Read, Hi-Byte Read. + * Transfer from or to the LANCE is always in 16Bit so Low and High + * registers are always relevant. + * + * The Data from the Readregister is not the data in the Writeregister!! + * + * Port: Status- and Controlregister. + * Two different registers which share the same address, Status is + * read-only, Control is write-only. + * + * I/O Command: + * Any bitcombination written in here starts the transmission between + * Host and LANCE. + */ + +typedef struct +{ + unsigned char ram[0x3fc0]; /* 16KB dual ported ram */ + unsigned char rom[0x0020]; /* 32Byte PROM containing 6Byte MAC */ + unsigned char res1[0x0010]; /* reserved */ + unsigned volatile short ioreg;/* LANCE I/O Register */ + unsigned volatile char port; /* Statusregister and Controlregister */ + unsigned char iocom; /* I/O Command Register */ +} SK_RAM; + +/* struct */ + +/* + * This is the structure for the dual ported ram. We + * have exactly 16 320 Bytes. In here there must be: + * + * - Initialize Block (starting at a word boundary) + * - Receive and Transmit Descriptor Rings (quadword boundary) + * - Data Buffers (arbitrary boundary) + * + * This is because LANCE has on SK_G16 only access to the dual ported + * RAM and nowhere else. + */ + +struct SK_ram +{ + struct init_block ib; + struct tmd tmde[TMDNUM]; + struct rmd rmde[RMDNUM]; + char tmdbuf[TMDNUM][PKT_BUF_SZ]; + char rmdbuf[RMDNUM][PKT_BUF_SZ]; +}; + +/* + * Structure where all necessary information is for ring buffer + * management and statistics. + */ + +struct priv +{ + struct SK_ram *ram; /* dual ported ram structure */ + struct rmd *rmdhead; /* start of receive ring descriptors */ + struct tmd *tmdhead; /* start of transmit ring descriptors */ + int rmdnum; /* actual used ring descriptor */ + int tmdnum; /* actual transmit descriptor for transmitting data */ + int tmdlast; /* last sent descriptor used for error handling, etc */ + void *rmdbufs[RMDNUM]; /* pointer to the receive buffers */ + void *tmdbufs[TMDNUM]; /* pointer to the transmit buffers */ + struct enet_statistics stats; /* Device driver statistics */ +}; + +/* global variable declaration */ + +/* IRQ map used to reserve a IRQ (see SK_open()) */ + +/* extern void *irq2dev_map[16]; */ /* Declared in <linux/ioport.h> */ + +/* static variables */ + +static SK_RAM *board; /* pointer to our memory mapped board components */ + +/* Macros */ + + +/* Function Prototypes */ + +/* + * Device Driver functions + * ----------------------- + * See for short explanation of each function its definitions header. + */ + +int SK_init(struct device *dev); +static int SK_probe(struct device *dev, short ioaddr); + +static int SK_open(struct device *dev); +static int SK_send_packet(struct sk_buff *skb, struct device *dev); +static void SK_interrupt(int irq, void *dev_id, struct pt_regs * regs); +static void SK_rxintr(struct device *dev); +static void SK_txintr(struct device *dev); +static int SK_close(struct device *dev); + +static struct enet_statistics *SK_get_stats(struct device *dev); + +unsigned int SK_rom_addr(void); + +static void set_multicast_list(struct device *dev); + +/* + * LANCE Functions + * --------------- + */ + +static int SK_lance_init(struct device *dev, unsigned short mode); +void SK_reset_board(void); +void SK_set_RAP(int reg_number); +int SK_read_reg(int reg_number); +int SK_rread_reg(void); +void SK_write_reg(int reg_number, int value); + +/* + * Debugging functions + * ------------------- + */ + +void SK_print_pos(struct device *dev, char *text); +void SK_print_dev(struct device *dev, char *text); +void SK_print_ram(struct device *dev); + + +/*- + * Function : SK_init + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/26 + * + * Description : Check for a SK_G16 network adaptor and initialize it. + * This function gets called by dev_init which initializes + * all Network devices. + * + * Parameters : I : struct device *dev - structure preconfigured + * from Space.c + * Return Value : 0 = Driver Found and initialized + * Errors : ENODEV - no device found + * ENXIO - not probed + * Globals : None + * Update History : + * YY/MM/DD uid Description +-*/ + +/* + * Check for a network adaptor of this type, and return '0' if one exists. + * If dev->base_addr == 0, probe all likely locations. + * If dev->base_addr == 1, always return failure. + * If dev->base_addr == 2, allocate space for the device and return success + * (detachable devices only). + */ + +int SK_init(struct device *dev) +{ + int ioaddr = 0; /* I/O port address used for POS regs */ + int *port, ports[] = SK_IO_PORTS; /* SK_G16 supported ports */ + + /* get preconfigured base_addr from dev which is done in Space.c */ + int base_addr = dev->base_addr; + + PRINTK(("%s: %s", SK_NAME, rcsid)); + rcsid = NULL; /* We do not want to use this further */ + + if (base_addr > 0x0ff) /* Check a single specified address */ + { + /* Check if on specified address is a SK_G16 */ + + if ( (inb(SK_POS0) == SK_IDLOW) || + (inb(SK_POS1) == SK_IDHIGH) ) + { + return SK_probe(dev, base_addr); + } + + return ENODEV; /* Sorry, but on specified address NO SK_G16 */ + } + else if (base_addr > 0) /* Don't probe at all */ + { + return ENXIO; + } + + /* Autoprobe base_addr */ + + for (port = &ports[0]; *port; port++) + { + ioaddr = *port; /* we need ioaddr for accessing POS regs */ + + /* Check if I/O Port region is used by another board */ + + if (check_region(ioaddr, ETHERCARD_TOTAL_SIZE)) + { + continue; /* Try next Port address */ + } + + /* Check if at ioaddr is a SK_G16 */ + + if ( !(inb(SK_POS0) == SK_IDLOW) || + !(inb(SK_POS1) == SK_IDHIGH) ) + { + continue; /* Try next Port address */ + } + + dev->base_addr = ioaddr; /* Set I/O Port Address */ + + if (SK_probe(dev, ioaddr) == 0) + { + return 0; /* Card found and initialized */ + } + } + + dev->base_addr = base_addr; /* Write back original base_addr */ + + return ENODEV; /* Failed to find or init driver */ + +} /* End of SK_init */ + + +/*- + * Function : SK_probe + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/26 + * + * Description : This function is called by SK_init and + * does the main part of initialization. + * + * Parameters : I : struct device *dev - SK_G16 device structure + * I : short ioaddr - I/O Port address where POS is. + * Return Value : 0 = Initialization done + * Errors : ENODEV - No SK_G16 found + * -1 - Configuration problem + * Globals : irq2dev_map - Which device uses which IRQ + * : board - pointer to SK_RAM + * Update History : + * YY/MM/DD uid Description + * 94/06/30 pwe SK_ADDR now checked and at the correct place +-*/ + +int SK_probe(struct device *dev, short ioaddr) +{ + int i,j; /* Counters */ + int sk_addr_flag = 0; /* SK ADDR correct? 1 - no, 0 - yes */ + unsigned int rom_addr; /* used to store RAM address used for POS_ADDR */ + + struct priv *p; /* SK_G16 private structure */ + + if (SK_ADDR & 0x3fff || SK_ADDR < 0xa0000) + { + + sk_addr_flag = 1; + + /* + * Now here we could use a routine which searches for a free + * place in the ram and set SK_ADDR if found. TODO. + */ + } + + if (SK_BOOT_ROM) /* Shall we keep Boot_ROM on ? */ + { + PRINTK(("## %s: SK_BOOT_ROM is set.\n", SK_NAME)); + + rom_addr = SK_rom_addr(); + + if (rom_addr == 0) /* No Boot_ROM found */ + { + if (sk_addr_flag) /* No or Invalid SK_ADDR is defined */ + { + printk("%s: SK_ADDR %#08x is not valid. Check configuration.\n", + dev->name, SK_ADDR); + return -1; + } + + rom_addr = SK_ADDR; /* assign predefined address */ + + PRINTK(("## %s: NO Bootrom found \n", SK_NAME)); + + outb(SK_ROM_RAM_OFF, SK_POS2); /* Boot_ROM + RAM off */ + outb(POS_ADDR, SK_POS3); /* Set RAM address */ + outb(SK_RAM_ON, SK_POS2); /* enable RAM */ + } + else if (rom_addr == SK_ADDR) + { + printk("%s: RAM + ROM are set to the same address %#08x\n" + " Check configuration. Now switching off Boot_ROM\n", + SK_NAME, rom_addr); + + outb(SK_ROM_RAM_OFF, SK_POS2); /* Boot_ROM + RAM off*/ + outb(POS_ADDR, SK_POS3); /* Set RAM address */ + outb(SK_RAM_ON, SK_POS2); /* enable RAM */ + } + else + { + PRINTK(("## %s: Found ROM at %#08x\n", SK_NAME, rom_addr)); + PRINTK(("## %s: Keeping Boot_ROM on\n", SK_NAME)); + + if (sk_addr_flag) /* No or Invalid SK_ADDR is defined */ + { + printk("%s: SK_ADDR %#08x is not valid. Check configuration.\n", + dev->name, SK_ADDR); + return -1; + } + + rom_addr = SK_ADDR; + + outb(SK_ROM_RAM_OFF, SK_POS2); /* Boot_ROM + RAM off */ + outb(POS_ADDR, SK_POS3); /* Set RAM address */ + outb(SK_ROM_RAM_ON, SK_POS2); /* RAM on, BOOT_ROM on */ + } + } + else /* Don't keep Boot_ROM */ + { + PRINTK(("## %s: SK_BOOT_ROM is not set.\n", SK_NAME)); + + if (sk_addr_flag) /* No or Invalid SK_ADDR is defined */ + { + printk("%s: SK_ADDR %#08x is not valid. Check configuration.\n", + dev->name, SK_ADDR); + return -1; + } + + rom_addr = SK_rom_addr(); /* Try to find a Boot_ROM */ + + /* IF we find a Boot_ROM disable it */ + + outb(SK_ROM_RAM_OFF, SK_POS2); /* Boot_ROM + RAM off */ + + /* We found a Boot_ROM and it's gone. Set RAM address on + * Boot_ROM address. + */ + + if (rom_addr) + { + printk("%s: We found Boot_ROM at %#08x. Now setting RAM on" + "that address\n", SK_NAME, rom_addr); + + outb(POS_ADDR, SK_POS3); /* Set RAM on Boot_ROM address */ + } + else /* We did not find a Boot_ROM, use predefined SK_ADDR for ram */ + { + if (sk_addr_flag) /* No or Invalid SK_ADDR is defined */ + { + printk("%s: SK_ADDR %#08x is not valid. Check configuration.\n", + dev->name, SK_ADDR); + return -1; + } + + rom_addr = SK_ADDR; + + outb(POS_ADDR, SK_POS3); /* Set RAM address */ + } + outb(SK_RAM_ON, SK_POS2); /* enable RAM */ + } + +#ifdef SK_DEBUG + SK_print_pos(dev, "POS registers after ROM, RAM config"); +#endif + + board = (SK_RAM *) rom_addr; + + /* Read in station address */ + for (i = 0, j = 0; i < ETH_ALEN; i++, j+=2) + { + dev->dev_addr[i] = board->rom[j]; + } + + /* Check for manufacturer code */ + if (!(dev->dev_addr[0] == SK_MAC0 && + dev->dev_addr[1] == SK_MAC1 && + dev->dev_addr[2] == SK_MAC2) ) + { + PRINTK(("## %s: We did not find SK_G16 at RAM location.\n", + SK_NAME)); + return ENODEV; /* NO SK_G16 found */ + } + + printk("%s: %s found at %#3x, HW addr: %#04x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, + "Schneider & Koch Netcard", + (unsigned int) dev->base_addr, + dev->dev_addr[0], + dev->dev_addr[1], + dev->dev_addr[2], + dev->dev_addr[3], + dev->dev_addr[4], + dev->dev_addr[5]); + + /* Allocate memory for private structure */ + p = dev->priv = (void *) kmalloc(sizeof(struct priv), GFP_KERNEL); + if (p == NULL) { + printk("%s: ERROR - no memory for driver data!\n", dev->name); + return -ENOMEM; + } + memset((char *) dev->priv, 0, sizeof(struct priv)); /* clear memory */ + + /* Grab the I/O Port region */ + request_region(ioaddr, ETHERCARD_TOTAL_SIZE,"sk_g16"); + + /* Assign our Device Driver functions */ + + dev->open = &SK_open; + dev->stop = &SK_close; + dev->hard_start_xmit = &SK_send_packet; + dev->get_stats = &SK_get_stats; + dev->set_multicast_list = &set_multicast_list; + + + /* Set the generic fields of the device structure */ + + ether_setup(dev); + + dev->flags &= ~IFF_MULTICAST; + + /* Initialize private structure */ + + p->ram = (struct SK_ram *) rom_addr; /* Set dual ported RAM addr */ + p->tmdhead = &(p->ram)->tmde[0]; /* Set TMD head */ + p->rmdhead = &(p->ram)->rmde[0]; /* Set RMD head */ + + /* Initialize buffer pointers */ + + for (i = 0; i < TMDNUM; i++) + { + p->tmdbufs[i] = &(p->ram)->tmdbuf[i]; + } + + for (i = 0; i < RMDNUM; i++) + { + p->rmdbufs[i] = &(p->ram)->rmdbuf[i]; + } + +#ifdef SK_DEBUG + SK_print_pos(dev, "End of SK_probe"); + SK_print_ram(dev); +#endif + + return 0; /* Initialization done */ + +} /* End of SK_probe() */ + + +/*- + * Function : SK_open + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/26 + * + * Description : This function is called sometimes after booting + * when ifconfig program is run. + * + * This function requests an IRQ, sets the correct + * IRQ in the card. Then calls SK_lance_init() to + * init and start the LANCE chip. Then if everything is + * ok returns with 0 (OK), which means SK_G16 is now + * opened and operational. + * + * (Called by dev_open() /net/inet/dev.c) + * + * Parameters : I : struct device *dev - SK_G16 device structure + * Return Value : 0 - Device opened + * Errors : -EAGAIN - Open failed + * Globals : irq2dev_map - which device uses which irq + * Side Effects : None + * Update History : + * YY/MM/DD uid Description +-*/ + +static int SK_open(struct device *dev) +{ + int i = 0; + int irqval = 0; + int ioaddr = dev->base_addr; + + int irqtab[] = SK_IRQS; + + struct priv *p = (struct priv *)dev->priv; + + PRINTK(("## %s: At beginning of SK_open(). CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + if (dev->irq == 0) /* Autoirq */ + { + i = 0; + + /* + * Check if one IRQ out of SK_IRQS is free and install + * interrupt handler. + * Most done by request_irq(). + * irqval: 0 - interrupt handler installed for IRQ irqtab[i] + * -EBUSY - interrupt busy + * -EINVAL - irq > 15 or handler = NULL + */ + + do + { + irqval = request_irq(irqtab[i], &SK_interrupt, 0, "sk_g16", NULL); + i++; + } while (irqval && irqtab[i]); + + if (irqval) /* We tried every possible IRQ but no success */ + { + printk("%s: unable to get an IRQ\n", dev->name); + return -EAGAIN; + } + + dev->irq = irqtab[--i]; + + outb(i<<2, SK_POS4); /* Set Card on probed IRQ */ + + } + else if (dev->irq == 2) /* IRQ2 is always IRQ9 */ + { + if (request_irq(9, &SK_interrupt, 0, "sk_g16", NULL)) + { + printk("%s: unable to get IRQ 9\n", dev->name); + return -EAGAIN; + } + dev->irq = 9; + + /* + * Now we set card on IRQ2. + * This can be confusing, but remember that IRQ2 on the network + * card is in reality IRQ9 + */ + outb(0x08, SK_POS4); /* set card to IRQ2 */ + + } + else /* Check IRQ as defined in Space.c */ + { + int i = 0; + + /* check if IRQ free and valid. Then install Interrupt handler */ + + if (request_irq(dev->irq, &SK_interrupt, 0, "sk_g16", NULL)) + { + printk("%s: unable to get selected IRQ\n", dev->name); + return -EAGAIN; + } + + switch(dev->irq) + { + case 3: i = 0; + break; + case 5: i = 1; + break; + case 2: i = 2; + break; + case 11:i = 3; + break; + default: + printk("%s: Preselected IRQ %d is invalid for %s boards", + dev->name, + dev->irq, + SK_NAME); + return -EAGAIN; + } + + outb(i<<2, SK_POS4); /* Set IRQ on card */ + } + + irq2dev_map[dev->irq] = dev; /* Set IRQ as used by us */ + + printk("%s: Schneider & Koch G16 at %#3x, IRQ %d, shared mem at %#08x\n", + dev->name, (unsigned int)dev->base_addr, + (int) dev->irq, (unsigned int) p->ram); + + if (!(i = SK_lance_init(dev, 0))) /* LANCE init OK? */ + { + + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + +#ifdef SK_DEBUG + + /* + * This debug block tries to stop LANCE, + * reinit LANCE with transmitter and receiver disabled, + * then stop again and reinit with NORMAL_MODE + */ + + printk("## %s: After lance init. CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0)); + SK_write_reg(CSR0, CSR0_STOP); + printk("## %s: LANCE stopped. CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0)); + SK_lance_init(dev, MODE_DTX | MODE_DRX); + printk("## %s: Reinit with DTX + DRX off. CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0)); + SK_write_reg(CSR0, CSR0_STOP); + printk("## %s: LANCE stopped. CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0)); + SK_lance_init(dev, MODE_NORMAL); + printk("## %s: LANCE back to normal mode. CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0)); + SK_print_pos(dev, "POS regs before returning OK"); + +#endif /* SK_DEBUG */ + + return 0; /* SK_open() is successful */ + } + else /* LANCE init failed */ + { + + PRINTK(("## %s: LANCE init failed: CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + dev->start = 0; /* Device not ready */ + return -EAGAIN; + } + +} /* End of SK_open() */ + + +/*- + * Function : SK_lance_init + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/26 + * + * Description : Reset LANCE chip, fill RMD, TMD structures with + * start values and Start LANCE. + * + * Parameters : I : struct device *dev - SK_G16 device structure + * I : int mode - put LANCE into "mode" see data-sheet for + * more info. + * Return Value : 0 - Init done + * Errors : -1 - Init failed + * Update History : + * YY/MM/DD uid Description +-*/ + +static int SK_lance_init(struct device *dev, unsigned short mode) +{ + int i; + struct priv *p = (struct priv *) dev->priv; + struct tmd *tmdp; + struct rmd *rmdp; + + PRINTK(("## %s: At beginning of LANCE init. CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + /* Reset LANCE */ + SK_reset_board(); + + /* Initialize TMD's with start values */ + p->tmdnum = 0; /* First descriptor for transmitting */ + p->tmdlast = 0; /* First descriptor for reading stats */ + + for (i = 0; i < TMDNUM; i++) /* Init all TMD's */ + { + tmdp = p->tmdhead + i; + + tmdp->u.buffer = (unsigned long) p->tmdbufs[i]; /* assign buffer */ + + /* Mark TMD as start and end of packet */ + tmdp->u.s.status = TX_STP | TX_ENP; + } + + + /* Initialize RMD's with start values */ + + p->rmdnum = 0; /* First RMD which will be used */ + + for (i = 0; i < RMDNUM; i++) /* Init all RMD's */ + { + rmdp = p->rmdhead + i; + + + rmdp->u.buffer = (unsigned long) p->rmdbufs[i]; /* assign buffer */ + + /* + * LANCE must be owner at beginning so that he can fill in + * receiving packets, set status and release RMD + */ + + rmdp->u.s.status = RX_OWN; + + rmdp->blen = -PKT_BUF_SZ; /* Buffer Size in a two's complement */ + + rmdp->mlen = 0; /* init message length */ + + } + + /* Fill LANCE Initialize Block */ + + (p->ram)->ib.mode = mode; /* Set operation mode */ + + for (i = 0; i < ETH_ALEN; i++) /* Set physical address */ + { + (p->ram)->ib.paddr[i] = dev->dev_addr[i]; + } + + for (i = 0; i < 8; i++) /* Set multicast, logical address */ + { + (p->ram)->ib.laddr[i] = 0; /* We do not use logical addressing */ + } + + /* Set ring descriptor pointers and set number of descriptors */ + + (p->ram)->ib.rdrp = (int) p->rmdhead | RMDNUMMASK; + (p->ram)->ib.tdrp = (int) p->tmdhead | TMDNUMMASK; + + /* Prepare LANCE Control and Status Registers */ + + cli(); + + SK_write_reg(CSR3, CSR3_ACON); /* Ale Control !!!THIS MUST BE SET!!!! */ + + /* + * LANCE addresses the RAM from 0x0000 to 0x3fbf and has no access to + * PC Memory locations. + * + * In structure SK_ram is defined that the first thing in ram + * is the initialization block. So his address is for LANCE always + * 0x0000 + * + * CSR1 contains low order bits 15:0 of initialization block address + * CSR2 is built of: + * 7:0 High order bits 23:16 of initialization block address + * 15:8 reserved, must be 0 + */ + + /* Set initialization block address (must be on word boundary) */ + SK_write_reg(CSR1, 0); /* Set low order bits 15:0 */ + SK_write_reg(CSR2, 0); /* Set high order bits 23:16 */ + + + PRINTK(("## %s: After setting CSR1-3. CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + /* Initialize LANCE */ + + /* + * INIT = Initialize, when set, causes the LANCE to begin the + * initialization procedure and access the Init Block. + */ + + SK_write_reg(CSR0, CSR0_INIT); + + sti(); + + /* Wait until LANCE finished initialization */ + + SK_set_RAP(CSR0); /* Register Address Pointer to CSR0 */ + + for (i = 0; (i < 100) && !(SK_rread_reg() & CSR0_IDON); i++) + ; /* Wait until init done or go ahead if problems (i>=100) */ + + if (i >= 100) /* Something is wrong ! */ + { + printk("%s: can't init am7990, status: %04x " + "init_block: %#08x\n", + dev->name, (int) SK_read_reg(CSR0), + (unsigned int) &(p->ram)->ib); + +#ifdef SK_DEBUG + SK_print_pos(dev, "LANCE INIT failed"); + SK_print_dev(dev,"Device Structure:"); +#endif + + return -1; /* LANCE init failed */ + } + + PRINTK(("## %s: init done after %d ticks\n", SK_NAME, i)); + + /* Clear Initialize done, enable Interrupts, start LANCE */ + + SK_write_reg(CSR0, CSR0_IDON | CSR0_INEA | CSR0_STRT); + + PRINTK(("## %s: LANCE started. CSR0: %#06x\n", SK_NAME, + SK_read_reg(CSR0))); + + return 0; /* LANCE is up and running */ + +} /* End of SK_lance_init() */ + + + +/*- + * Function : SK_send_packet + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/27 + * + * Description : Writes an socket buffer into a transmit descriptor + * and starts transmission. + * + * Parameters : I : struct sk_buff *skb - packet to transfer + * I : struct device *dev - SK_G16 device structure + * Return Value : 0 - OK + * 1 - Could not transmit (dev_queue_xmit will queue it) + * and try to sent it later + * Globals : None + * Side Effects : None + * Update History : + * YY/MM/DD uid Description +-*/ + +static int SK_send_packet(struct sk_buff *skb, struct device *dev) +{ + struct priv *p = (struct priv *) dev->priv; + struct tmd *tmdp; + + if (dev->tbusy) + { + /* if Transmitter more than 150ms busy -> time_out */ + + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 15) + { + return 1; /* We have to try transmit later */ + } + + printk("%s: xmitter timed out, try to restart!\n", dev->name); + + SK_lance_init(dev, MODE_NORMAL); /* Reinit LANCE */ + + dev->tbusy = 0; /* Clear Transmitter flag */ + + dev->trans_start = jiffies; /* Mark Start of transmission */ + + } + + /* + * If some upper Layer thinks we missed a transmit done interrupt + * we are passed NULL. + * (dev_queue_xmit net/inet/dev.c + */ + + if (skb == NULL) + { + /* + * Dequeue packets from transmit queue and send them. + */ + dev_tint(dev); + + return 0; + } + + PRINTK2(("## %s: SK_send_packet() called, CSR0 %#04x.\n", + SK_NAME, SK_read_reg(CSR0))); + + + /* + * Block a timer-based transmit from overlapping. + * This means check if we are already in. + */ + + if (set_bit(0, (void *) &dev->tbusy) != 0) /* dev->tbusy already set ? */ + { + printk("%s: Transmitter access conflict.\n", dev->name); + } + else + { + /* Evaluate Packet length */ + short len = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + + tmdp = p->tmdhead + p->tmdnum; /* Which descriptor for transmitting */ + + /* Fill in Transmit Message Descriptor */ + + /* Copy data into dual ported ram */ + + memcpy((char *) (tmdp->u.buffer & 0x00ffffff), (char *)skb->data, + skb->len); + + tmdp->blen = -len; /* set length to transmit */ + + /* + * Packet start and end is always set because we use the maximum + * packet length as buffer length. + * Relinquish ownership to LANCE + */ + + tmdp->u.s.status = TX_OWN | TX_STP | TX_ENP; + + /* Start Demand Transmission */ + SK_write_reg(CSR0, CSR0_TDMD | CSR0_INEA); + + dev->trans_start = jiffies; /* Mark start of transmission */ + + /* Set pointer to next transmit buffer */ + p->tmdnum++; + p->tmdnum &= TMDNUM-1; + + /* Do we own the next transmit buffer ? */ + if (! ((p->tmdhead + p->tmdnum)->u.s.status & TX_OWN) ) + { + /* + * We own next buffer and are ready to transmit, so + * clear busy flag + */ + dev->tbusy = 0; + } + } + dev_kfree_skb(skb, FREE_WRITE); + return 0; +} /* End of SK_send_packet */ + + +/*- + * Function : SK_interrupt + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/27 + * + * Description : SK_G16 interrupt handler which checks for LANCE + * Errors, handles transmit and receive interrupts + * + * Parameters : I : int irq, void *dev_id, struct pt_regs * regs - + * Return Value : None + * Errors : None + * Globals : None + * Side Effects : None + * Update History : + * YY/MM/DD uid Description +-*/ + +static void SK_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + int csr0; + struct device *dev = (struct device *) irq2dev_map[irq]; + struct priv *p = (struct priv *) dev->priv; + + + PRINTK2(("## %s: SK_interrupt(). status: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + if (dev == NULL) + { + printk("SK_interrupt(): IRQ %d for unknown device.\n", irq); + } + + + if (dev->interrupt) + { + printk("%s: Re-entering the interrupt handler.\n", dev->name); + } + + csr0 = SK_read_reg(CSR0); /* store register for checking */ + + dev->interrupt = 1; /* We are handling an interrupt */ + + /* + * Acknowledge all of the current interrupt sources, disable + * Interrupts (INEA = 0) + */ + + SK_write_reg(CSR0, csr0 & CSR0_CLRALL); + + if (csr0 & CSR0_ERR) /* LANCE Error */ + { + printk("%s: error: %04x\n", dev->name, csr0); + + if (csr0 & CSR0_MISS) /* No place to store packet ? */ + { + p->stats.rx_dropped++; + } + } + + if (csr0 & CSR0_RINT) /* Receive Interrupt (packet arrived) */ + { + SK_rxintr(dev); + } + + if (csr0 & CSR0_TINT) /* Transmit interrupt (packet sent) */ + { + SK_txintr(dev); + } + + SK_write_reg(CSR0, CSR0_INEA); /* Enable Interrupts */ + + dev->interrupt = 0; /* We are out */ +} /* End of SK_interrupt() */ + + +/*- + * Function : SK_txintr + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/27 + * + * Description : After sending a packet we check status, update + * statistics and relinquish ownership of transmit + * descriptor ring. + * + * Parameters : I : struct device *dev - SK_G16 device structure + * Return Value : None + * Errors : None + * Globals : None + * Update History : + * YY/MM/DD uid Description +-*/ + +static void SK_txintr(struct device *dev) +{ + int tmdstat; + struct tmd *tmdp; + struct priv *p = (struct priv *) dev->priv; + + + PRINTK2(("## %s: SK_txintr() status: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + tmdp = p->tmdhead + p->tmdlast; /* Which buffer we sent at last ? */ + + /* Set next buffer */ + p->tmdlast++; + p->tmdlast &= TMDNUM-1; + + tmdstat = tmdp->u.s.status & 0xff00; /* filter out status bits 15:08 */ + + /* + * We check status of transmitted packet. + * see LANCE data-sheet for error explanation + */ + if (tmdstat & TX_ERR) /* Error occurred */ + { + printk("%s: TX error: %04x %04x\n", dev->name, (int) tmdstat, + (int) tmdp->status2); + + if (tmdp->status2 & TX_TDR) /* TDR problems? */ + { + printk("%s: tdr-problems \n", dev->name); + } + + if (tmdp->status2 & TX_RTRY) /* Failed in 16 attempts to transmit ? */ + p->stats.tx_aborted_errors++; + if (tmdp->status2 & TX_LCOL) /* Late collision ? */ + p->stats.tx_window_errors++; + if (tmdp->status2 & TX_LCAR) /* Loss of Carrier ? */ + p->stats.tx_carrier_errors++; + if (tmdp->status2 & TX_UFLO) /* Underflow error ? */ + { + p->stats.tx_fifo_errors++; + + /* + * If UFLO error occurs it will turn transmitter of. + * So we must reinit LANCE + */ + + SK_lance_init(dev, MODE_NORMAL); + } + + p->stats.tx_errors++; + + tmdp->status2 = 0; /* Clear error flags */ + } + else if (tmdstat & TX_MORE) /* Collisions occurred ? */ + { + /* + * Here I have a problem. + * I only know that there must be one or up to 15 collisions. + * That's why TX_MORE is set, because after 16 attempts TX_RTRY + * will be set which means couldn't send packet aborted transfer. + * + * First I did not have this in but then I thought at minimum + * we see that something was not ok. + * If anyone knows something better than this to handle this + * please report it. (see Email addresses in the README file) + */ + + p->stats.collisions++; + } + else /* Packet sent without any problems */ + { + p->stats.tx_packets++; + } + + /* + * We mark transmitter not busy anymore, because now we have a free + * transmit descriptor which can be filled by SK_send_packet and + * afterwards sent by the LANCE + */ + + dev->tbusy = 0; + + /* + * mark_bh(NET_BH); + * This will cause net_bh() to run after this interrupt handler. + * + * The function which do handle slow IRQ parts is do_bottom_half() + * which runs at normal kernel priority, that means all interrupt are + * enabled. (see kernel/irq.c) + * + * net_bh does something like this: + * - check if already in net_bh + * - try to transmit something from the send queue + * - if something is in the receive queue send it up to higher + * levels if it is a known protocol + * - try to transmit something from the send queue + */ + + mark_bh(NET_BH); + +} /* End of SK_txintr() */ + + +/*- + * Function : SK_rxintr + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/27 + * + * Description : Buffer sent, check for errors, relinquish ownership + * of the receive message descriptor. + * + * Parameters : I : SK_G16 device structure + * Return Value : None + * Globals : None + * Update History : + * YY/MM/DD uid Description +-*/ + +static void SK_rxintr(struct device *dev) +{ + + struct rmd *rmdp; + int rmdstat; + struct priv *p = (struct priv *) dev->priv; + + PRINTK2(("## %s: SK_rxintr(). CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + rmdp = p->rmdhead + p->rmdnum; + + /* As long as we own the next entry, check status and send + * it up to higher layer + */ + + while (!( (rmdstat = rmdp->u.s.status) & RX_OWN)) + { + /* + * Start and end of packet must be set, because we use + * the ethernet maximum packet length (1518) as buffer size. + * + * Because our buffers are at maximum OFLO and BUFF errors are + * not to be concerned (see Data sheet) + */ + + if ((rmdstat & (RX_STP | RX_ENP)) != (RX_STP | RX_ENP)) + { + /* Start of a frame > 1518 Bytes ? */ + + if (rmdstat & RX_STP) + { + p->stats.rx_errors++; /* bad packet received */ + p->stats.rx_length_errors++; /* packet too long */ + + printk("%s: packet too long\n", dev->name); + } + + /* + * All other packets will be ignored until a new frame with + * start (RX_STP) set follows. + * + * What we do is just give descriptor free for new incoming + * packets. + */ + + rmdp->u.s.status = RX_OWN; /* Relinquish ownership to LANCE */ + + } + else if (rmdstat & RX_ERR) /* Receive Error ? */ + { + printk("%s: RX error: %04x\n", dev->name, (int) rmdstat); + + p->stats.rx_errors++; + + if (rmdstat & RX_FRAM) p->stats.rx_frame_errors++; + if (rmdstat & RX_CRC) p->stats.rx_crc_errors++; + + rmdp->u.s.status = RX_OWN; /* Relinquish ownership to LANCE */ + + } + else /* We have a packet which can be queued for the upper layers */ + { + + int len = (rmdp->mlen & 0x0fff); /* extract message length from receive buffer */ + struct sk_buff *skb; + + skb = dev_alloc_skb(len+2); /* allocate socket buffer */ + + if (skb == NULL) /* Could not get mem ? */ + { + + /* + * Couldn't allocate sk_buffer so we give descriptor back + * to Lance, update statistics and go ahead. + */ + + rmdp->u.s.status = RX_OWN; /* Relinquish ownership to LANCE */ + printk("%s: Couldn't allocate sk_buff, deferring packet.\n", + dev->name); + p->stats.rx_dropped++; + + break; /* Jump out */ + } + + /* Prepare sk_buff to queue for upper layers */ + + skb->dev = dev; + skb_reserve(skb,2); /* Align IP header on 16 byte boundary */ + + /* + * Copy data out of our receive descriptor into sk_buff. + * + * (rmdp->u.buffer & 0x00ffffff) -> get address of buffer and + * ignore status fields) + */ + + memcpy(skb_put(skb,len), (unsigned char *) (rmdp->u.buffer & 0x00ffffff), + len); + + + /* + * Notify the upper protocol layers that there is another packet + * to handle + * + * netif_rx() always succeeds. see /net/inet/dev.c for more. + */ + + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); /* queue packet and mark it for processing */ + + /* + * Packet is queued and marked for processing so we + * free our descriptor and update statistics + */ + + rmdp->u.s.status = RX_OWN; + p->stats.rx_packets++; + + + p->rmdnum++; + p->rmdnum %= RMDNUM; + + rmdp = p->rmdhead + p->rmdnum; + } + } +} /* End of SK_rxintr() */ + + +/*- + * Function : SK_close + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/26 + * + * Description : close gets called from dev_close() and should + * deinstall the card (free_irq, mem etc). + * + * Parameters : I : struct device *dev - our device structure + * Return Value : 0 - closed device driver + * Errors : None + * Globals : None + * Update History : + * YY/MM/DD uid Description +-*/ + +/* I have tried to set BOOT_ROM on and RAM off but then, after a 'ifconfig + * down' the system stops. So I don't shut set card to init state. + */ + +static int SK_close(struct device *dev) +{ + + PRINTK(("## %s: SK_close(). CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + dev->tbusy = 1; /* Transmitter busy */ + dev->start = 0; /* Card down */ + + printk("%s: Shutting %s down CSR0 %#06x\n", dev->name, SK_NAME, + (int) SK_read_reg(CSR0)); + + SK_write_reg(CSR0, CSR0_STOP); /* STOP the LANCE */ + + free_irq(dev->irq, NULL); /* Free IRQ */ + irq2dev_map[dev->irq] = 0; /* Mark IRQ as unused */ + + return 0; /* always succeed */ + +} /* End of SK_close() */ + + +/*- + * Function : SK_get_stats + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/26 + * + * Description : Return current status structure to upper layers. + * It is called by sprintf_stats (dev.c). + * + * Parameters : I : struct device *dev - our device structure + * Return Value : struct enet_statistics * - our current statistics + * Errors : None + * Side Effects : None + * Update History : + * YY/MM/DD uid Description +-*/ + +static struct enet_statistics *SK_get_stats(struct device *dev) +{ + + struct priv *p = (struct priv *) dev->priv; + + PRINTK(("## %s: SK_get_stats(). CSR0: %#06x\n", + SK_NAME, SK_read_reg(CSR0))); + + return &p->stats; /* Return Device status */ + +} /* End of SK_get_stats() */ + + +/*- + * Function : set_multicast_list + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/26 + * + * Description : This function gets called when a program performs + * a SIOCSIFFLAGS call. Ifconfig does this if you call + * 'ifconfig [-]allmulti' which enables or disables the + * Promiscuous mode. + * Promiscuous mode is when the Network card accepts all + * packets, not only the packets which match our MAC + * Address. It is useful for writing a network monitor, + * but it is also a security problem. You have to remember + * that all information on the net is not encrypted. + * + * Parameters : I : struct device *dev - SK_G16 device Structure + * Return Value : None + * Errors : None + * Globals : None + * Update History : + * YY/MM/DD uid Description + * 95/10/18 ACox New multicast calling scheme +-*/ + + +/* Set or clear the multicast filter for SK_G16. + */ + +static void set_multicast_list(struct device *dev) +{ + + if (dev->flags&IFF_PROMISC) + { + /* Reinitialize LANCE with MODE_PROM set */ + SK_lance_init(dev, MODE_PROM); + } + else if (dev->mc_count==0 && !(dev->flags&IFF_ALLMULTI)) + { + /* Reinitialize LANCE without MODE_PROM */ + SK_lance_init(dev, MODE_NORMAL); + } + else + { + /* Multicast with logical address filter on */ + /* Reinitialize LANCE without MODE_PROM */ + SK_lance_init(dev, MODE_NORMAL); + + /* Not implemented yet. */ + } +} /* End of set_multicast_list() */ + + + +/*- + * Function : SK_rom_addr + * Author : Patrick J.D. Weichmann + * Date Created : 94/06/01 + * + * Description : Try to find a Boot_ROM at all possible locations + * + * Parameters : None + * Return Value : Address where Boot_ROM is + * Errors : 0 - Did not find Boot_ROM + * Globals : None + * Update History : + * YY/MM/DD uid Description +-*/ + +unsigned int SK_rom_addr(void) +{ + int i,j; + int rom_found = 0; + unsigned int rom_location[] = SK_BOOT_ROM_LOCATIONS; + unsigned char rom_id[] = SK_BOOT_ROM_ID; + unsigned char *test_byte; + + /* Autodetect Boot_ROM */ + PRINTK(("## %s: Autodetection of Boot_ROM\n", SK_NAME)); + + for (i = 0; (rom_location[i] != 0) && (rom_found == 0); i++) + { + + PRINTK(("## Trying ROM location %#08x", rom_location[i])); + + rom_found = 1; + for (j = 0; j < 6; j++) + { + test_byte = (unsigned char *) (rom_location[i]+j); + PRINTK((" %02x ", *test_byte)); + + if(!(*test_byte == rom_id[j])) + { + rom_found = 0; + } + } + PRINTK(("\n")); + } + + if (rom_found == 1) + { + PRINTK(("## %s: Boot_ROM found at %#08x\n", + SK_NAME, rom_location[(i-1)])); + + return (rom_location[--i]); + } + else + { + PRINTK(("%s: No Boot_ROM found\n", SK_NAME)); + return 0; + } +} /* End of SK_rom_addr() */ + + + +/* LANCE access functions + * + * ! CSR1-3 can only be accessed when in CSR0 the STOP bit is set ! + */ + + +/*- + * Function : SK_reset_board + * + * Author : Patrick J.D. Weichmann + * + * Date Created : 94/05/25 + * + * Description : This function resets SK_G16 and all components, but + * POS registers are not changed + * + * Parameters : None + * Return Value : None + * Errors : None + * Globals : SK_RAM *board - SK_RAM structure pointer + * + * Update History : + * YY/MM/DD uid Description +-*/ + +void SK_reset_board(void) +{ + int i; + + SK_PORT = 0x00; /* Reset active */ + for (i = 0; i < 10 ; i++) /* Delay min 5ms */ + ; + SK_PORT = SK_RESET; /* Set back to normal operation */ + +} /* End of SK_reset_board() */ + + +/*- + * Function : SK_set_RAP + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/25 + * + * Description : Set LANCE Register Address Port to register + * for later data transfer. + * + * Parameters : I : reg_number - which CSR to read/write from/to + * Return Value : None + * Errors : None + * Globals : SK_RAM *board - SK_RAM structure pointer + * Update History : + * YY/MM/DD uid Description +-*/ + +void SK_set_RAP(int reg_number) +{ + SK_IOREG = reg_number; + SK_PORT = SK_RESET | SK_RAP | SK_WREG; + SK_IOCOM = SK_DOIO; + + while (SK_PORT & SK_IORUN) + ; +} /* End of SK_set_RAP() */ + + +/*- + * Function : SK_read_reg + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/25 + * + * Description : Set RAP and read data from a LANCE CSR register + * + * Parameters : I : reg_number - which CSR to read from + * Return Value : Register contents + * Errors : None + * Globals : SK_RAM *board - SK_RAM structure pointer + * Update History : + * YY/MM/DD uid Description +-*/ + +int SK_read_reg(int reg_number) +{ + SK_set_RAP(reg_number); + + SK_PORT = SK_RESET | SK_RDATA | SK_RREG; + SK_IOCOM = SK_DOIO; + + while (SK_PORT & SK_IORUN) + ; + return (SK_IOREG); + +} /* End of SK_read_reg() */ + + +/*- + * Function : SK_rread_reg + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/28 + * + * Description : Read data from preseted register. + * This function requires that you know which + * Register is actually set. Be aware that CSR1-3 + * can only be accessed when in CSR0 STOP is set. + * + * Return Value : Register contents + * Errors : None + * Globals : SK_RAM *board - SK_RAM structure pointer + * Update History : + * YY/MM/DD uid Description +-*/ + +int SK_rread_reg(void) +{ + SK_PORT = SK_RESET | SK_RDATA | SK_RREG; + + SK_IOCOM = SK_DOIO; + + while (SK_PORT & SK_IORUN) + ; + return (SK_IOREG); + +} /* End of SK_rread_reg() */ + + +/*- + * Function : SK_write_reg + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/25 + * + * Description : This function sets the RAP then fills in the + * LANCE I/O Reg and starts Transfer to LANCE. + * It waits until transfer has ended which is max. 7 ms + * and then it returns. + * + * Parameters : I : reg_number - which CSR to write to + * I : value - what value to fill into register + * Return Value : None + * Errors : None + * Globals : SK_RAM *board - SK_RAM structure pointer + * Update History : + * YY/MM/DD uid Description +-*/ + +void SK_write_reg(int reg_number, int value) +{ + SK_set_RAP(reg_number); + + SK_IOREG = value; + SK_PORT = SK_RESET | SK_RDATA | SK_WREG; + SK_IOCOM = SK_DOIO; + + while (SK_PORT & SK_IORUN) + ; +} /* End of SK_write_reg */ + + + +/* + * Debugging functions + * ------------------- + */ + +/*- + * Function : SK_print_pos + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/25 + * + * Description : This function prints out the 4 POS (Programmable + * Option Select) Registers. Used mainly to debug operation. + * + * Parameters : I : struct device *dev - SK_G16 device structure + * I : char * - Text which will be printed as title + * Return Value : None + * Errors : None + * Update History : + * YY/MM/DD uid Description +-*/ + +void SK_print_pos(struct device *dev, char *text) +{ + int ioaddr = dev->base_addr; + + unsigned char pos0 = inb(SK_POS0), + pos1 = inb(SK_POS1), + pos2 = inb(SK_POS2), + pos3 = inb(SK_POS3), + pos4 = inb(SK_POS4); + + + printk("## %s: %s.\n" + "## pos0=%#4x pos1=%#4x pos2=%#04x pos3=%#08x pos4=%#04x\n", + SK_NAME, text, pos0, pos1, pos2, (pos3<<14), pos4); + +} /* End of SK_print_pos() */ + + + +/*- + * Function : SK_print_dev + * Author : Patrick J.D. Weichmann + * Date Created : 94/05/25 + * + * Description : This function simply prints out the important fields + * of the device structure. + * + * Parameters : I : struct device *dev - SK_G16 device structure + * I : char *text - Title for printing + * Return Value : None + * Errors : None + * Update History : + * YY/MM/DD uid Description +-*/ + +void SK_print_dev(struct device *dev, char *text) +{ + if (dev == NULL) + { + printk("## %s: Device Structure. %s\n", SK_NAME, text); + printk("## DEVICE == NULL\n"); + } + else + { + printk("## %s: Device Structure. %s\n", SK_NAME, text); + printk("## Device Name: %s Base Address: %#06lx IRQ: %d\n", + dev->name, dev->base_addr, dev->irq); + + printk("## FLAGS: start: %d tbusy: %ld int: %d\n", + dev->start, dev->tbusy, dev->interrupt); + + printk("## next device: %#08x init function: %#08x\n", + (int) dev->next, (int) dev->init); + } + +} /* End of SK_print_dev() */ + + + +/*- + * Function : SK_print_ram + * Author : Patrick J.D. Weichmann + * Date Created : 94/06/02 + * + * Description : This function is used to check how are things set up + * in the 16KB RAM. Also the pointers to the receive and + * transmit descriptor rings and rx and tx buffers locations. + * It contains a minor bug in printing, but has no effect to the values + * only newlines are not correct. + * + * Parameters : I : struct device *dev - SK_G16 device structure + * Return Value : None + * Errors : None + * Globals : None + * Update History : + * YY/MM/DD uid Description +-*/ + +void SK_print_ram(struct device *dev) +{ + + int i; + struct priv *p = (struct priv *) dev->priv; + + printk("## %s: RAM Details.\n" + "## RAM at %#08x tmdhead: %#08x rmdhead: %#08x initblock: %#08x\n", + SK_NAME, + (unsigned int) p->ram, + (unsigned int) p->tmdhead, + (unsigned int) p->rmdhead, + (unsigned int) &(p->ram)->ib); + + printk("## "); + + for(i = 0; i < TMDNUM; i++) + { + if (!(i % 3)) /* Every third line do a newline */ + { + printk("\n## "); + } + printk("tmdbufs%d: %#08x ", (i+1), (int) p->tmdbufs[i]); + } + printk("## "); + + for(i = 0; i < RMDNUM; i++) + { + if (!(i % 3)) /* Every third line do a newline */ + { + printk("\n## "); + } + printk("rmdbufs%d: %#08x ", (i+1), (int) p->rmdbufs[i]); + } + printk("\n"); + +} /* End of SK_print_ram() */ + diff --git a/linux/src/drivers/net/sk_g16.h b/linux/src/drivers/net/sk_g16.h new file mode 100644 index 00000000..28e907f3 --- /dev/null +++ b/linux/src/drivers/net/sk_g16.h @@ -0,0 +1,165 @@ +/*- + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * + * Module : sk_g16.h + * Version : $Revision: 1.1 $ + * + * Author : M.Hipp (mhipp@student.uni-tuebingen.de) + * changes by : Patrick J.D. Weichmann + * + * Date Created : 94/05/25 + * + * Description : In here are all necessary definitions of + * the am7990 (LANCE) chip used for writing a + * network device driver which uses this chip + * + * $Log$ +-*/ + +#ifndef SK_G16_H + +#define SK_G16_H + + +/* + * Control and Status Register 0 (CSR0) bit definitions + * + * (R=Readable) (W=Writeable) (S=Set on write) (C-Clear on write) + * + */ + +#define CSR0_ERR 0x8000 /* Error summary (R) */ +#define CSR0_BABL 0x4000 /* Babble transmitter timeout error (RC) */ +#define CSR0_CERR 0x2000 /* Collision Error (RC) */ +#define CSR0_MISS 0x1000 /* Missed packet (RC) */ +#define CSR0_MERR 0x0800 /* Memory Error (RC) */ +#define CSR0_RINT 0x0400 /* Receiver Interrupt (RC) */ +#define CSR0_TINT 0x0200 /* Transmit Interrupt (RC) */ +#define CSR0_IDON 0x0100 /* Initialization Done (RC) */ +#define CSR0_INTR 0x0080 /* Interrupt Flag (R) */ +#define CSR0_INEA 0x0040 /* Interrupt Enable (RW) */ +#define CSR0_RXON 0x0020 /* Receiver on (R) */ +#define CSR0_TXON 0x0010 /* Transmitter on (R) */ +#define CSR0_TDMD 0x0008 /* Transmit Demand (RS) */ +#define CSR0_STOP 0x0004 /* Stop (RS) */ +#define CSR0_STRT 0x0002 /* Start (RS) */ +#define CSR0_INIT 0x0001 /* Initialize (RS) */ + +#define CSR0_CLRALL 0x7f00 /* mask for all clearable bits */ + +/* + * Control and Status Register 3 (CSR3) bit definitions + * + */ + +#define CSR3_BSWAP 0x0004 /* Byte Swap (RW) */ +#define CSR3_ACON 0x0002 /* ALE Control (RW) */ +#define CSR3_BCON 0x0001 /* Byte Control (RW) */ + +/* + * Initialization Block Mode operation Bit Definitions. + */ + +#define MODE_PROM 0x8000 /* Promiscuous Mode */ +#define MODE_INTL 0x0040 /* Internal Loopback */ +#define MODE_DRTY 0x0020 /* Disable Retry */ +#define MODE_COLL 0x0010 /* Force Collision */ +#define MODE_DTCR 0x0008 /* Disable Transmit CRC) */ +#define MODE_LOOP 0x0004 /* Loopback */ +#define MODE_DTX 0x0002 /* Disable the Transmitter */ +#define MODE_DRX 0x0001 /* Disable the Receiver */ + +#define MODE_NORMAL 0x0000 /* Normal operation mode */ + +/* + * Receive message descriptor status bit definitions. + */ + +#define RX_OWN 0x80 /* Owner bit 0 = host, 1 = lance */ +#define RX_ERR 0x40 /* Error Summary */ +#define RX_FRAM 0x20 /* Framing Error */ +#define RX_OFLO 0x10 /* Overflow Error */ +#define RX_CRC 0x08 /* CRC Error */ +#define RX_BUFF 0x04 /* Buffer Error */ +#define RX_STP 0x02 /* Start of Packet */ +#define RX_ENP 0x01 /* End of Packet */ + + +/* + * Transmit message descriptor status bit definitions. + */ + +#define TX_OWN 0x80 /* Owner bit 0 = host, 1 = lance */ +#define TX_ERR 0x40 /* Error Summary */ +#define TX_MORE 0x10 /* More the 1 retry needed to Xmit */ +#define TX_ONE 0x08 /* One retry needed to Xmit */ +#define TX_DEF 0x04 /* Deferred */ +#define TX_STP 0x02 /* Start of Packet */ +#define TX_ENP 0x01 /* End of Packet */ + +/* + * Transmit status (2) (valid if TX_ERR == 1) + */ + +#define TX_BUFF 0x8000 /* Buffering error (no ENP) */ +#define TX_UFLO 0x4000 /* Underflow (late memory) */ +#define TX_LCOL 0x1000 /* Late collision */ +#define TX_LCAR 0x0400 /* Loss of Carrier */ +#define TX_RTRY 0x0200 /* Failed after 16 retransmissions */ +#define TX_TDR 0x003f /* Time-domain-reflectometer-value */ + + +/* + * Structures used for Communication with the LANCE + */ + +/* LANCE Initialize Block */ + +struct init_block +{ + unsigned short mode; /* Mode Register */ + unsigned char paddr[6]; /* Physical Address (MAC) */ + unsigned char laddr[8]; /* Logical Filter Address (not used) */ + unsigned int rdrp; /* Receive Descriptor Ring pointer */ + unsigned int tdrp; /* Transmit Descriptor Ring pointer */ +}; + + +/* Receive Message Descriptor Entry */ + +struct rmd +{ + union + { + unsigned long buffer; /* Address of buffer */ + struct + { + unsigned char unused[3]; + unsigned volatile char status; /* Status Bits */ + } s; + } u; + volatile short blen; /* Buffer Length (two's complement) */ + unsigned short mlen; /* Message Byte Count */ +}; + + +/* Transmit Message Descriptor Entry */ + +struct tmd +{ + union + { + unsigned long buffer; /* Address of buffer */ + struct + { + unsigned char unused[3]; + unsigned volatile char status; /* Status Bits */ + } s; + } u; + unsigned short blen; /* Buffer Length (two's complement) */ + unsigned volatile short status2; /* Error Status Bits */ +}; + +#endif /* End of SK_G16_H */ diff --git a/linux/src/drivers/net/smc-ultra.c b/linux/src/drivers/net/smc-ultra.c new file mode 100644 index 00000000..074a235b --- /dev/null +++ b/linux/src/drivers/net/smc-ultra.c @@ -0,0 +1,496 @@ +/* smc-ultra.c: A SMC Ultra ethernet driver for linux. */ +/* + This is a driver for the SMC Ultra and SMC EtherEZ ISA ethercards. + + Written 1993-1998 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This driver uses the cards in the 8390-compatible mode. + Most of the run-time complexity is handled by the generic code in + 8390.c. The code in this file is responsible for + + ultra_probe() Detecting and initializing the card. + ultra_probe1() + + ultra_open() The card-specific details of starting, stopping + ultra_reset_8390() and resetting the 8390 NIC core. + ultra_close() + + ultra_block_input() Routines for reading and writing blocks of + ultra_block_output() packet buffer memory. + ultra_pio_input() + ultra_pio_output() + + This driver enables the shared memory only when doing the actual data + transfers to avoid a bug in early version of the card that corrupted + data transferred by a AHA1542. + + This driver now supports the programmed-I/O (PIO) data transfer mode of + the EtherEZ. It does not use the non-8390-compatible "Altego" mode. + That support (if available) is in smc-ez.c. + + Changelog: + + Paul Gortmaker : multiple card support for module users. + Donald Becker : 4/17/96 PIO support, minor potential problems avoided. + Donald Becker : 6/6/96 correctly set auto-wrap bit. +*/ + +static const char *version = + "smc-ultra.c:v2.02 2/3/98 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include "8390.h" + +/* A zero-terminated list of I/O addresses to be probed. */ +static unsigned int ultra_portlist[] = +{0x200, 0x220, 0x240, 0x280, 0x300, 0x340, 0x380, 0}; + +int ultra_probe(struct device *dev); +int ultra_probe1(struct device *dev, int ioaddr); + +static int ultra_open(struct device *dev); +static void ultra_reset_8390(struct device *dev); +static void ultra_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void ultra_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void ultra_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static void ultra_pio_get_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void ultra_pio_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void ultra_pio_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static int ultra_close_card(struct device *dev); + + +#define START_PG 0x00 /* First page of TX buffer */ + +#define ULTRA_CMDREG 0 /* Offset to ASIC command register. */ +#define ULTRA_RESET 0x80 /* Board reset, in ULTRA_CMDREG. */ +#define ULTRA_MEMENB 0x40 /* Enable the shared memory. */ +#define IOPD 0x02 /* I/O Pipe Data (16 bits), PIO operation. */ +#define IOPA 0x07 /* I/O Pipe Address for PIO operation. */ +#define ULTRA_NIC_OFFSET 16 /* NIC register offset from the base_addr. */ +#define ULTRA_IO_EXTENT 32 +#define EN0_ERWCNT 0x08 /* Early receive warning count. */ + +/* Probe for the Ultra. This looks like a 8013 with the station + address PROM at I/O ports <base>+8 to <base>+13, with a checksum + following. +*/ +#ifdef HAVE_DEVLIST +struct netdev_entry ultra_drv = +{"ultra", ultra_probe1, NETCARD_IO_EXTENT, netcard_portlist}; +#else + +int ultra_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return ultra_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; ultra_portlist[i]; i++) { + int ioaddr = ultra_portlist[i]; + if (check_region(ioaddr, ULTRA_IO_EXTENT)) + continue; + if (ultra_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +int ultra_probe1(struct device *dev, int ioaddr) +{ + int i; + int checksum = 0; + const char *model_name; + unsigned char eeprom_irq = 0; + static unsigned version_printed = 0; + /* Values from various config regs. */ + unsigned char num_pages, irqreg, addr, piomode; + unsigned char idreg = inb(ioaddr + 7); + unsigned char reg4 = inb(ioaddr + 4) & 0x7f; + + /* Check the ID nibble. */ + if ((idreg & 0xF0) != 0x20 /* SMC Ultra */ + && (idreg & 0xF0) != 0x40) /* SMC EtherEZ */ + return ENODEV; + + /* Select the station address register set. */ + outb(reg4, ioaddr + 4); + + for (i = 0; i < 8; i++) + checksum += inb(ioaddr + 8 + i); + if ((checksum & 0xff) != 0xFF) + return ENODEV; + + if (dev == NULL) + dev = init_etherdev(0, 0); + + if (ei_debug && version_printed++ == 0) + printk(version); + + model_name = (idreg & 0xF0) == 0x20 ? "SMC Ultra" : "SMC EtherEZ"; + + printk("%s: %s at %#3x,", dev->name, model_name, ioaddr); + + for (i = 0; i < 6; i++) + printk(" %2.2X", dev->dev_addr[i] = inb(ioaddr + 8 + i)); + + /* Switch from the station address to the alternate register set and + read the useful registers there. */ + outb(0x80 | reg4, ioaddr + 4); + + /* Enabled FINE16 mode to avoid BIOS ROM width mismatches @ reboot. */ + outb(0x80 | inb(ioaddr + 0x0c), ioaddr + 0x0c); + piomode = inb(ioaddr + 0x8); + addr = inb(ioaddr + 0xb); + irqreg = inb(ioaddr + 0xd); + + /* Switch back to the station address register set so that the MS-DOS driver + can find the card after a warm boot. */ + outb(reg4, ioaddr + 4); + + if (dev->irq < 2) { + unsigned char irqmap[] = {0, 9, 3, 5, 7, 10, 11, 15}; + int irq; + + /* The IRQ bits are split. */ + irq = irqmap[((irqreg & 0x40) >> 4) + ((irqreg & 0x0c) >> 2)]; + + if (irq == 0) { + printk(", failed to detect IRQ line.\n"); + return -EAGAIN; + } + dev->irq = irq; + eeprom_irq = 1; + } + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk (", no memory for dev->priv.\n"); + return -ENOMEM; + } + + /* OK, we are certain this is going to work. Setup the device. */ + request_region(ioaddr, ULTRA_IO_EXTENT, model_name); + + /* The 8390 isn't at the base address, so fake the offset */ + dev->base_addr = ioaddr+ULTRA_NIC_OFFSET; + + { + int addr_tbl[4] = {0x0C0000, 0x0E0000, 0xFC0000, 0xFE0000}; + short num_pages_tbl[4] = {0x20, 0x40, 0x80, 0xff}; + + dev->mem_start = ((addr & 0x0f) << 13) + addr_tbl[(addr >> 6) & 3] ; + num_pages = num_pages_tbl[(addr >> 4) & 3]; + } + + ei_status.name = model_name; + ei_status.word16 = 1; + ei_status.tx_start_page = START_PG; + ei_status.rx_start_page = START_PG + TX_PAGES; + ei_status.stop_page = num_pages; + + dev->rmem_start = dev->mem_start + TX_PAGES*256; + dev->mem_end = dev->rmem_end + = dev->mem_start + (ei_status.stop_page - START_PG)*256; + + if (piomode) { + printk(",%s IRQ %d programmed-I/O mode.\n", + eeprom_irq ? "EEPROM" : "assigned ", dev->irq); + ei_status.block_input = &ultra_pio_input; + ei_status.block_output = &ultra_pio_output; + ei_status.get_8390_hdr = &ultra_pio_get_hdr; + } else { + printk(",%s IRQ %d memory %#lx-%#lx.\n", eeprom_irq ? "" : "assigned ", + dev->irq, dev->mem_start, dev->mem_end-1); + ei_status.block_input = &ultra_block_input; + ei_status.block_output = &ultra_block_output; + ei_status.get_8390_hdr = &ultra_get_8390_hdr; + } + ei_status.reset_8390 = &ultra_reset_8390; + dev->open = &ultra_open; + dev->stop = &ultra_close_card; + NS8390_init(dev, 0); + + return 0; +} + +static int +ultra_open(struct device *dev) +{ + int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; /* ASIC addr */ + unsigned char irq2reg[] = {0, 0, 0x04, 0x08, 0, 0x0C, 0, 0x40, + 0, 0x04, 0x44, 0x48, 0, 0, 0, 0x4C, }; + + if (request_irq(dev->irq, ei_interrupt, 0, ei_status.name, dev)) + return -EAGAIN; + + outb(0x00, ioaddr); /* Disable shared memory for safety. */ + outb(0x80, ioaddr + 5); + /* Set the IRQ line. */ + outb(inb(ioaddr + 4) | 0x80, ioaddr + 4); + outb((inb(ioaddr + 13) & ~0x4C) | irq2reg[dev->irq], ioaddr + 13); + outb(inb(ioaddr + 4) & 0x7f, ioaddr + 4); + + if (ei_status.block_input == &ultra_pio_input) { + outb(0x11, ioaddr + 6); /* Enable interrupts and PIO. */ + outb(0x01, ioaddr + 0x19); /* Enable ring read auto-wrap. */ + } else + outb(0x01, ioaddr + 6); /* Enable interrupts and memory. */ + /* Set the early receive warning level in window 0 high enough not + to receive ERW interrupts. */ + outb_p(E8390_NODMA+E8390_PAGE0, dev->base_addr); + outb(0xff, dev->base_addr + EN0_ERWCNT); + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static void +ultra_reset_8390(struct device *dev) +{ + int cmd_port = dev->base_addr - ULTRA_NIC_OFFSET; /* ASIC base addr */ + + outb(ULTRA_RESET, cmd_port); + if (ei_debug > 1) printk("resetting Ultra, t=%ld...", jiffies); + ei_status.txing = 0; + + outb(0x00, cmd_port); /* Disable shared memory for safety. */ + outb(0x80, cmd_port + 5); + if (ei_status.block_input == &ultra_pio_input) + outb(0x11, cmd_port + 6); /* Enable interrupts and PIO. */ + else + outb(0x01, cmd_port + 6); /* Enable interrupts and memory. */ + + if (ei_debug > 1) printk("reset done\n"); + return; +} + +/* Grab the 8390 specific header. Similar to the block_input routine, but + we don't need to be concerned with ring wrap as the header will be at + the start of a page, so we optimize accordingly. */ + +static void +ultra_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + unsigned long hdr_start = dev->mem_start + ((ring_page - START_PG)<<8); + + outb(ULTRA_MEMENB, dev->base_addr - ULTRA_NIC_OFFSET); /* shmem on */ +#ifdef notdef + /* Officially this is what we are doing, but the readl() is faster */ + memcpy_fromio(hdr, hdr_start, sizeof(struct e8390_pkt_hdr)); +#else + ((unsigned int*)hdr)[0] = readl(hdr_start); +#endif + outb(0x00, dev->base_addr - ULTRA_NIC_OFFSET); /* shmem off */ +} + +/* Block input and output are easy on shared memory ethercards, the only + complication is when the ring buffer wraps. */ + +static void +ultra_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + unsigned long xfer_start = dev->mem_start + ring_offset - (START_PG<<8); + + /* Enable shared memory. */ + outb(ULTRA_MEMENB, dev->base_addr - ULTRA_NIC_OFFSET); + + if (xfer_start + count > dev->rmem_end) { + /* We must wrap the input move. */ + int semi_count = dev->rmem_end - xfer_start; + memcpy_fromio(skb->data, xfer_start, semi_count); + count -= semi_count; + memcpy_fromio(skb->data + semi_count, dev->rmem_start, count); + } else { + /* Packet is in one chunk -- we can copy + cksum. */ + eth_io_copy_and_sum(skb, xfer_start, count, 0); + } + + outb(0x00, dev->base_addr - ULTRA_NIC_OFFSET); /* Disable memory. */ +} + +static void +ultra_block_output(struct device *dev, int count, const unsigned char *buf, + int start_page) +{ + unsigned long shmem = dev->mem_start + ((start_page - START_PG)<<8); + + /* Enable shared memory. */ + outb(ULTRA_MEMENB, dev->base_addr - ULTRA_NIC_OFFSET); + + memcpy_toio(shmem, buf, count); + + outb(0x00, dev->base_addr - ULTRA_NIC_OFFSET); /* Disable memory. */ +} + +/* The identical operations for programmed I/O cards. + The PIO model is trivial to use: the 16 bit start address is written + byte-sequentially to IOPA, with no intervening I/O operations, and the + data is read or written to the IOPD data port. + The only potential complication is that the address register is shared + and must be always be rewritten between each read/write direction change. + This is no problem for us, as the 8390 code ensures that we are single + threaded. */ +static void ultra_pio_get_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page) +{ + int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; /* ASIC addr */ + outb(0x00, ioaddr + IOPA); /* Set the address, LSB first. */ + outb(ring_page, ioaddr + IOPA); + insw(ioaddr + IOPD, hdr, sizeof(struct e8390_pkt_hdr)>>1); +} + +static void ultra_pio_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; /* ASIC addr */ + char *buf = skb->data; + + /* For now set the address again, although it should already be correct. */ + outb(ring_offset, ioaddr + IOPA); /* Set the address, LSB first. */ + outb(ring_offset >> 8, ioaddr + IOPA); + /* We know skbuffs are padded to at least word alignment. */ + insw(ioaddr + IOPD, buf, (count+1)>>1); +} + +static void ultra_pio_output(struct device *dev, int count, + const unsigned char *buf, const int start_page) +{ + int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; /* ASIC addr */ + outb(0x00, ioaddr + IOPA); /* Set the address, LSB first. */ + outb(start_page, ioaddr + IOPA); + /* An extra odd byte is OK here as well. */ + outsw(ioaddr + IOPD, buf, (count+1)>>1); +} + +static int +ultra_close_card(struct device *dev) +{ + int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; /* CMDREG */ + + dev->start = 0; + dev->tbusy = 1; + + if (ei_debug > 1) + printk("%s: Shutting down ethercard.\n", dev->name); + + outb(0x00, ioaddr + 6); /* Disable interrupts. */ + free_irq(dev->irq, dev); + irq2dev_map[dev->irq] = 0; + + NS8390_init(dev, 0); + + /* We should someday disable shared memory and change to 8-bit mode + "just in case"... */ + + MOD_DEC_USE_COUNT; + + return 0; +} + + +#ifdef MODULE +#define MAX_ULTRA_CARDS 4 /* Max number of Ultra cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_ULTRA_CARDS] = { 0, }; +static struct device dev_ultra[MAX_ULTRA_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_ULTRA_CARDS] = { 0, }; +static int irq[MAX_ULTRA_CARDS] = { 0, }; + +/* This is set up so that only a single autoprobe takes place per call. +ISA device autoprobes on a running machine are not recommended. */ +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_ULTRA_CARDS; this_dev++) { + struct device *dev = &dev_ultra[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->init = ultra_probe; + if (io[this_dev] == 0) { + if (this_dev != 0) break; /* only autoprobe 1st one */ + printk(KERN_NOTICE "smc-ultra.c: Presently autoprobing (not recommended) for a single card.\n"); + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "smc-ultra.c: No SMC Ultra card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_ULTRA_CARDS; this_dev++) { + struct device *dev = &dev_ultra[this_dev]; + if (dev->priv != NULL) { + /* NB: ultra_close_card() does free_irq + irq2dev */ + int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; + kfree(dev->priv); + dev->priv = NULL; + release_region(ioaddr, ULTRA_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -Wall -O6 -I/usr/src/linux/net/inet -c smc-ultra.c" + * version-control: t + * kept-new-versions: 5 + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/smc-ultra32.c b/linux/src/drivers/net/smc-ultra32.c new file mode 100644 index 00000000..f616e259 --- /dev/null +++ b/linux/src/drivers/net/smc-ultra32.c @@ -0,0 +1,413 @@ +/* smc-ultra32.c: An SMC Ultra32 EISA ethernet driver for linux. + +Sources: + + This driver is based on (cloned from) the ISA SMC Ultra driver + written by Donald Becker. Modifications to support the EISA + version of the card by Paul Gortmaker and Leonard N. Zubkoff. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + +Theory of Operation: + + The SMC Ultra32C card uses the SMC 83c790 chip which is also + found on the ISA SMC Ultra cards. It has a shared memory mode of + operation that makes it similar to the ISA version of the card. + The main difference is that the EISA card has 32KB of RAM, but + only an 8KB window into that memory. The EISA card also can be + set for a bus-mastering mode of operation via the ECU, but that + is not (and probably will never be) supported by this driver. + The ECU should be run to enable shared memory and to disable the + bus-mastering feature for use with linux. + + By programming the 8390 to use only 8KB RAM, the modifications + to the ISA driver can be limited to the probe and initialization + code. This allows easy integration of EISA support into the ISA + driver. However, the driver development kit from SMC provided the + register information for sliding the 8KB window, and hence the 8390 + is programmed to use the full 32KB RAM. + + Unfortunately this required code changes outside the probe/init + routines, and thus we decided to separate the EISA driver from + the ISA one. In this way, ISA users don't end up with a larger + driver due to the EISA code, and EISA users don't end up with a + larger driver due to the ISA EtherEZ PIO code. The driver is + similar to the 3c503/16 driver, in that the window must be set + back to the 1st 8KB of space for access to the two 8390 Tx slots. + + In testing, using only 8KB RAM (3 Tx / 5 Rx) didn't appear to + be a limiting factor, since the EISA bus could get packets off + the card fast enough, but having the use of lots of RAM as Rx + space is extra insurance if interrupt latencies become excessive. + +*/ + +static const char *version = "smc-ultra32.c: 06/97 v1.00\n"; + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> + +#include <asm/io.h> +#include <asm/system.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include "8390.h" + +int ultra32_probe(struct device *dev); +int ultra32_probe1(struct device *dev, int ioaddr); +static int ultra32_open(struct device *dev); +static void ultra32_reset_8390(struct device *dev); +static void ultra32_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void ultra32_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void ultra32_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static int ultra32_close(struct device *dev); + +#define ULTRA32_CMDREG 0 /* Offset to ASIC command register. */ +#define ULTRA32_RESET 0x80 /* Board reset, in ULTRA32_CMDREG. */ +#define ULTRA32_MEMENB 0x40 /* Enable the shared memory. */ +#define ULTRA32_NIC_OFFSET 16 /* NIC register offset from the base_addr. */ +#define ULTRA32_IO_EXTENT 32 +#define EN0_ERWCNT 0x08 /* Early receive warning count. */ + +/* + * Defines that apply only to the Ultra32 EISA card. Note that + * "smc" = 10011 01101 00011 = 0x4da3, and hence !smc8010.cfg translates + * into an EISA ID of 0x1080A34D + */ +#define ULTRA32_BASE 0xca0 +#define ULTRA32_ID 0x1080a34d +#define ULTRA32_IDPORT (-0x20) /* 0xc80 */ +/* Config regs 1->7 from the EISA !SMC8010.CFG file. */ +#define ULTRA32_CFG1 0x04 /* 0xca4 */ +#define ULTRA32_CFG2 0x05 /* 0xca5 */ +#define ULTRA32_CFG3 (-0x18) /* 0xc88 */ +#define ULTRA32_CFG4 (-0x17) /* 0xc89 */ +#define ULTRA32_CFG5 (-0x16) /* 0xc8a */ +#define ULTRA32_CFG6 (-0x15) /* 0xc8b */ +#define ULTRA32_CFG7 0x0d /* 0xcad */ + + +/* Probe for the Ultra32. This looks like a 8013 with the station + address PROM at I/O ports <base>+8 to <base>+13, with a checksum + following. +*/ + +int ultra32_probe(struct device *dev) +{ + const char *ifmap[] = {"UTP No Link", "", "UTP/AUI", "UTP/BNC"}; + int ioaddr, edge, media; + + if (!EISA_bus) return ENODEV; + + /* EISA spec allows for up to 16 slots, but 8 is typical. */ + for (ioaddr = 0x1000 + ULTRA32_BASE; ioaddr < 0x9000; ioaddr += 0x1000) + if (check_region(ioaddr, ULTRA32_IO_EXTENT) == 0 && + inb(ioaddr + ULTRA32_IDPORT) != 0xff && + inl(ioaddr + ULTRA32_IDPORT) == ULTRA32_ID) { + media = inb(ioaddr + ULTRA32_CFG7) & 0x03; + edge = inb(ioaddr + ULTRA32_CFG5) & 0x08; + printk("SMC Ultra32 in EISA Slot %d, Media: %s, %s IRQs.\n", + ioaddr >> 12, ifmap[media], + (edge ? "Edge Triggered" : "Level Sensitive")); + if (ultra32_probe1(dev, ioaddr) == 0) + return 0; + } + return ENODEV; +} + +int ultra32_probe1(struct device *dev, int ioaddr) +{ + int i; + int checksum = 0; + const char *model_name; + static unsigned version_printed = 0; + /* Values from various config regs. */ + unsigned char idreg = inb(ioaddr + 7); + unsigned char reg4 = inb(ioaddr + 4) & 0x7f; + + /* Check the ID nibble. */ + if ((idreg & 0xf0) != 0x20) /* SMC Ultra */ + return ENODEV; + + /* Select the station address register set. */ + outb(reg4, ioaddr + 4); + + for (i = 0; i < 8; i++) + checksum += inb(ioaddr + 8 + i); + if ((checksum & 0xff) != 0xff) + return ENODEV; + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk("smc-ultra32.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + if (ei_debug && version_printed++ == 0) + printk(version); + + model_name = "SMC Ultra32"; + + printk("%s: %s at 0x%X,", dev->name, model_name, ioaddr); + + for (i = 0; i < 6; i++) + printk(" %2.2X", dev->dev_addr[i] = inb(ioaddr + 8 + i)); + + /* Switch from the station address to the alternate register set and + read the useful registers there. */ + outb(0x80 | reg4, ioaddr + 4); + + /* Enable FINE16 mode to avoid BIOS ROM width mismatches @ reboot. */ + outb(0x80 | inb(ioaddr + 0x0c), ioaddr + 0x0c); + + /* Reset RAM addr. */ + outb(0x00, ioaddr + 0x0b); + + /* Switch back to the station address register set so that the + MS-DOS driver can find the card after a warm boot. */ + outb(reg4, ioaddr + 4); + + if ((inb(ioaddr + ULTRA32_CFG5) & 0x40) == 0) { + printk("\nsmc-ultra32: Card RAM is disabled! " + "Run EISA config utility.\n"); + return ENODEV; + } + if ((inb(ioaddr + ULTRA32_CFG2) & 0x04) == 0) + printk("\nsmc-ultra32: Ignoring Bus-Master enable bit. " + "Run EISA config utility.\n"); + + if (dev->irq < 2) { + unsigned char irqmap[] = {0, 9, 3, 5, 7, 10, 11, 15}; + int irq = irqmap[inb(ioaddr + ULTRA32_CFG5) & 0x07]; + if (irq == 0) { + printk(", failed to detect IRQ line.\n"); + return -EAGAIN; + } + dev->irq = irq; + } + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk (", no memory for dev->priv.\n"); + return -ENOMEM; + } + + /* OK, we are certain this is going to work. Setup the device. */ + request_region(ioaddr, ULTRA32_IO_EXTENT, model_name); + + /* The 8390 isn't at the base address, so fake the offset */ + dev->base_addr = ioaddr + ULTRA32_NIC_OFFSET; + + /* Save RAM address in the unused reg0 to avoid excess inb's. */ + ei_status.reg0 = inb(ioaddr + ULTRA32_CFG3) & 0xfc; + + dev->mem_start = 0xc0000 + ((ei_status.reg0 & 0x7c) << 11); + + ei_status.name = model_name; + ei_status.word16 = 1; + ei_status.tx_start_page = 0; + ei_status.rx_start_page = TX_PAGES; + /* All Ultra32 cards have 32KB memory with an 8KB window. */ + ei_status.stop_page = 128; + + dev->rmem_start = dev->mem_start + TX_PAGES*256; + dev->mem_end = dev->rmem_end = dev->mem_start + 0x1fff; + + printk(", IRQ %d, 32KB memory, 8KB window at 0x%lx-0x%lx.\n", + dev->irq, dev->mem_start, dev->mem_end); + ei_status.block_input = &ultra32_block_input; + ei_status.block_output = &ultra32_block_output; + ei_status.get_8390_hdr = &ultra32_get_8390_hdr; + ei_status.reset_8390 = &ultra32_reset_8390; + dev->open = &ultra32_open; + dev->stop = &ultra32_close; + NS8390_init(dev, 0); + + return 0; +} + +static int ultra32_open(struct device *dev) +{ + int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET; /* ASIC addr */ + + if (request_irq(dev->irq, ei_interrupt, 0, ei_status.name, dev)) + return -EAGAIN; + + outb(ULTRA32_MEMENB, ioaddr); /* Enable Shared Memory. */ + outb(0x80, ioaddr + ULTRA32_CFG6); /* Enable Interrupts. */ + outb(0x84, ioaddr + 5); /* Enable MEM16 & Disable Bus Master. */ + outb(0x01, ioaddr + 6); /* Enable Interrupts. */ + /* Set the early receive warning level in window 0 high enough not + to receive ERW interrupts. */ + outb_p(E8390_NODMA+E8390_PAGE0, dev->base_addr); + outb(0xff, dev->base_addr + EN0_ERWCNT); + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static int ultra32_close(struct device *dev) +{ + int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET; /* CMDREG */ + + dev->start = 0; + dev->tbusy = 1; + + if (ei_debug > 1) + printk("%s: Shutting down ethercard.\n", dev->name); + + outb(0x00, ioaddr + ULTRA32_CFG6); /* Disable Interrupts. */ + outb(0x00, ioaddr + 6); /* Disable interrupts. */ + free_irq(dev->irq, dev); + irq2dev_map[dev->irq] = 0; + + NS8390_init(dev, 0); + + MOD_DEC_USE_COUNT; + + return 0; +} + +static void ultra32_reset_8390(struct device *dev) +{ + int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET; /* ASIC base addr */ + + outb(ULTRA32_RESET, ioaddr); + if (ei_debug > 1) printk("resetting Ultra32, t=%ld...", jiffies); + ei_status.txing = 0; + + outb(ULTRA32_MEMENB, ioaddr); /* Enable Shared Memory. */ + outb(0x80, ioaddr + ULTRA32_CFG6); /* Enable Interrupts. */ + outb(0x84, ioaddr + 5); /* Enable MEM16 & Disable Bus Master. */ + outb(0x01, ioaddr + 6); /* Enable Interrupts. */ + if (ei_debug > 1) printk("reset done\n"); + return; +} + +/* Grab the 8390 specific header. Similar to the block_input routine, but + we don't need to be concerned with ring wrap as the header will be at + the start of a page, so we optimize accordingly. */ + +static void ultra32_get_8390_hdr(struct device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + unsigned long hdr_start = dev->mem_start + ((ring_page & 0x1f) << 8); + unsigned int RamReg = dev->base_addr - ULTRA32_NIC_OFFSET + ULTRA32_CFG3; + + /* Select correct 8KB Window. */ + outb(ei_status.reg0 | ((ring_page & 0x60) >> 5), RamReg); + +#ifdef notdef + /* Officially this is what we are doing, but the readl() is faster */ + memcpy_fromio(hdr, hdr_start, sizeof(struct e8390_pkt_hdr)); +#else + ((unsigned int*)hdr)[0] = readl(hdr_start); +#endif +} + +/* Block input and output are easy on shared memory ethercards, the only + complication is when the ring buffer wraps, or in this case, when a + packet spans an 8KB boundary. Note that the current 8KB segment is + already set by the get_8390_hdr routine. */ + +static void ultra32_block_input(struct device *dev, + int count, + struct sk_buff *skb, + int ring_offset) +{ + unsigned long xfer_start = dev->mem_start + (ring_offset & 0x1fff); + unsigned int RamReg = dev->base_addr - ULTRA32_NIC_OFFSET + ULTRA32_CFG3; + + if ((ring_offset & ~0x1fff) != ((ring_offset + count - 1) & ~0x1fff)) { + int semi_count = 8192 - (ring_offset & 0x1FFF); + memcpy_fromio(skb->data, xfer_start, semi_count); + count -= semi_count; + if (ring_offset < 96*256) { + /* Select next 8KB Window. */ + ring_offset += semi_count; + outb(ei_status.reg0 | ((ring_offset & 0x6000) >> 13), RamReg); + memcpy_fromio(skb->data + semi_count, dev->mem_start, count); + } else { + /* Select first 8KB Window. */ + outb(ei_status.reg0, RamReg); + memcpy_fromio(skb->data + semi_count, dev->rmem_start, count); + } + } else { + /* Packet is in one chunk -- we can copy + cksum. */ + eth_io_copy_and_sum(skb, xfer_start, count, 0); + } +} + +static void ultra32_block_output(struct device *dev, + int count, + const unsigned char *buf, + int start_page) +{ + unsigned long xfer_start = dev->mem_start + (start_page<<8); + unsigned int RamReg = dev->base_addr - ULTRA32_NIC_OFFSET + ULTRA32_CFG3; + + /* Select first 8KB Window. */ + outb(ei_status.reg0, RamReg); + + memcpy_toio(xfer_start, buf, count); +} + +#ifdef MODULE +#define MAX_ULTRA32_CARDS 4 /* Max number of Ultra cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_ULTRA32_CARDS] = { 0, }; +static struct device dev_ultra[MAX_ULTRA32_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +int init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_ULTRA32_CARDS; this_dev++) { + struct device *dev = &dev_ultra[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->init = ultra32_probe; + if (register_netdev(dev) != 0) { + if (found > 0) return 0; /* Got at least one. */ + printk(KERN_WARNING "smc-ultra32.c: No SMC Ultra32 found.\n"); + return -ENXIO; + } + found++; + } + + return 0; +} + +void cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_ULTRA32_CARDS; this_dev++) { + struct device *dev = &dev_ultra[this_dev]; + if (dev->priv != NULL) { + /* NB: ultra32_close_card() does free_irq + irq2dev */ + int ioaddr = dev->base_addr - ULTRA32_NIC_OFFSET; + kfree(dev->priv); + dev->priv = NULL; + release_region(ioaddr, ULTRA32_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ diff --git a/linux/src/drivers/net/smc9194.c b/linux/src/drivers/net/smc9194.c new file mode 100644 index 00000000..e3d648d0 --- /dev/null +++ b/linux/src/drivers/net/smc9194.c @@ -0,0 +1,1779 @@ +/*------------------------------------------------------------------------ + . smc9194.c + . This is a driver for SMC's 9000 series of Ethernet cards. + . + . Copyright (C) 1996 by Erik Stahlman + . This software may be used and distributed according to the terms + . of the GNU Public License, incorporated herein by reference. + . + . "Features" of the SMC chip: + . 4608 byte packet memory. ( for the 91C92. Others have more ) + . EEPROM for configuration + . AUI/TP selection ( mine has 10Base2/10BaseT select ) + . + . Arguments: + . io = for the base address + . irq = for the IRQ + . ifport = 0 for autodetect, 1 for TP, 2 for AUI ( or 10base2 ) + . + . author: + . Erik Stahlman ( erik@vt.edu ) + . + . Hardware multicast code from Peter Cammaert ( pc@denkart.be ) + . + . Sources: + . o SMC databook + . o skeleton.c by Donald Becker ( becker@cesdis.gsfc.nasa.gov ) + . o ( a LOT of advice from Becker as well ) + . + . History: + . 12/07/95 Erik Stahlman written, got receive/xmit handled + . 01/03/96 Erik Stahlman worked out some bugs, actually usable!!! :-) + . 01/06/96 Erik Stahlman cleaned up some, better testing, etc + . 01/29/96 Erik Stahlman fixed autoirq, added multicast + . 02/01/96 Erik Stahlman 1. disabled all interrupts in smc_reset + . 2. got rid of post-decrementing bug -- UGH. + . 02/13/96 Erik Stahlman Tried to fix autoirq failure. Added more + . descriptive error messages. + . 02/15/96 Erik Stahlman Fixed typo that caused detection failure + . 02/23/96 Erik Stahlman Modified it to fit into kernel tree + . Added support to change hardware address + . Cleared stats on opens + . 02/26/96 Erik Stahlman Trial support for Kernel 1.2.13 + . Kludge for automatic IRQ detection + . 03/04/96 Erik Stahlman Fixed kernel 1.3.70 + + . Fixed bug reported by Gardner Buchanan in + . smc_enable, with outw instead of outb + . 03/06/96 Erik Stahlman Added hardware multicast from Peter Cammaert + ----------------------------------------------------------------------------*/ + +static const char *version = + "smc9194.c:v0.12 03/06/96 by Erik Stahlman (erik@vt.edu)\n"; + +#ifdef MODULE +#include <linux/module.h> +#include <linux/version.h> +#endif + +#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/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "smc9194.h" +/*------------------------------------------------------------------------ + . + . Configuration options, for the experienced user to change. + . + -------------------------------------------------------------------------*/ + +/* + . this is for kernels > 1.2.70 +*/ +#define REALLY_NEW_KERNEL +#ifndef REALLY_NEW_KERNEL +#define free_irq( x, y ) free_irq( x ) +#define request_irq( x, y, z, u, v ) request_irq( x, y, z, u ) +#endif + +/* + . Do you want to use this with old kernels. + . WARNING: this is not well tested. +#define SUPPORT_OLD_KERNEL +*/ + + +/* + . Do you want to use 32 bit xfers? This should work on all chips, as + . the chipset is designed to accommodate them. +*/ +#define USE_32_BIT 1 + +/* + .the SMC9194 can be at any of the following port addresses. To change, + .for a slightly different card, you can add it to the array. Keep in + .mind that the array must end in zero. +*/ +static unsigned int smc_portlist[] = + { 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, + 0x300, 0x320, 0x340, 0x360, 0x380, 0x3A0, 0x3C0, 0x3E0, 0}; + +/* + . Wait time for memory to be free. This probably shouldn't be + . tuned that much, as waiting for this means nothing else happens + . in the system +*/ +#define MEMORY_WAIT_TIME 16 + +/* + . DEBUGGING LEVELS + . + . 0 for normal operation + . 1 for slightly more details + . >2 for various levels of increasingly useless information + . 2 for interrupt tracking, status flags + . 3 for packet dumps, etc. +*/ +#define SMC_DEBUG 0 + +#if (SMC_DEBUG > 2 ) +#define PRINTK3(x) printk x +#else +#define PRINTK3(x) +#endif + +#if SMC_DEBUG > 1 +#define PRINTK2(x) printk x +#else +#define PRINTK2(x) +#endif + +#ifdef SMC_DEBUG +#define PRINTK(x) printk x +#else +#define PRINTK(x) +#endif + + +/* the older versions of the kernel cannot support autoprobing */ +#ifdef SUPPORT_OLD_KERNEL +#define NO_AUTOPROBE +#endif + + +/*------------------------------------------------------------------------ + . + . The internal workings of the driver. If you are changing anything + . here with the SMC stuff, you should have the datasheet and known + . what you are doing. + . + -------------------------------------------------------------------------*/ +#define CARDNAME "SMC9194" + +#ifdef SUPPORT_OLD_KERNEL +char kernel_version[] = UTS_RELEASE; +#endif + +/* store this information for the driver.. */ +struct smc_local { + /* + these are things that the kernel wants me to keep, so users + can find out semi-useless statistics of how well the card is + performing + */ + struct enet_statistics stats; + + /* + If I have to wait until memory is available to send + a packet, I will store the skbuff here, until I get the + desired memory. Then, I'll send it out and free it. + */ + struct sk_buff * saved_skb; + + /* + . This keeps track of how many packets that I have + . sent out. When an TX_EMPTY interrupt comes, I know + . that all of these have been sent. + */ + int packets_waiting; +}; + + +/*----------------------------------------------------------------- + . + . The driver can be entered at any of the following entry points. + . + .------------------------------------------------------------------ */ + +/* + . This is called by register_netdev(). It is responsible for + . checking the portlist for the SMC9000 series chipset. If it finds + . one, then it will initialize the device, find the hardware information, + . and sets up the appropriate device parameters. + . NOTE: Interrupts are *OFF* when this procedure is called. + . + . NB:This shouldn't be static since it is referred to externally. +*/ +int smc_init(struct device *dev); + +/* + . The kernel calls this function when someone wants to use the device, + . typically 'ifconfig ethX up'. +*/ +static int smc_open(struct device *dev); + +/* + . This is called by the kernel to send a packet out into the net. it's + . responsible for doing a best-effort send, but if it's simply not possible + . to send it, the packet gets dropped. +*/ +static int smc_send_packet(struct sk_buff *skb, struct device *dev); + +/* + . This is called by the kernel in response to 'ifconfig ethX down'. It + . is responsible for cleaning up everything that the open routine + . does, and maybe putting the card into a powerdown state. +*/ +static int smc_close(struct device *dev); + +/* + . This routine allows the proc file system to query the driver's + . statistics. +*/ +static struct enet_statistics * smc_query_statistics( struct device *dev); + +/* + . Finally, a call to set promiscuous mode ( for TCPDUMP and related + . programs ) and multicast modes. +*/ +#ifdef SUPPORT_OLD_KERNEL +static void smc_set_multicast_list(struct device *dev, int num_addrs, + void *addrs); +#else +static void smc_set_multicast_list(struct device *dev); +#endif + +/*--------------------------------------------------------------- + . + . Interrupt level calls.. + . + ----------------------------------------------------------------*/ + +/* + . Handles the actual interrupt +*/ +#ifdef REALLY_NEW_KERNEL +static void smc_interrupt(int irq, void *, struct pt_regs *regs); +#else +static void smc_interrupt(int irq, struct pt_regs *regs); +#endif +/* + . This is a separate procedure to handle the receipt of a packet, to + . leave the interrupt code looking slightly cleaner +*/ +inline static void smc_rcv( struct device *dev ); +/* + . This handles a TX interrupt, which is only called when an error + . relating to a packet is sent. +*/ +inline static void smc_tx( struct device * dev ); + +/* + ------------------------------------------------------------ + . + . Internal routines + . + ------------------------------------------------------------ +*/ + +/* + . Test if a given location contains a chip, trying to cause as + . little damage as possible if it's not a SMC chip. +*/ +static int smc_probe( int ioaddr ); + +/* + . this routine initializes the cards hardware, prints out the configuration + . to the system log as well as the vanity message, and handles the setup + . of a device parameter. + . It will give an error if it can't initialize the card. +*/ +static int smc_initcard( struct device *, int ioaddr ); + +/* + . A rather simple routine to print out a packet for debugging purposes. +*/ +#if SMC_DEBUG > 2 +static void print_packet( byte *, int ); +#endif + +#define tx_done(dev) 1 + +/* this is called to actually send the packet to the chip */ +static void smc_hardware_send_packet( struct device * dev ); + +/* Since I am not sure if I will have enough room in the chip's ram + . to store the packet, I call this routine, which either sends it + . now, or generates an interrupt when the card is ready for the + . packet */ +static int smc_wait_to_send_packet( struct sk_buff * skb, struct device *dev ); + +/* this does a soft reset on the device */ +static void smc_reset( int ioaddr ); + +/* Enable Interrupts, Receive, and Transmit */ +static void smc_enable( int ioaddr ); + +/* this puts the device in an inactive state */ +static void smc_shutdown( int ioaddr ); + +#ifndef NO_AUTOPROBE +/* This routine will find the IRQ of the driver if one is not + . specified in the input to the device. */ +static int smc_findirq( int ioaddr ); +#endif + +/* + this routine will set the hardware multicast table to the specified + values given it by the higher level routines +*/ +#ifndef SUPPORT_OLD_KERNEL +static void smc_setmulticast( int ioaddr, int count, struct dev_mc_list * ); +static int crc32( char *, int ); +#endif + +#ifdef SUPPORT_OLD_KERNEL +extern struct device *init_etherdev(struct device *dev, int sizeof_private, + unsigned long *mem_startp ); +#endif + +/* + . Function: smc_reset( int ioaddr ) + . Purpose: + . This sets the SMC91xx chip to its normal state, hopefully from whatever + . mess that any other DOS driver has put it in. + . + . Maybe I should reset more registers to defaults in here? SOFTRESET should + . do that for me. + . + . Method: + . 1. send a SOFT RESET + . 2. wait for it to finish + . 3. enable autorelease mode + . 4. reset the memory management unit + . 5. clear all interrupts + . +*/ +static void smc_reset( int ioaddr ) +{ + /* This resets the registers mostly to defaults, but doesn't + affect EEPROM. That seems unnecessary */ + SMC_SELECT_BANK( 0 ); + outw( RCR_SOFTRESET, ioaddr + RCR ); + + /* this should pause enough for the chip to be happy */ + SMC_DELAY( ); + + /* Set the transmit and receive configuration registers to + default values */ + outw( RCR_CLEAR, ioaddr + RCR ); + outw( TCR_CLEAR, ioaddr + TCR ); + + /* set the control register to automatically + release successfully transmitted packets, to make the best + use out of our limited memory */ + SMC_SELECT_BANK( 1 ); + outw( inw( ioaddr + CONTROL ) | CTL_AUTO_RELEASE , ioaddr + CONTROL ); + + /* Reset the MMU */ + SMC_SELECT_BANK( 2 ); + outw( MC_RESET, ioaddr + MMU_CMD ); + + /* Note: It doesn't seem that waiting for the MMU busy is needed here, + but this is a place where future chipsets _COULD_ break. Be wary + of issuing another MMU command right after this */ + + outb( 0, ioaddr + INT_MASK ); +} + +/* + . Function: smc_enable + . Purpose: let the chip talk to the outside work + . Method: + . 1. Enable the transmitter + . 2. Enable the receiver + . 3. Enable interrupts +*/ +static void smc_enable( int ioaddr ) +{ + SMC_SELECT_BANK( 0 ); + /* see the header file for options in TCR/RCR NORMAL*/ + outw( TCR_NORMAL, ioaddr + TCR ); + outw( RCR_NORMAL, ioaddr + RCR ); + + /* now, enable interrupts */ + SMC_SELECT_BANK( 2 ); + outb( SMC_INTERRUPT_MASK, ioaddr + INT_MASK ); +} + +/* + . Function: smc_shutdown + . Purpose: closes down the SMC91xxx chip. + . Method: + . 1. zero the interrupt mask + . 2. clear the enable receive flag + . 3. clear the enable xmit flags + . + . TODO: + . (1) maybe utilize power down mode. + . Why not yet? Because while the chip will go into power down mode, + . the manual says that it will wake up in response to any I/O requests + . in the register space. Empirical results do not show this working. +*/ +static void smc_shutdown( int ioaddr ) +{ + /* no more interrupts for me */ + SMC_SELECT_BANK( 2 ); + outb( 0, ioaddr + INT_MASK ); + + /* and tell the card to stay away from that nasty outside world */ + SMC_SELECT_BANK( 0 ); + outb( RCR_CLEAR, ioaddr + RCR ); + outb( TCR_CLEAR, ioaddr + TCR ); +#if 0 + /* finally, shut the chip down */ + SMC_SELECT_BANK( 1 ); + outw( inw( ioaddr + CONTROL ), CTL_POWERDOWN, ioaddr + CONTROL ); +#endif +} + + +#ifndef SUPPORT_OLD_KERNEL +/* + . Function: smc_setmulticast( int ioaddr, int count, dev_mc_list * adds ) + . Purpose: + . This sets the internal hardware table to filter out unwanted multicast + . packets before they take up memory. + . + . The SMC chip uses a hash table where the high 6 bits of the CRC of + . address are the offset into the table. If that bit is 1, then the + . multicast packet is accepted. Otherwise, it's dropped silently. + . + . To use the 6 bits as an offset into the table, the high 3 bits are the + . number of the 8 bit register, while the low 3 bits are the bit within + . that register. + . + . This routine is based very heavily on the one provided by Peter Cammaert. +*/ + + +static void smc_setmulticast( int ioaddr, int count, struct dev_mc_list * addrs ) { + int i; + unsigned char multicast_table[ 8 ]; + struct dev_mc_list * cur_addr; + /* table for flipping the order of 3 bits */ + unsigned char invert3[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + + /* start with a table of all zeros: reject all */ + memset( multicast_table, 0, sizeof( multicast_table ) ); + + cur_addr = addrs; + for ( i = 0; i < count ; i ++, cur_addr = cur_addr->next ) { + int position; + + /* do we have a pointer here? */ + if ( !cur_addr ) + break; + /* make sure this is a multicast address - shouldn't this + be a given if we have it here ? */ + if ( !( *cur_addr->dmi_addr & 1 ) ) + continue; + + /* only use the low order bits */ + position = crc32( cur_addr->dmi_addr, 6 ) & 0x3f; + + /* do some messy swapping to put the bit in the right spot */ + multicast_table[invert3[position&7]] |= + (1<<invert3[(position>>3)&7]); + + } + /* now, the table can be loaded into the chipset */ + SMC_SELECT_BANK( 3 ); + + for ( i = 0; i < 8 ; i++ ) { + outb( multicast_table[i], ioaddr + MULTICAST1 + i ); + } +} + +/* + Finds the CRC32 of a set of bytes. + Again, from Peter Cammaert's code. +*/ +static int crc32( char * s, int length ) { + /* indices */ + int perByte; + int perBit; + /* crc polynomial for Ethernet */ + const unsigned long poly = 0xedb88320; + /* crc value - preinitialized to all 1's */ + unsigned long crc_value = 0xffffffff; + + for ( perByte = 0; perByte < length; perByte ++ ) { + unsigned char c; + + c = *(s++); + for ( perBit = 0; perBit < 8; perBit++ ) { + crc_value = (crc_value>>1)^ + (((crc_value^c)&0x01)?poly:0); + c >>= 1; + } + } + return crc_value; +} + +#endif + + +/* + . Function: smc_wait_to_send_packet( struct sk_buff * skb, struct device * ) + . Purpose: + . Attempt to allocate memory for a packet, if chip-memory is not + . available, then tell the card to generate an interrupt when it + . is available. + . + . Algorithm: + . + . o if the saved_skb is not currently null, then drop this packet + . on the floor. This should never happen, because of TBUSY. + . o if the saved_skb is null, then replace it with the current packet, + . o See if I can sending it now. + . o (NO): Enable interrupts and let the interrupt handler deal with it. + . o (YES):Send it now. +*/ +static int smc_wait_to_send_packet( struct sk_buff * skb, struct device * dev ) +{ + struct smc_local *lp = (struct smc_local *)dev->priv; + unsigned short ioaddr = dev->base_addr; + word length; + unsigned short numPages; + word time_out; + + if ( lp->saved_skb) { + /* THIS SHOULD NEVER HAPPEN. */ + lp->stats.tx_aborted_errors++; + printk(CARDNAME": Bad Craziness - sent packet while busy.\n" ); + return 1; + } + lp->saved_skb = skb; + + length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + + /* + . the MMU wants the number of pages to be the number of 256 bytes + . 'pages', minus 1 ( since a packet can't ever have 0 pages :) ) + */ + numPages = length / 256; + + if (numPages > 7 ) { + printk(CARDNAME": Far too big packet error. \n"); + /* freeing the packet is a good thing here... but should + . any packets of this size get down here? */ + dev_kfree_skb (skb, FREE_WRITE); + lp->saved_skb = NULL; + /* this IS an error, but, i don't want the skb saved */ + return 0; + } + /* either way, a packet is waiting now */ + lp->packets_waiting++; + + /* now, try to allocate the memory */ + SMC_SELECT_BANK( 2 ); + outw( MC_ALLOC | numPages, ioaddr + MMU_CMD ); + /* + . Performance Hack + . + . wait a short amount of time.. if I can send a packet now, I send + . it now. Otherwise, I enable an interrupt and wait for one to be + . available. + . + . I could have handled this a slightly different way, by checking to + . see if any memory was available in the FREE MEMORY register. However, + . either way, I need to generate an allocation, and the allocation works + . no matter what, so I saw no point in checking free memory. + */ + time_out = MEMORY_WAIT_TIME; + do { + word status; + + status = inb( ioaddr + INTERRUPT ); + if ( status & IM_ALLOC_INT ) { + /* acknowledge the interrupt */ + outb( IM_ALLOC_INT, ioaddr + INTERRUPT ); + break; + } + } while ( -- time_out ); + + if ( !time_out ) { + /* oh well, wait until the chip finds memory later */ + SMC_ENABLE_INT( IM_ALLOC_INT ); + PRINTK2((CARDNAME": memory allocation deferred. \n")); + /* it's deferred, but I'll handle it later */ + return 0; + } + /* or YES! I can send the packet now.. */ + smc_hardware_send_packet(dev); + + return 0; +} + +/* + . Function: smc_hardware_send_packet(struct device * ) + . Purpose: + . This sends the actual packet to the SMC9xxx chip. + . + . Algorithm: + . First, see if a saved_skb is available. + . ( this should NOT be called if there is no 'saved_skb' + . Now, find the packet number that the chip allocated + . Point the data pointers at it in memory + . Set the length word in the chip's memory + . Dump the packet to chip memory + . Check if a last byte is needed ( odd length packet ) + . if so, set the control flag right + . Tell the card to send it + . Enable the transmit interrupt, so I know if it failed + . Free the kernel data if I actually sent it. +*/ +static void smc_hardware_send_packet( struct device * dev ) +{ + struct smc_local *lp = (struct smc_local *)dev->priv; + byte packet_no; + struct sk_buff * skb = lp->saved_skb; + word length; + unsigned short ioaddr; + byte * buf; + + ioaddr = dev->base_addr; + + if ( !skb ) { + PRINTK((CARDNAME": In XMIT with no packet to send \n")); + return; + } + length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + buf = skb->data; + + /* If I get here, I _know_ there is a packet slot waiting for me */ + packet_no = inb( ioaddr + PNR_ARR + 1 ); + if ( packet_no & 0x80 ) { + /* or isn't there? BAD CHIP! */ + printk(KERN_DEBUG CARDNAME": Memory allocation failed. \n"); + kfree(skb); + lp->saved_skb = NULL; + dev->tbusy = 0; + return; + } + + /* we have a packet address, so tell the card to use it */ + outb( packet_no, ioaddr + PNR_ARR ); + + /* point to the beginning of the packet */ + outw( PTR_AUTOINC , ioaddr + POINTER ); + + PRINTK3((CARDNAME": Trying to xmit packet of length %x\n", length )); +#if SMC_DEBUG > 2 + print_packet( buf, length ); +#endif + + /* send the packet length ( +6 for status, length and ctl byte ) + and the status word ( set to zeros ) */ +#ifdef USE_32_BIT + outl( (length +6 ) << 16 , ioaddr + DATA_1 ); +#else + outw( 0, ioaddr + DATA_1 ); + /* send the packet length ( +6 for status words, length, and ctl*/ + outb( (length+6) & 0xFF,ioaddr + DATA_1 ); + outb( (length+6) >> 8 , ioaddr + DATA_1 ); +#endif + + /* send the actual data + . I _think_ it's faster to send the longs first, and then + . mop up by sending the last word. It depends heavily + . on alignment, at least on the 486. Maybe it would be + . a good idea to check which is optimal? But that could take + . almost as much time as is saved? + */ +#ifdef USE_32_BIT + if ( length & 0x2 ) { + outsl(ioaddr + DATA_1, buf, length >> 2 ); + outw( *((word *)(buf + (length & 0xFFFFFFFC))),ioaddr +DATA_1); + } + else + outsl(ioaddr + DATA_1, buf, length >> 2 ); +#else + outsw(ioaddr + DATA_1 , buf, (length ) >> 1); +#endif + /* Send the last byte, if there is one. */ + + if ( (length & 1) == 0 ) { + outw( 0, ioaddr + DATA_1 ); + } else { + outb( buf[length -1 ], ioaddr + DATA_1 ); + outb( 0x20, ioaddr + DATA_1); + } + + /* enable the interrupts */ + SMC_ENABLE_INT( (IM_TX_INT | IM_TX_EMPTY_INT) ); + + /* and let the chipset deal with it */ + outw( MC_ENQUEUE , ioaddr + MMU_CMD ); + + PRINTK2((CARDNAME": Sent packet of length %d \n",length)); + + lp->saved_skb = NULL; + dev_kfree_skb (skb, FREE_WRITE); + + dev->trans_start = jiffies; + + /* we can send another packet */ + dev->tbusy = 0; + + + return; +} + +/*------------------------------------------------------------------------- + | + | smc_init( struct device * dev ) + | Input parameters: + | dev->base_addr == 0, try to find all possible locations + | dev->base_addr == 1, return failure code + | dev->base_addr == 2, always allocate space, and return success + | dev->base_addr == <anything else> this is the address to check + | + | Output: + | 0 --> there is a device + | anything else, error + | + --------------------------------------------------------------------------- +*/ +int smc_init(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + /* try a specific location */ + if (base_addr > 0x1ff) { + int error; + error = smc_probe(base_addr); + if ( 0 == error ) { + return smc_initcard( dev, base_addr ); + } + return error; + } else { + if ( 0 != base_addr ) { + return -ENXIO; + } + } + + /* check every ethernet address */ + for (i = 0; smc_portlist[i]; i++) { + int ioaddr = smc_portlist[i]; + + /* check if the area is available */ + if (check_region( ioaddr , SMC_IO_EXTENT)) + continue; + + /* check this specific address */ + if ( smc_probe( ioaddr ) == 0) { + return smc_initcard( dev, ioaddr ); + } + } + + /* couldn't find anything */ + return -ENODEV; +} + +#ifndef NO_AUTOPROBE +/*---------------------------------------------------------------------- + . smc_findirq + . + . This routine has a simple purpose -- make the SMC chip generate an + . interrupt, so an auto-detect routine can detect it, and find the IRQ, + ------------------------------------------------------------------------ +*/ +int smc_findirq( int ioaddr ) +{ + int timeout = 20; + + + /* I have to do a STI() here, because this is called from + a routine that does an CLI during this process, making it + rather difficult to get interrupts for auto detection */ + sti(); + + autoirq_setup( 0 ); + + /* + * What I try to do here is trigger an ALLOC_INT. This is done + * by allocating a small chunk of memory, which will give an interrupt + * when done. + */ + + + SMC_SELECT_BANK(2); + /* enable ALLOCation interrupts ONLY */ + outb( IM_ALLOC_INT, ioaddr + INT_MASK ); + + /* + . Allocate 512 bytes of memory. Note that the chip was just + . reset so all the memory is available + */ + outw( MC_ALLOC | 1, ioaddr + MMU_CMD ); + + /* + . Wait until positive that the interrupt has been generated + */ + while ( timeout ) { + byte int_status; + + int_status = inb( ioaddr + INTERRUPT ); + + if ( int_status & IM_ALLOC_INT ) + break; /* got the interrupt */ + timeout--; + } + /* there is really nothing that I can do here if timeout fails, + as autoirq_report will return a 0 anyway, which is what I + want in this case. Plus, the clean up is needed in both + cases. */ + + /* DELAY HERE! + On a fast machine, the status might change before the interrupt + is given to the processor. This means that the interrupt was + never detected, and autoirq_report fails to report anything. + This should fix autoirq_* problems. + */ + SMC_DELAY(); + SMC_DELAY(); + + /* and disable all interrupts again */ + outb( 0, ioaddr + INT_MASK ); + + /* clear hardware interrupts again, because that's how it + was when I was called... */ + cli(); + + /* and return what I found */ + return autoirq_report( 0 ); +} +#endif + +/*---------------------------------------------------------------------- + . Function: smc_probe( int ioaddr ) + . + . Purpose: + . Tests to see if a given ioaddr points to an SMC9xxx chip. + . Returns a 0 on success + . + . Algorithm: + . (1) see if the high byte of BANK_SELECT is 0x33 + . (2) compare the ioaddr with the base register's address + . (3) see if I recognize the chip ID in the appropriate register + . + .--------------------------------------------------------------------- + */ + +static int smc_probe( int ioaddr ) +{ + unsigned int bank; + word revision_register; + word base_address_register; + + /* First, see if the high byte is 0x33 */ + bank = inw( ioaddr + BANK_SELECT ); + if ( (bank & 0xFF00) != 0x3300 ) { + return -ENODEV; + } + /* The above MIGHT indicate a device, but I need to write to further + test this. */ + outw( 0x0, ioaddr + BANK_SELECT ); + bank = inw( ioaddr + BANK_SELECT ); + if ( (bank & 0xFF00 ) != 0x3300 ) { + return -ENODEV; + } + /* well, we've already written once, so hopefully another time won't + hurt. This time, I need to switch the bank register to bank 1, + so I can access the base address register */ + SMC_SELECT_BANK(1); + base_address_register = inw( ioaddr + BASE ); + if ( ioaddr != ( base_address_register >> 3 & 0x3E0 ) ) { + printk(CARDNAME ": IOADDR %x doesn't match configuration (%x)." + "Probably not a SMC chip\n", + ioaddr, base_address_register >> 3 & 0x3E0 ); + /* well, the base address register didn't match. Must not have + been a SMC chip after all. */ + return -ENODEV; + } + + /* check if the revision register is something that I recognize. + These might need to be added to later, as future revisions + could be added. */ + SMC_SELECT_BANK(3); + revision_register = inw( ioaddr + REVISION ); + if ( !chip_ids[ ( revision_register >> 4 ) & 0xF ] ) { + /* I don't recognize this chip, so... */ + printk(CARDNAME ": IO %x: Unrecognized revision register:" + " %x, Contact author. \n", ioaddr, revision_register ); + + return -ENODEV; + } + + /* at this point I'll assume that the chip is an SMC9xxx. + It might be prudent to check a listing of MAC addresses + against the hardware address, or do some other tests. */ + return 0; +} + +/*--------------------------------------------------------------- + . Here I do typical initialization tasks. + . + . o Initialize the structure if needed + . o print out my vanity message if not done so already + . o print out what type of hardware is detected + . o print out the ethernet address + . o find the IRQ + . o set up my private data + . o configure the dev structure with my subroutines + . o actually GRAB the irq. + . o GRAB the region + .----------------------------------------------------------------- +*/ +static int smc_initcard(struct device *dev, int ioaddr) +{ + int i; + + static unsigned version_printed = 0; + + /* registers */ + word revision_register; + word configuration_register; + word memory_info_register; + word memory_cfg_register; + + const char * version_string; + const char * if_string; + int memory; + + int irqval; + + /* see if I need to initialize the ethernet card structure */ + if (dev == NULL) { +#ifdef SUPPORT_OLD_KERNEL +#ifndef MODULE +/* note: the old module interface does not support this call */ + dev = init_etherdev( 0, sizeof( struct smc_local ), 0 ); +#endif +#else + dev = init_etherdev(0, 0); +#endif + if (dev == NULL) + return -ENOMEM; + } + + if (version_printed++ == 0) + printk("%s", version); + + /* fill in some of the fields */ + dev->base_addr = ioaddr; + + /* + . Get the MAC address ( bank 1, regs 4 - 9 ) + */ + SMC_SELECT_BANK( 1 ); + for ( i = 0; i < 6; i += 2 ) { + word address; + + address = inw( ioaddr + ADDR0 + i ); + dev->dev_addr[ i + 1] = address >> 8; + dev->dev_addr[ i ] = address & 0xFF; + } + + /* get the memory information */ + + SMC_SELECT_BANK( 0 ); + memory_info_register = inw( ioaddr + MIR ); + memory_cfg_register = inw( ioaddr + MCR ); + memory = ( memory_cfg_register >> 9 ) & 0x7; /* multiplier */ + memory *= 256 * ( memory_info_register & 0xFF ); + + /* + Now, I want to find out more about the chip. This is sort of + redundant, but it's cleaner to have it in both, rather than having + one VERY long probe procedure. + */ + SMC_SELECT_BANK(3); + revision_register = inw( ioaddr + REVISION ); + version_string = chip_ids[ ( revision_register >> 4 ) & 0xF ]; + if ( !version_string ) { + /* I shouldn't get here because this call was done before.... */ + return -ENODEV; + } + + /* is it using AUI or 10BaseT ? */ + if ( dev->if_port == 0 ) { + SMC_SELECT_BANK(1); + configuration_register = inw( ioaddr + CONFIG ); + if ( configuration_register & CFG_AUI_SELECT ) + dev->if_port = 2; + else + dev->if_port = 1; + } + if_string = interfaces[ dev->if_port - 1 ]; + + /* now, reset the chip, and put it into a known state */ + smc_reset( ioaddr ); + + /* + . If dev->irq is 0, then the device has to be banged on to see + . what the IRQ is. + . + . This banging doesn't always detect the IRQ, for unknown reasons. + . a workaround is to reset the chip and try again. + . + . Interestingly, the DOS packet driver *SETS* the IRQ on the card to + . be what is requested on the command line. I don't do that, mostly + . because the card that I have uses a non-standard method of accessing + . the IRQs, and because this _should_ work in most configurations. + . + . Specifying an IRQ is done with the assumption that the user knows + . what (s)he is doing. No checking is done!!!! + . + */ +#ifndef NO_AUTOPROBE + if ( dev->irq < 2 ) { + int trials; + + trials = 3; + while ( trials-- ) { + dev->irq = smc_findirq( ioaddr ); + if ( dev->irq ) + break; + /* kick the card and try again */ + smc_reset( ioaddr ); + } + } + if (dev->irq == 0 ) { + printk(CARDNAME": Couldn't autodetect your IRQ. Use irq=xx.\n"); + return -ENODEV; + } +#else + if (dev->irq == 0 ) { + printk(CARDNAME + ": Autoprobing IRQs is not supported for old kernels.\n"); + return -ENODEV; + } +#endif + if (dev->irq == 2) { + /* Fixup for users that don't know that IRQ 2 is really IRQ 9, + * or don't know which one to set. + */ + dev->irq = 9; + } + + /* now, print out the card info, in a short format.. */ + + printk(CARDNAME ": %s(r:%d) at %#3x IRQ:%d INTF:%s MEM:%db ", + version_string, revision_register & 0xF, ioaddr, dev->irq, + if_string, memory ); + /* + . Print the Ethernet address + */ + printk("ADDR: "); + for (i = 0; i < 5; i++) + printk("%2.2x:", dev->dev_addr[i] ); + printk("%2.2x \n", dev->dev_addr[5] ); + + + /* Initialize the private structure. */ + if (dev->priv == NULL) { + dev->priv = kmalloc(sizeof(struct smc_local), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + } + /* set the private data to zero by default */ + memset(dev->priv, 0, sizeof(struct smc_local)); + + /* Fill in the fields of the device structure with ethernet values. */ + ether_setup(dev); + + /* Grab the IRQ */ + irqval = request_irq(dev->irq, &smc_interrupt, 0, CARDNAME, NULL); + if (irqval) { + printk(CARDNAME": unable to get IRQ %d (irqval=%d).\n", + dev->irq, irqval); + return -EAGAIN; + } + irq2dev_map[dev->irq] = dev; + + /* Grab the region so that no one else tries to probe our ioports. */ + request_region(ioaddr, SMC_IO_EXTENT, CARDNAME); + + dev->open = smc_open; + dev->stop = smc_close; + dev->hard_start_xmit = smc_send_packet; + dev->get_stats = smc_query_statistics; +#ifdef HAVE_MULTICAST + dev->set_multicast_list = &smc_set_multicast_list; +#endif + + return 0; +} + +#if SMC_DEBUG > 2 +static void print_packet( byte * buf, int length ) +{ +#if 0 + int i; + int remainder; + int lines; + + printk("Packet of length %d \n", length ); + lines = length / 16; + remainder = length % 16; + + for ( i = 0; i < lines ; i ++ ) { + int cur; + + for ( cur = 0; cur < 8; cur ++ ) { + byte a, b; + + a = *(buf ++ ); + b = *(buf ++ ); + printk("%02x%02x ", a, b ); + } + printk("\n"); + } + for ( i = 0; i < remainder/2 ; i++ ) { + byte a, b; + + a = *(buf ++ ); + b = *(buf ++ ); + printk("%02x%02x ", a, b ); + } + printk("\n"); +#endif +} +#endif + + +/* + * Open and Initialize the board + * + * Set up everything, reset the card, etc .. + * + */ +static int smc_open(struct device *dev) +{ + int ioaddr = dev->base_addr; + + int i; /* used to set hw ethernet address */ + + /* clear out all the junk that was put here before... */ + memset(dev->priv, 0, sizeof(struct smc_local)); + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif + + /* reset the hardware */ + + smc_reset( ioaddr ); + smc_enable( ioaddr ); + + /* Select which interface to use */ + + SMC_SELECT_BANK( 1 ); + if ( dev->if_port == 1 ) { + outw( inw( ioaddr + CONFIG ) & ~CFG_AUI_SELECT, + ioaddr + CONFIG ); + } + else if ( dev->if_port == 2 ) { + outw( inw( ioaddr + CONFIG ) | CFG_AUI_SELECT, + ioaddr + CONFIG ); + } + + /* + According to Becker, I have to set the hardware address + at this point, because the (l)user can set it with an + ioctl. Easily done... + */ + SMC_SELECT_BANK( 1 ); + for ( i = 0; i < 6; i += 2 ) { + word address; + + address = dev->dev_addr[ i + 1 ] << 8 ; + address |= dev->dev_addr[ i ]; + outw( address, ioaddr + ADDR0 + i ); + } + return 0; +} + +/*-------------------------------------------------------- + . Called by the kernel to send a packet out into the void + . of the net. This routine is largely based on + . skeleton.c, from Becker. + .-------------------------------------------------------- +*/ +static int smc_send_packet(struct sk_buff *skb, struct device *dev) +{ + 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; + printk(KERN_WARNING CARDNAME": transmit timed out, %s?\n", + tx_done(dev) ? "IRQ conflict" : + "network cable problem"); + /* "kick" the adaptor */ + smc_reset( dev->base_addr ); + smc_enable( dev->base_addr ); + + dev->tbusy = 0; + dev->trans_start = jiffies; + /* clear anything saved */ + ((struct smc_local *)dev->priv)->saved_skb = NULL; + } + + /* 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) { + dev_tint(dev); + return 0; + } + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (set_bit(0, (void*)&dev->tbusy) != 0) { + printk(KERN_WARNING CARDNAME": Transmitter access conflict.\n"); + dev_kfree_skb (skb, FREE_WRITE); + } else { + /* Well, I want to send the packet.. but I don't know + if I can send it right now... */ + return smc_wait_to_send_packet( skb, dev ); + } + return 0; +} + +/*-------------------------------------------------------------------- + . + . This is the main routine of the driver, to handle the device when + . it needs some attention. + . + . So: + . first, save state of the chipset + . branch off into routines to handle each case, and acknowledge + . each to the interrupt register + . and finally restore state. + . + ---------------------------------------------------------------------*/ +#ifdef REALLY_NEW_KERNEL +static void smc_interrupt(int irq, void * dev_id, struct pt_regs * regs) +#else +static void smc_interrupt(int irq, struct pt_regs * regs) +#endif +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + int ioaddr = dev->base_addr; + struct smc_local *lp = (struct smc_local *)dev->priv; + + byte status; + word card_stats; + byte mask; + int timeout; + /* state registers */ + word saved_bank; + word saved_pointer; + + + + PRINTK3((CARDNAME": SMC interrupt started \n")); + + if (dev == NULL) { + printk(KERN_WARNING CARDNAME": irq %d for unknown device.\n", + irq); + return; + } + +/* will Linux let this happen ?? If not, this costs some speed */ + if ( dev->interrupt ) { + printk(KERN_WARNING CARDNAME": interrupt inside interrupt.\n"); + return; + } + + dev->interrupt = 1; + + saved_bank = inw( ioaddr + BANK_SELECT ); + + SMC_SELECT_BANK(2); + saved_pointer = inw( ioaddr + POINTER ); + + mask = inb( ioaddr + INT_MASK ); + /* clear all interrupts */ + outb( 0, ioaddr + INT_MASK ); + + + /* set a timeout value, so I don't stay here forever */ + timeout = 4; + + PRINTK2((KERN_WARNING CARDNAME ": MASK IS %x \n", mask )); + do { + /* read the status flag, and mask it */ + status = inb( ioaddr + INTERRUPT ) & mask; + if (!status ) + break; + + PRINTK3((KERN_WARNING CARDNAME + ": Handling interrupt status %x \n", status )); + + if (status & IM_RCV_INT) { + /* Got a packet(s). */ + PRINTK2((KERN_WARNING CARDNAME + ": Receive Interrupt\n")); + smc_rcv(dev); + } else if (status & IM_TX_INT ) { + PRINTK2((KERN_WARNING CARDNAME + ": TX ERROR handled\n")); + smc_tx(dev); + outb(IM_TX_INT, ioaddr + INTERRUPT ); + } else if (status & IM_TX_EMPTY_INT ) { + /* update stats */ + SMC_SELECT_BANK( 0 ); + card_stats = inw( ioaddr + COUNTER ); + /* single collisions */ + lp->stats.collisions += card_stats & 0xF; + card_stats >>= 4; + /* multiple collisions */ + lp->stats.collisions += card_stats & 0xF; + + /* these are for when linux supports these statistics */ +#if 0 + card_stats >>= 4; + /* deferred */ + card_stats >>= 4; + /* excess deferred */ +#endif + SMC_SELECT_BANK( 2 ); + PRINTK2((KERN_WARNING CARDNAME + ": TX_BUFFER_EMPTY handled\n")); + outb( IM_TX_EMPTY_INT, ioaddr + INTERRUPT ); + mask &= ~IM_TX_EMPTY_INT; + lp->stats.tx_packets += lp->packets_waiting; + lp->packets_waiting = 0; + + } else if (status & IM_ALLOC_INT ) { + PRINTK2((KERN_DEBUG CARDNAME + ": Allocation interrupt \n")); + /* clear this interrupt so it doesn't happen again */ + mask &= ~IM_ALLOC_INT; + + smc_hardware_send_packet( dev ); + + /* enable xmit interrupts based on this */ + mask |= ( IM_TX_EMPTY_INT | IM_TX_INT ); + + /* and let the card send more packets to me */ + mark_bh( NET_BH ); + + PRINTK2((CARDNAME": Handoff done successfully.\n")); + } else if (status & IM_RX_OVRN_INT ) { + lp->stats.rx_errors++; + lp->stats.rx_fifo_errors++; + outb( IM_RX_OVRN_INT, ioaddr + INTERRUPT ); + } else if (status & IM_EPH_INT ) { + PRINTK((CARDNAME ": UNSUPPORTED: EPH INTERRUPT \n")); + } else if (status & IM_ERCV_INT ) { + PRINTK((CARDNAME ": UNSUPPORTED: ERCV INTERRUPT \n")); + outb( IM_ERCV_INT, ioaddr + INTERRUPT ); + } + } while ( timeout -- ); + + + /* restore state register */ + SMC_SELECT_BANK( 2 ); + outb( mask, ioaddr + INT_MASK ); + + PRINTK3(( KERN_WARNING CARDNAME ": MASK is now %x \n", mask )); + outw( saved_pointer, ioaddr + POINTER ); + + SMC_SELECT_BANK( saved_bank ); + + dev->interrupt = 0; + PRINTK3((CARDNAME ": Interrupt done\n")); + return; +} + +/*------------------------------------------------------------- + . + . smc_rcv - receive a packet from the card + . + . There is ( at least ) a packet waiting to be read from + . chip-memory. + . + . o Read the status + . o If an error, record it + . o otherwise, read in the packet + -------------------------------------------------------------- +*/ +static void smc_rcv(struct device *dev) +{ + struct smc_local *lp = (struct smc_local *)dev->priv; + int ioaddr = dev->base_addr; + int packet_number; + word status; + word packet_length; + + /* assume bank 2 */ + + packet_number = inw( ioaddr + FIFO_PORTS ); + + if ( packet_number & FP_RXEMPTY ) { + /* we got called , but nothing was on the FIFO */ + PRINTK((CARDNAME ": WARNING: smc_rcv with nothing on FIFO. \n")); + /* don't need to restore anything */ + return; + } + + /* start reading from the start of the packet */ + outw( PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr + POINTER ); + + /* First two words are status and packet_length */ + status = inw( ioaddr + DATA_1 ); + packet_length = inw( ioaddr + DATA_1 ); + + packet_length &= 0x07ff; /* mask off top bits */ + + PRINTK2(("RCV: STATUS %4x LENGTH %4x\n", status, packet_length )); + /* + . the packet length contains 3 extra words : + . status, length, and a extra word with an odd byte . + */ + packet_length -= 6; + + if ( !(status & RS_ERRORS ) ){ + /* do stuff to make a new packet */ + struct sk_buff * skb; + byte * data; + + /* read one extra byte */ + if ( status & RS_ODDFRAME ) + packet_length++; + + /* set multicast stats */ + if ( status & RS_MULTICAST ) + lp->stats.multicast++; + +#ifdef SUPPORT_OLD_KERNEL + skb = alloc_skb( packet_length + 5, GFP_ATOMIC ); +#else + skb = dev_alloc_skb( packet_length + 5); +#endif + + if ( skb == NULL ) { + printk(KERN_NOTICE CARDNAME + ": Low memory, packet dropped.\n"); + lp->stats.rx_dropped++; + } + + /* + ! This should work without alignment, but it could be + ! in the worse case + */ +#ifndef SUPPORT_OLD_KERNEL + /* TODO: Should I use 32bit alignment here ? */ + skb_reserve( skb, 2 ); /* 16 bit alignment */ +#endif + + skb->dev = dev; +#ifdef SUPPORT_OLD_KERNEL + skb->len = packet_length; + data = skb->data; +#else + data = skb_put( skb, packet_length); +#endif +#ifdef USE_32_BIT + /* QUESTION: Like in the TX routine, do I want + to send the DWORDs or the bytes first, or some + mixture. A mixture might improve already slow PIO + performance */ + PRINTK3((" Reading %d dwords (and %d bytes) \n", + packet_length >> 2, packet_length & 3 )); + insl(ioaddr + DATA_1 , data, packet_length >> 2 ); + /* read the left over bytes */ + insb( ioaddr + DATA_1, data + (packet_length & 0xFFFFFC), + packet_length & 0x3 ); +#else + PRINTK3((" Reading %d words and %d byte(s) \n", + (packet_length >> 1 ), packet_length & 1 ); + if ( packet_length & 1 ) + *(data++) = inb( ioaddr + DATA_1 ); + insw(ioaddr + DATA_1 , data, (packet_length + 1 ) >> 1); + if ( packet_length & 1 ) { + data += packet_length & ~1; + *((data++) = inb( ioaddr + DATA_1 ); + } +#endif +#if SMC_DEBUG > 2 + print_packet( data, packet_length ); +#endif + +#ifndef SUPPORT_OLD_KERNEL + skb->protocol = eth_type_trans(skb, dev ); +#endif + netif_rx(skb); + lp->stats.rx_packets++; + } else { + /* error ... */ + lp->stats.rx_errors++; + + if ( status & RS_ALGNERR ) lp->stats.rx_frame_errors++; + if ( status & (RS_TOOSHORT | RS_TOOLONG ) ) + lp->stats.rx_length_errors++; + if ( status & RS_BADCRC) lp->stats.rx_crc_errors++; + } + /* error or good, tell the card to get rid of this packet */ + outw( MC_RELEASE, ioaddr + MMU_CMD ); + + + return; +} + + +/************************************************************************* + . smc_tx + . + . Purpose: Handle a transmit error message. This will only be called + . when an error, because of the AUTO_RELEASE mode. + . + . Algorithm: + . Save pointer and packet no + . Get the packet no from the top of the queue + . check if it's valid ( if not, is this an error??? ) + . read the status word + . record the error + . ( resend? Not really, since we don't want old packets around ) + . Restore saved values + ************************************************************************/ +static void smc_tx( struct device * dev ) +{ + int ioaddr = dev->base_addr; + struct smc_local *lp = (struct smc_local *)dev->priv; + byte saved_packet; + byte packet_no; + word tx_status; + + + /* assume bank 2 */ + + saved_packet = inb( ioaddr + PNR_ARR ); + packet_no = inw( ioaddr + FIFO_PORTS ); + packet_no &= 0x7F; + + /* select this as the packet to read from */ + outb( packet_no, ioaddr + PNR_ARR ); + + /* read the first word from this packet */ + outw( PTR_AUTOINC | PTR_READ, ioaddr + POINTER ); + + tx_status = inw( ioaddr + DATA_1 ); + PRINTK3((CARDNAME": TX DONE STATUS: %4x \n", tx_status )); + + lp->stats.tx_errors++; + if ( tx_status & TS_LOSTCAR ) lp->stats.tx_carrier_errors++; + if ( tx_status & TS_LATCOL ) { + printk(KERN_DEBUG CARDNAME + ": Late collision occurred on last xmit.\n"); + lp->stats.tx_window_errors++; + } +#if 0 + if ( tx_status & TS_16COL ) { ... } +#endif + + if ( tx_status & TS_SUCCESS ) { + printk(CARDNAME": Successful packet caused interrupt \n"); + } + /* re-enable transmit */ + SMC_SELECT_BANK( 0 ); + outw( inw( ioaddr + TCR ) | TCR_ENABLE, ioaddr + TCR ); + + /* kill the packet */ + SMC_SELECT_BANK( 2 ); + outw( MC_FREEPKT, ioaddr + MMU_CMD ); + + /* one less packet waiting for me */ + lp->packets_waiting--; + + outb( saved_packet, ioaddr + PNR_ARR ); + return; +} + +/*---------------------------------------------------- + . smc_close + . + . this makes the board clean up everything that it can + . and not talk to the outside world. Caused by + . an 'ifconfig ethX down' + . + -----------------------------------------------------*/ +static int smc_close(struct device *dev) +{ + dev->tbusy = 1; + dev->start = 0; + + /* clear everything */ + smc_shutdown( dev->base_addr ); + + /* Update the statistics here. */ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif + + return 0; +} + +/*------------------------------------------------------------ + . Get the current statistics. + . This may be called with the card open or closed. + .-------------------------------------------------------------*/ +static struct enet_statistics * smc_query_statistics(struct device *dev) { + struct smc_local *lp = (struct smc_local *)dev->priv; + + return &lp->stats; +} + +/*----------------------------------------------------------- + . smc_set_multicast_list + . + . This routine will, depending on the values passed to it, + . either make it accept multicast packets, go into + . promiscuous mode ( for TCPDUMP and cousins ) or accept + . a select set of multicast packets +*/ +#ifdef SUPPORT_OLD_KERNEL +static void smc_set_multicast_list( struct device * dev, + int num_addrs, void * addrs ) +#else +static void smc_set_multicast_list(struct device *dev) +#endif +{ + short ioaddr = dev->base_addr; + + SMC_SELECT_BANK(0); +#ifdef SUPPORT_OLD_KERNEL + if ( num_addrs < 0 ) +#else + if ( dev->flags & IFF_PROMISC ) +#endif + outw( inw(ioaddr + RCR ) | RCR_PROMISC, ioaddr + RCR ); + +/* BUG? I never disable promiscuous mode if multicasting was turned on. + Now, I turn off promiscuous mode, but I don't do anything to multicasting + when promiscuous mode is turned on. +*/ + + /* Here, I am setting this to accept all multicast packets. + I don't need to zero the multicast table, because the flag is + checked before the table is + */ +#ifdef SUPPORT_OLD_KERNEL + else if ( num_addrs > 20 ) /* arbitrary constant */ +#else + else if (dev->flags & IFF_ALLMULTI) +#endif + outw( inw(ioaddr + RCR ) | RCR_ALMUL, ioaddr + RCR ); + + /* We just get all multicast packets even if we only want them + . from one source. This will be changed at some future + . point. */ +#ifdef SUPPORT_OLD_KERNEL + else if (num_addrs > 0 ) { +/* the old kernel support will not have hardware multicast support. It would + involve more kludges, and make the multicast setting code even worse. + Instead, just use the ALMUL method. This is reasonable, considering that + it is seldom used +*/ + outw( inw( ioaddr + RCR ) & ~RCR_PROMISC, ioaddr + RCR ); + outw( inw( ioadddr + RCR ) | RCR_ALMUL, ioadddr + RCR ); + } +#else + else if (dev->mc_count ) { + /* support hardware multicasting */ + + /* be sure I get rid of flags I might have set */ + outw( inw( ioaddr + RCR ) & ~(RCR_PROMISC | RCR_ALMUL), + ioaddr + RCR ); + /* NOTE: this has to set the bank, so make sure it is the + last thing called. The bank is set to zero at the top */ + smc_setmulticast( ioaddr, dev->mc_count, dev->mc_list ); + } +#endif + else { + outw( inw( ioaddr + RCR ) & ~(RCR_PROMISC | RCR_ALMUL), + ioaddr + RCR ); + + /* + since I'm disabling all multicast entirely, I need to + clear the multicast list + */ + SMC_SELECT_BANK( 3 ); + outw( 0, ioaddr + MULTICAST1 ); + outw( 0, ioaddr + MULTICAST2 ); + outw( 0, ioaddr + MULTICAST3 ); + outw( 0, ioaddr + MULTICAST4 ); + } +} + +#ifdef MODULE + +static char devicename[9] = { 0, }; +static struct device devSMC9194 = { + devicename, /* device name is inserted by linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0, 0, /* I/O address, IRQ */ + 0, 0, 0, NULL, smc_init }; + +int io = 0; +int irq = 0; +int ifport = 0; + +int init_module(void) +{ + int result; + + if (io == 0) + printk(KERN_WARNING + CARDNAME": You shouldn't use auto-probing with insmod!\n" ); + + /* copy the parameters from insmod into the device structure */ + devSMC9194.base_addr = io; + devSMC9194.irq = irq; + devSMC9194.if_port = ifport; + if ((result = register_netdev(&devSMC9194)) != 0) + return result; + + return 0; +} + +void cleanup_module(void) +{ + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + unregister_netdev(&devSMC9194); + + free_irq(devSMC9194.irq, NULL ); + irq2dev_map[devSMC9194.irq] = NULL; + release_region(devSMC9194.base_addr, SMC_IO_EXTENT); + + if (devSMC9194.priv) + kfree_s(devSMC9194.priv, sizeof(struct smc_local)); +} + +#endif /* MODULE */ + diff --git a/linux/src/drivers/net/smc9194.h b/linux/src/drivers/net/smc9194.h new file mode 100644 index 00000000..66f8b8cd --- /dev/null +++ b/linux/src/drivers/net/smc9194.h @@ -0,0 +1,240 @@ +/*------------------------------------------------------------------------ + . smc9194.h + . Copyright (C) 1996 by Erik Stahlman + . + . This software may be used and distributed according to the terms + . of the GNU Public License, incorporated herein by reference. + . + . This file contains register information and access macros for + . the SMC91xxx chipset. + . + . Information contained in this file was obtained from the SMC91C94 + . manual from SMC. To get a copy, if you really want one, you can find + . information under www.smc.com in the components division. + . ( this thanks to advice from Donald Becker ). + . + . Authors + . Erik Stahlman ( erik@vt.edu ) + . + . History + . 01/06/96 Erik Stahlman moved definitions here from main .c file + . 01/19/96 Erik Stahlman polished this up some, and added better + . error handling + . + ---------------------------------------------------------------------------*/ +#ifndef _SMC9194_H_ +#define _SMC9194_H_ + +/* I want some simple types */ + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned long int dword; + + +/* Because of bank switching, the SMC91xxx uses only 16 I/O ports */ + +#define SMC_IO_EXTENT 16 + + +/*--------------------------------------------------------------- + . + . A description of the SMC registers is probably in order here, + . although for details, the SMC datasheet is invaluable. + . + . Basically, the chip has 4 banks of registers ( 0 to 3 ), which + . are accessed by writing a number into the BANK_SELECT register + . ( I also use a SMC_SELECT_BANK macro for this ). + . + . The banks are configured so that for most purposes, bank 2 is all + . that is needed for simple run time tasks. + -----------------------------------------------------------------------*/ + +/* + . Bank Select Register: + . + . yyyy yyyy 0000 00xx + . xx = bank number + . yyyy yyyy = 0x33, for identification purposes. +*/ +#define BANK_SELECT 14 + +/* BANK 0 */ + +#define TCR 0 /* transmit control register */ +#define TCR_ENABLE 0x0001 /* if this is 1, we can transmit */ +#define TCR_FDUPLX 0x0800 /* receive packets sent out */ +#define TCR_STP_SQET 0x1000 /* stop transmitting if Signal quality error */ +#define TCR_MON_CNS 0x0400 /* monitors the carrier status */ +#define TCR_PAD_ENABLE 0x0080 /* pads short packets to 64 bytes */ + +#define TCR_CLEAR 0 /* do NOTHING */ +/* the normal settings for the TCR register : */ +/* QUESTION: do I want to enable padding of short packets ? */ +#define TCR_NORMAL TCR_ENABLE + + +#define EPH_STATUS 2 +#define ES_LINK_OK 0x4000 /* is the link integrity ok ? */ + +#define RCR 4 +#define RCR_SOFTRESET 0x8000 /* resets the chip */ +#define RCR_STRIP_CRC 0x200 /* strips CRC */ +#define RCR_ENABLE 0x100 /* IFF this is set, we can receive packets */ +#define RCR_ALMUL 0x4 /* receive all multicast packets */ +#define RCR_PROMISC 0x2 /* enable promiscuous mode */ + +/* the normal settings for the RCR register : */ +#define RCR_NORMAL (RCR_STRIP_CRC | RCR_ENABLE) +#define RCR_CLEAR 0x0 /* set it to a base state */ + +#define COUNTER 6 +#define MIR 8 +#define MCR 10 +/* 12 is reserved */ + +/* BANK 1 */ +#define CONFIG 0 +#define CFG_AUI_SELECT 0x100 +#define BASE 2 +#define ADDR0 4 +#define ADDR1 6 +#define ADDR2 8 +#define GENERAL 10 +#define CONTROL 12 +#define CTL_POWERDOWN 0x2000 +#define CTL_LE_ENABLE 0x80 +#define CTL_CR_ENABLE 0x40 +#define CTL_TE_ENABLE 0x0020 +#define CTL_AUTO_RELEASE 0x0800 +#define CTL_EPROM_ACCESS 0x0003 /* high if Eprom is being read */ + +/* BANK 2 */ +#define MMU_CMD 0 +#define MC_BUSY 1 /* only readable bit in the register */ +#define MC_NOP 0 +#define MC_ALLOC 0x20 /* or with number of 256 byte packets */ +#define MC_RESET 0x40 +#define MC_REMOVE 0x60 /* remove the current rx packet */ +#define MC_RELEASE 0x80 /* remove and release the current rx packet */ +#define MC_FREEPKT 0xA0 /* Release packet in PNR register */ +#define MC_ENQUEUE 0xC0 /* Enqueue the packet for transmit */ + +#define PNR_ARR 2 +#define FIFO_PORTS 4 + +#define FP_RXEMPTY 0x8000 +#define FP_TXEMPTY 0x80 + +#define POINTER 6 +#define PTR_READ 0x2000 +#define PTR_RCV 0x8000 +#define PTR_AUTOINC 0x4000 +#define PTR_AUTO_INC 0x0040 + +#define DATA_1 8 +#define DATA_2 10 +#define INTERRUPT 12 + +#define INT_MASK 13 +#define IM_RCV_INT 0x1 +#define IM_TX_INT 0x2 +#define IM_TX_EMPTY_INT 0x4 +#define IM_ALLOC_INT 0x8 +#define IM_RX_OVRN_INT 0x10 +#define IM_EPH_INT 0x20 +#define IM_ERCV_INT 0x40 /* not on SMC9192 */ + +/* BANK 3 */ +#define MULTICAST1 0 +#define MULTICAST2 2 +#define MULTICAST3 4 +#define MULTICAST4 6 +#define MGMT 8 +#define REVISION 10 /* ( hi: chip id low: rev # ) */ + + +/* this is NOT on SMC9192 */ +#define ERCV 12 + +#define CHIP_9190 3 +#define CHIP_9194 4 +#define CHIP_9195 5 +#define CHIP_91100 7 + +static const char * chip_ids[ 15 ] = { + NULL, NULL, NULL, + /* 3 */ "SMC91C90/91C92", + /* 4 */ "SMC91C94", + /* 5 */ "SMC91C95", + NULL, + /* 7 */ "SMC91C100", + NULL, NULL, NULL, NULL, + NULL, NULL, NULL}; + +/* + . Transmit status bits +*/ +#define TS_SUCCESS 0x0001 +#define TS_LOSTCAR 0x0400 +#define TS_LATCOL 0x0200 +#define TS_16COL 0x0010 + +/* + . Receive status bits +*/ +#define RS_ALGNERR 0x8000 +#define RS_BADCRC 0x2000 +#define RS_ODDFRAME 0x1000 +#define RS_TOOLONG 0x0800 +#define RS_TOOSHORT 0x0400 +#define RS_MULTICAST 0x0001 +#define RS_ERRORS (RS_ALGNERR | RS_BADCRC | RS_TOOLONG | RS_TOOSHORT) + +static const char * interfaces[ 2 ] = { "TP", "AUI" }; + +/*------------------------------------------------------------------------- + . I define some macros to make it easier to do somewhat common + . or slightly complicated, repeated tasks. + --------------------------------------------------------------------------*/ + +/* select a register bank, 0 to 3 */ + +#define SMC_SELECT_BANK(x) { outw( x, ioaddr + BANK_SELECT ); } + +/* define a small delay for the reset */ +#define SMC_DELAY() { inw( ioaddr + RCR );\ + inw( ioaddr + RCR );\ + inw( ioaddr + RCR ); } + +/* this enables an interrupt in the interrupt mask register */ +#define SMC_ENABLE_INT(x) {\ + unsigned char mask;\ + SMC_SELECT_BANK(2);\ + mask = inb( ioaddr + INT_MASK );\ + mask |= (x);\ + outb( mask, ioaddr + INT_MASK ); \ +} + +/* this disables an interrupt from the interrupt mask register */ + +#define SMC_DISABLE_INT(x) {\ + unsigned char mask;\ + SMC_SELECT_BANK(2);\ + mask = inb( ioaddr + INT_MASK );\ + mask &= ~(x);\ + outb( mask, ioaddr + INT_MASK ); \ +} + +/*---------------------------------------------------------------------- + . Define the interrupts that I want to receive from the card + . + . I want: + . IM_EPH_INT, for nasty errors + . IM_RCV_INT, for happy received packets + . IM_RX_OVRN_INT, because I have to kick the receiver + --------------------------------------------------------------------------*/ +#define SMC_INTERRUPT_MASK (IM_EPH_INT | IM_RX_OVRN_INT | IM_RCV_INT) + +#endif /* _SMC_9194_H_ */ + diff --git a/linux/src/drivers/net/tlan.c b/linux/src/drivers/net/tlan.c new file mode 100644 index 00000000..11e12bbc --- /dev/null +++ b/linux/src/drivers/net/tlan.c @@ -0,0 +1,2863 @@ +/******************************************************************** + * + * Linux ThunderLAN Driver + * + * tlan.c + * by James Banks + * + * (C) 1997-1998 Caldera, Inc. + * (C) 1998 James Banks + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * + ** This file is best viewed/edited with columns>=132. + * + ** Useful (if not required) reading: + * + * Texas Instruments, ThunderLAN Programmer's Guide, + * TI Literature Number SPWU013A + * available in PDF format from www.ti.com + * Level One, LXT901 and LXT970 Data Sheets + * available in PDF format from www.level1.com + * National Semiconductor, DP83840A Data Sheet + * available in PDF format from www.national.com + * Microchip Technology, 24C01A/02A/04A Data Sheet + * available in PDF format from www.microchip.com + * + ********************************************************************/ + + +#include <linux/module.h> + +#include "tlan.h" + +#include <linux/bios32.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/etherdevice.h> +#include <linux/delay.h> + + + +typedef u32 (TLanIntVectorFunc)( struct device *, u16 ); + + +#ifdef MODULE + +static struct device *TLanDevices = NULL; +static int TLanDevicesInstalled = 0; + +#endif + + +static int debug = 0; +static int aui = 0; +static int sa_int = 0; +static int bbuf = 0; +static int duplex = 0; +static int speed = 0; +static u8 *TLanPadBuffer; +static char TLanSignature[] = "TLAN"; +static int TLanVersionMajor = 1; +static int TLanVersionMinor = 0; + + +static TLanAdapterEntry TLanAdapterList[] = { + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETELLIGENT_10, + "Compaq Netelligent 10 T PCI UTP", + TLAN_ADAPTER_ACTIVITY_LED, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETELLIGENT_10_100, + "Compaq Netelligent 10/100 TX PCI UTP", + TLAN_ADAPTER_ACTIVITY_LED, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETFLEX_3P_INTEGRATED, + "Compaq Integrated NetFlex-3/P", + TLAN_ADAPTER_NONE, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETFLEX_3P, + "Compaq NetFlex-3/P", + TLAN_ADAPTER_UNMANAGED_PHY | TLAN_ADAPTER_BIT_RATE_PHY, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETFLEX_3P_BNC, + "Compaq NetFlex-3/P", + TLAN_ADAPTER_NONE, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETELLIGENT_10_100_PROLIANT, + "Compaq Netelligent Integrated 10/100 TX UTP", + TLAN_ADAPTER_NONE, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETELLIGENT_10_100_DUAL, + "Compaq Netelligent Dual 10/100 TX PCI UTP", + TLAN_ADAPTER_NONE, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_DESKPRO_4000_5233MMX, + "Compaq Netelligent 10/100 TX Embedded UTP", + TLAN_ADAPTER_NONE, + 0x83 + }, + { PCI_VENDOR_ID_OLICOM, + PCI_DEVICE_ID_OLICOM_OC2183, + "Olicom OC-2183/2185", + TLAN_ADAPTER_USE_INTERN_10, + 0xF8 + }, + { PCI_VENDOR_ID_OLICOM, + PCI_DEVICE_ID_OLICOM_OC2325, + "Olicom OC-2325", + TLAN_ADAPTER_UNMANAGED_PHY, + 0xF8 + }, + { PCI_VENDOR_ID_OLICOM, + PCI_DEVICE_ID_OLICOM_OC2326, + "Olicom OC-2326", + TLAN_ADAPTER_USE_INTERN_10, + 0xF8 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETELLIGENT_10_100_WS_5100, + "Compaq Netelligent 10/100 TX UTP", + TLAN_ADAPTER_ACTIVITY_LED, + 0x83 + }, + { PCI_VENDOR_ID_COMPAQ, + PCI_DEVICE_ID_NETELLIGENT_10_T2, + "Compaq Netelligent 10 T/2 PCI UTP/Coax", + TLAN_ADAPTER_NONE, + 0x83 + }, + { 0, + 0, + NULL, + 0, + 0 + } /* End of List */ +}; + + +static int TLan_PciProbe( u8 *, u8 *, u8 *, u8 *, u32 *, u32 * ); +static int TLan_Init( struct device * ); +static int TLan_Open(struct device *dev); +static int TLan_StartTx(struct sk_buff *, struct device *); +static void TLan_HandleInterrupt(int, void *, struct pt_regs *); +static int TLan_Close(struct device *); +static struct net_device_stats *TLan_GetStats( struct device * ); +static void TLan_SetMulticastList( struct device * ); + +static u32 TLan_HandleInvalid( struct device *, u16 ); +static u32 TLan_HandleTxEOF( struct device *, u16 ); +static u32 TLan_HandleStatOverflow( struct device *, u16 ); +static u32 TLan_HandleRxEOF( struct device *, u16 ); +static u32 TLan_HandleDummy( struct device *, u16 ); +static u32 TLan_HandleTxEOC( struct device *, u16 ); +static u32 TLan_HandleStatusCheck( struct device *, u16 ); +static u32 TLan_HandleRxEOC( struct device *, u16 ); + +static void TLan_Timer( unsigned long ); + +static void TLan_ResetLists( struct device * ); +static void TLan_FreeLists( struct device * ); +static void TLan_PrintDio( u16 ); +static void TLan_PrintList( TLanList *, char *, int ); +static void TLan_ReadAndClearStats( struct device *, int ); +static void TLan_ResetAdapter( struct device * ); +static void TLan_FinishReset( struct device * ); +static void TLan_SetMac( struct device *, int areg, char *mac ); + +static void TLan_PhyPrint( struct device * ); +static void TLan_PhyDetect( struct device * ); +static void TLan_PhyPowerDown( struct device * ); +static void TLan_PhyPowerUp( struct device * ); +static void TLan_PhyReset( struct device * ); +static void TLan_PhyStartLink( struct device * ); +static void TLan_PhyFinishAutoNeg( struct device * ); +/* +static int TLan_PhyNop( struct device * ); +static int TLan_PhyInternalCheck( struct device * ); +static int TLan_PhyInternalService( struct device * ); +static int TLan_PhyDp83840aCheck( struct device * ); +*/ + +static int TLan_MiiReadReg( struct device *, u16, u16, u16 * ); +static void TLan_MiiSendData( u16, u32, unsigned ); +static void TLan_MiiSync( u16 ); +static void TLan_MiiWriteReg( struct device *, u16, u16, u16 ); + +static void TLan_EeSendStart( u16 ); +static int TLan_EeSendByte( u16, u8, int ); +static void TLan_EeReceiveByte( u16, u8 *, int ); +static int TLan_EeReadByte( struct device *, u8, u8 * ); + + +static TLanIntVectorFunc *TLanIntVector[TLAN_INT_NUMBER_OF_INTS] = { + TLan_HandleInvalid, + TLan_HandleTxEOF, + TLan_HandleStatOverflow, + TLan_HandleRxEOF, + TLan_HandleDummy, + TLan_HandleTxEOC, + TLan_HandleStatusCheck, + TLan_HandleRxEOC +}; + +static inline void +TLan_SetTimer( struct device *dev, u32 ticks, u32 type ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + + cli(); + if ( priv->timer.function != NULL ) { + return; + } + priv->timer.function = &TLan_Timer; + sti(); + + priv->timer.data = (unsigned long) dev; + priv->timer.expires = jiffies + ticks; + priv->timerSetAt = jiffies; + priv->timerType = type; + add_timer( &priv->timer ); + +} /* TLan_SetTimer */ + + +/***************************************************************************** +****************************************************************************** + + ThunderLAN Driver Primary Functions + + These functions are more or less common to all Linux network drivers. + +****************************************************************************** +*****************************************************************************/ + + +#ifdef MODULE + + /*************************************************************** + * init_module + * + * Returns: + * 0 if module installed ok, non-zero if not. + * Parms: + * None + * + * This function begins the setup of the driver creating a + * pad buffer, finding all TLAN devices (matching + * TLanAdapterList entries), and creating and initializing a + * device structure for each adapter. + * + **************************************************************/ + +extern int init_module(void) +{ + TLanPrivateInfo *priv; + u8 bus; + struct device *dev; + size_t dev_size; + u8 dfn; + u32 index; + int failed; + int found; + u32 io_base; + u8 irq; + u8 rev; + + printk( "TLAN driver, v%d.%d, (C) 1997-8 Caldera, Inc.\n", + TLanVersionMajor, + TLanVersionMinor + ); + TLanPadBuffer = (u8 *) kmalloc( TLAN_MIN_FRAME_SIZE, + ( GFP_KERNEL | GFP_DMA ) + ); + if ( TLanPadBuffer == NULL ) { + printk( "TLAN: Could not allocate memory for pad buffer.\n" ); + return -ENOMEM; + } + + memset( TLanPadBuffer, 0, TLAN_MIN_FRAME_SIZE ); + + dev_size = sizeof(struct device) + sizeof(TLanPrivateInfo); + + while ( ( found = TLan_PciProbe( &bus, &dfn, &irq, &rev, &io_base, &index ) ) ) { + dev = (struct device *) kmalloc( dev_size, GFP_KERNEL ); + if ( dev == NULL ) { + printk( "TLAN: Could not allocate memory for device.\n" ); + continue; + } + memset( dev, 0, dev_size ); + + dev->priv = priv = ( (void *) dev ) + sizeof(struct device); + dev->name = priv->devName; + strcpy( priv->devName, " " ); + dev->base_addr = io_base; + dev->irq = irq; + dev->init = TLan_Init; + + priv->adapter = &TLanAdapterList[index]; + priv->adapterRev = rev; + priv->aui = aui; + if ( ( duplex != 1 ) && ( duplex != 2 ) ) { + duplex = 0; + } + priv->duplex = duplex; + if ( ( speed != 10 ) && ( speed != 100 ) ) { + speed = 0; + } + priv->speed = speed; + priv->sa_int = sa_int; + priv->debug = debug; + + ether_setup( dev ); + + failed = register_netdev( dev ); + + if ( failed ) { + printk( "TLAN: Could not register device.\n" ); + kfree( dev ); + } else { + priv->nextDevice = TLanDevices; + TLanDevices = dev; + TLanDevicesInstalled++; + printk("TLAN: %s irq=%2d io=%04x, %s, Rev. %d\n", + dev->name, + (int) dev->irq, + (int) dev->base_addr, + priv->adapter->deviceLabel, + priv->adapterRev ); + } + } + + /* printk( "TLAN: Found %d device(s).\n", TLanDevicesInstalled ); */ + + return ( ( TLanDevicesInstalled >= 0 ) ? 0 : -ENODEV ); + +} /* init_module */ + + + + + /*************************************************************** + * cleanup_module + * + * Returns: + * Nothing + * Parms: + * None + * + * Goes through the TLanDevices list and frees the device + * structs and memory associated with each device (lists + * and buffers). It also ureserves the IO port regions + * associated with this device. + * + **************************************************************/ + +extern void cleanup_module(void) +{ + struct device *dev; + TLanPrivateInfo *priv; + + while ( TLanDevicesInstalled ) { + dev = TLanDevices; + priv = (TLanPrivateInfo *) dev->priv; + if ( priv->dmaStorage ) { + kfree( priv->dmaStorage ); + } + release_region( dev->base_addr, 0x10 ); + unregister_netdev( dev ); + TLanDevices = priv->nextDevice; + kfree( dev ); + TLanDevicesInstalled--; + } + kfree( TLanPadBuffer ); + +} /* cleanup_module */ + + +#else /* MODULE */ + + + + + /*************************************************************** + * tlan_probe + * + * Returns: + * 0 on success, error code on error + * Parms: + * dev device struct to use if adapter is + * found. + * + * The name is lower case to fit in with all the rest of + * the netcard_probe names. This function looks for a/ + * another TLan based adapter, setting it up with the + * provided device struct if one is found. + * + **************************************************************/ + +extern int tlan_probe( struct device *dev ) +{ + TLanPrivateInfo *priv; + static int pad_allocated = 0; + int found; + u8 bus, dfn, irq, rev; + u32 io_base, index; + + found = TLan_PciProbe( &bus, &dfn, &irq, &rev, &io_base, &index ); + + if ( ! found ) { + return -ENODEV; + } + + dev->priv = kmalloc( sizeof(TLanPrivateInfo), GFP_KERNEL ); + + if ( dev->priv == NULL ) { + printk( "TLAN: Could not allocate memory for device.\n" ); + return -ENOMEM; + } + + memset( dev->priv, 0, sizeof(TLanPrivateInfo) ); + + if ( ! pad_allocated ) { + TLanPadBuffer = (u8 *) kmalloc( TLAN_MIN_FRAME_SIZE, +// ( GFP_KERNEL | GFP_DMA ) + ( GFP_KERNEL ) + ); + if ( TLanPadBuffer == NULL ) { + printk( "TLAN: Could not allocate memory for padding.\n" ); + kfree( dev->priv ); + return -ENOMEM; + } else { + pad_allocated = 1; + memset( TLanPadBuffer, 0, TLAN_MIN_FRAME_SIZE ); + } + } + + priv = (TLanPrivateInfo *) dev->priv; + + dev->name = priv->devName; + strcpy( priv->devName, " " ); + + dev = init_etherdev( dev, sizeof(TLanPrivateInfo) ); + + dev->base_addr = io_base; + dev->irq = irq; + + + priv->adapter = &TLanAdapterList[index]; + priv->adapterRev = rev; + priv->aui = dev->mem_start & 0x01; + priv->duplex = ( ( dev->mem_start & 0x0C ) == 0x0C ) ? 0 : ( dev->mem_start & 0x0C ) >> 2; + priv->speed = ( ( dev->mem_start & 0x30 ) == 0x30 ) ? 0 : ( dev->mem_start & 0x30 ) >> 4; + if ( priv->speed == 0x1 ) { + priv->speed = TLAN_SPEED_10; + } else if ( priv->speed == 0x2 ) { + priv->speed = TLAN_SPEED_100; + } + priv->sa_int = dev->mem_start & 0x02; + priv->debug = dev->mem_end; + + + printk("TLAN %d.%d: %s irq=%2d io=%04x, %s, Rev. %d\n", + TLanVersionMajor, + TLanVersionMinor, + dev->name, + (int) irq, + io_base, + priv->adapter->deviceLabel, + priv->adapterRev ); + + TLan_Init( dev ); + + return 0; + +} /* tlan_probe */ + + +#endif /* MODULE */ + + + + + /*************************************************************** + * TLan_PciProbe + * + * Returns: + * 1 if another TLAN card was found, 0 if not. + * Parms: + * pci_bus The PCI bus the card was found + * on. + * pci_dfn The PCI whatever the card was + * found at. + * pci_irq The IRQ of the found adapter. + * pci_rev The revision of the adapter. + * pci_io_base The first IO port used by the + * adapter. + * dl_ix The index in the device list + * of the adapter. + * + * This function searches for an adapter with PCI vendor + * and device IDs matching those in the TLanAdapterList. + * The function 'remembers' the last device it found, + * and so finds a new device (if anymore are to be found) + * each time the function is called. It then looks up + * pertinent PCI info and returns it to the caller. + * + **************************************************************/ + +int TLan_PciProbe( u8 *pci_bus, u8 *pci_dfn, u8 *pci_irq, u8 *pci_rev, u32 *pci_io_base, u32 *dl_ix ) +{ + static int dl_index = 0; + static int pci_index = 0; + + int not_found; + u8 pci_latency; + u16 pci_command; + int reg; + + + if ( ! pcibios_present() ) { + printk( "TLAN: PCI Bios not present.\n" ); + return 0; + } + + for (; TLanAdapterList[dl_index].vendorId != 0; dl_index++) { + + not_found = pcibios_find_device( + TLanAdapterList[dl_index].vendorId, + TLanAdapterList[dl_index].deviceId, + pci_index, + pci_bus, + pci_dfn + ); + + if ( ! not_found ) { + + TLAN_DBG( + TLAN_DEBUG_GNRL, + "TLAN: found: Vendor Id = 0x%hx, Device Id = 0x%hx\n", + TLanAdapterList[dl_index].vendorId, + TLanAdapterList[dl_index].deviceId + ); + + pcibios_read_config_byte ( *pci_bus, *pci_dfn, PCI_REVISION_ID, pci_rev); + pcibios_read_config_byte ( *pci_bus, *pci_dfn, PCI_INTERRUPT_LINE, pci_irq); + pcibios_read_config_word ( *pci_bus, *pci_dfn, PCI_COMMAND, &pci_command); + pcibios_read_config_dword( *pci_bus, *pci_dfn, PCI_BASE_ADDRESS_0, pci_io_base); + pcibios_read_config_byte ( *pci_bus, *pci_dfn, PCI_LATENCY_TIMER, &pci_latency); + + if (pci_latency < 0x10) { + pcibios_write_config_byte( *pci_bus, *pci_dfn, PCI_LATENCY_TIMER, 0xff); + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: Setting latency timer to max.\n"); + } + + for ( reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg +=4 ) { + pcibios_read_config_dword( *pci_bus, *pci_dfn, reg, pci_io_base); + if ((pci_command & PCI_COMMAND_IO) && (*pci_io_base & 0x3)) { + *pci_io_base &= PCI_BASE_ADDRESS_IO_MASK; + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: IO mapping is available at %x.\n", *pci_io_base); + break; + } else { + *pci_io_base = 0; + } + } + + if ( *pci_io_base == 0 ) + printk("TLAN: IO mapping not available, ignoring device.\n"); + + if ( ! ( pci_command & PCI_COMMAND_MASTER ) ) { + pcibios_write_config_word ( *pci_bus, *pci_dfn, PCI_COMMAND, pci_command | PCI_COMMAND_MASTER ); + printk( "TLAN: Activating PCI bus mastering for this device.\n" ); + } + + pci_index++; + + if ( *pci_io_base ) { + *dl_ix = dl_index; + return 1; + } + + } else { + pci_index = 0; + } + } + + return 0; + +} /* TLan_PciProbe */ + + + + + /*************************************************************** + * TLan_Init + * + * Returns: + * 0 on success, error code otherwise. + * Parms: + * dev The structure of the device to be + * init'ed. + * + * This function completes the initialization of the + * device structure and driver. It reserves the IO + * addresses, allocates memory for the lists and bounce + * buffers, retrieves the MAC address from the eeprom + * and assignes the device's methods. + * + **************************************************************/ + +int TLan_Init( struct device *dev ) +{ + int dma_size; + int err; + int i; + TLanPrivateInfo *priv; + + priv = (TLanPrivateInfo *) dev->priv; + + err = check_region( dev->base_addr, 0x10 ); + if ( err ) { + printk( "TLAN: %s: Io port region 0x%lx size 0x%x in use.\n", + dev->name, + dev->base_addr, + 0x10 ); + return -EIO; + } + request_region( dev->base_addr, 0x10, TLanSignature ); + + if ( bbuf ) { + dma_size = ( TLAN_NUM_RX_LISTS + TLAN_NUM_TX_LISTS ) + * ( sizeof(TLanList) + TLAN_MAX_FRAME_SIZE ); + } else { + dma_size = ( TLAN_NUM_RX_LISTS + TLAN_NUM_TX_LISTS ) + * ( sizeof(TLanList) ); + } + + priv->dmaStorage = kmalloc( dma_size, GFP_KERNEL | GFP_DMA ); + if ( priv->dmaStorage == NULL ) { + printk( "TLAN: Could not allocate lists and buffers for %s.\n", + dev->name ); + return -ENOMEM; + } + memset( priv->dmaStorage, 0, dma_size ); + priv->rxList = (TLanList *) + ( ( ( (u32) priv->dmaStorage ) + 7 ) & 0xFFFFFFF8 ); + priv->txList = priv->rxList + TLAN_NUM_RX_LISTS; + + if ( bbuf ) { + priv->rxBuffer = (u8 *) ( priv->txList + TLAN_NUM_TX_LISTS ); + priv->txBuffer = priv->rxBuffer + + ( TLAN_NUM_RX_LISTS * TLAN_MAX_FRAME_SIZE ); + } + + err = 0; + for ( i = 0; i < 6 ; i++ ) + err |= TLan_EeReadByte( dev, + (u8) priv->adapter->addrOfs + i, + (u8 *) &dev->dev_addr[i] ); + if ( err ) { + printk( "TLAN: %s: Error reading MAC from eeprom: %d\n", + dev->name, + err ); + } + + dev->addr_len = 6; + + dev->open = &TLan_Open; + dev->hard_start_xmit = &TLan_StartTx; + dev->stop = &TLan_Close; + dev->get_stats = &TLan_GetStats; + dev->set_multicast_list = &TLan_SetMulticastList; + + + return 0; + +} /* TLan_Init */ + + + + + /*************************************************************** + * TLan_Open + * + * Returns: + * 0 on success, error code otherwise. + * Parms: + * dev Structure of device to be opened. + * + * This routine puts the driver and TLAN adapter in a + * state where it is ready to send and receive packets. + * It allocates the IRQ, resets and brings the adapter + * out of reset, and allows interrupts. It also delays + * the startup for autonegotiation or sends a Rx GO + * command to the adapter, as appropriate. + * + **************************************************************/ + +int TLan_Open( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + int err; + + priv->tlanRev = TLan_DioRead8( dev->base_addr, TLAN_DEF_REVISION ); + if ( priv->sa_int ) { + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: Using SA_INTERRUPT\n" ); + err = request_irq( dev->irq, TLan_HandleInterrupt, SA_SHIRQ | SA_INTERRUPT, TLanSignature, dev ); + } else { + err = request_irq( dev->irq, TLan_HandleInterrupt, SA_SHIRQ, TLanSignature, dev ); + } + if ( err ) { + printk( "TLAN: Cannot open %s because IRQ %d is already in use.\n", dev->name, dev->irq ); + return -EAGAIN; + } + + MOD_INC_USE_COUNT; + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* NOTE: It might not be necessary to read the stats before a + reset if you don't care what the values are. + */ + TLan_ResetLists( dev ); + TLan_ReadAndClearStats( dev, TLAN_IGNORE ); + TLan_ResetAdapter( dev ); + + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: %s: Opened. TLAN Chip Rev: %x\n", dev->name, priv->tlanRev ); + + return 0; + +} /* TLan_Open */ + + + + + /*************************************************************** + * TLan_StartTx + * + * Returns: + * 0 on success, non-zero on failure. + * Parms: + * skb A pointer to the sk_buff containing the + * frame to be sent. + * dev The device to send the data on. + * + * This function adds a frame to the Tx list to be sent + * ASAP. First it verifies that the adapter is ready and + * there is room in the queue. Then it sets up the next + * available list, copies the frame to the corresponding + * buffer. If the adapter Tx channel is idle, it gives + * the adapter a Tx Go command on the list, otherwise it + * sets the forward address of the previous list to point + * to this one. Then it frees the sk_buff. + * + **************************************************************/ + +int TLan_StartTx( struct sk_buff *skb, struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + TLanList *tail_list; + u8 *tail_buffer; + int pad; + + if ( ! priv->phyOnline ) { + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: %s PHY is not ready\n", dev->name ); + dev_kfree_skb( skb, FREE_WRITE ); + return 0; + } + + tail_list = priv->txList + priv->txTail; + + if ( tail_list->cStat != TLAN_CSTAT_UNUSED ) { + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: %s is busy (Head=%d Tail=%d)\n", dev->name, priv->txHead, priv->txTail ); + dev->tbusy = 1; + priv->txBusyCount++; + return 1; + } + + tail_list->forward = 0; + + if ( bbuf ) { + tail_buffer = priv->txBuffer + ( priv->txTail * TLAN_MAX_FRAME_SIZE ); + memcpy( tail_buffer, skb->data, skb->len ); + } else { + tail_list->buffer[0].address = virt_to_bus( skb->data ); + tail_list->buffer[9].address = (u32) skb; + } + + pad = TLAN_MIN_FRAME_SIZE - skb->len; + + if ( pad > 0 ) { + tail_list->frameSize = (u16) skb->len + pad; + tail_list->buffer[0].count = (u32) skb->len; + tail_list->buffer[1].count = TLAN_LAST_BUFFER | (u32) pad; + tail_list->buffer[1].address = virt_to_bus( TLanPadBuffer ); + } else { + tail_list->frameSize = (u16) skb->len; + tail_list->buffer[0].count = TLAN_LAST_BUFFER | (u32) skb->len; + tail_list->buffer[1].count = 0; + tail_list->buffer[1].address = 0; + } + + cli(); + tail_list->cStat = TLAN_CSTAT_READY; + if ( ! priv->txInProgress ) { + priv->txInProgress = 1; + outw( 0x4, dev->base_addr + TLAN_HOST_INT ); + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: Starting TX on buffer %d\n", priv->txTail ); + outl( virt_to_bus( tail_list ), dev->base_addr + TLAN_CH_PARM ); + outl( TLAN_HC_GO | TLAN_HC_ACK, dev->base_addr + TLAN_HOST_CMD ); + } else { + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: Adding buffer %d to TX channel\n", priv->txTail ); + if ( priv->txTail == 0 ) { + ( priv->txList + ( TLAN_NUM_TX_LISTS - 1 ) )->forward = virt_to_bus( tail_list ); + } else { + ( priv->txList + ( priv->txTail - 1 ) )->forward = virt_to_bus( tail_list ); + } + } + sti(); + + CIRC_INC( priv->txTail, TLAN_NUM_TX_LISTS ); + + if ( bbuf ) { + dev_kfree_skb( skb, FREE_WRITE ); + } + + dev->trans_start = jiffies; + return 0; + +} /* TLan_StartTx */ + + + + + /*************************************************************** + * TLan_HandleInterrupt + * + * Returns: + * Nothing + * Parms: + * irq The line on which the interrupt + * occurred. + * dev_id A pointer to the device assigned to + * this irq line. + * regs ??? + * + * This function handles an interrupt generated by its + * assigned TLAN adapter. The function deactivates + * interrupts on its adapter, records the type of + * interrupt, executes the appropriate subhandler, and + * acknowdges the interrupt to the adapter (thus + * re-enabling adapter interrupts. + * + **************************************************************/ + +void TLan_HandleInterrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 ack; + struct device *dev; + u32 host_cmd; + u16 host_int; + int type; + + dev = (struct device *) dev_id; + + cli(); + if ( dev->interrupt ) { + printk( "TLAN: Re-entering interrupt handler for %s: %d.\n" , dev->name, dev->interrupt ); + } + dev->interrupt++; + + host_int = inw( dev->base_addr + TLAN_HOST_INT ); + outw( host_int, dev->base_addr + TLAN_HOST_INT ); + + type = ( host_int & TLAN_HI_IT_MASK ) >> 2; + + ack = TLanIntVector[type]( dev, host_int ); + + if ( ack ) { + host_cmd = TLAN_HC_ACK | ack | ( type << 18 ); + outl( host_cmd, dev->base_addr + TLAN_HOST_CMD ); + } + + dev->interrupt--; + sti(); + +} /* TLan_HandleInterrupts */ + + + + + /*************************************************************** + * TLan_Close + * + * Returns: + * An error code. + * Parms: + * dev The device structure of the device to + * close. + * + * This function shuts down the adapter. It records any + * stats, puts the adapter into reset state, deactivates + * its time as needed, and frees the irq it is using. + * + **************************************************************/ + +int TLan_Close(struct device *dev) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + + dev->start = 0; + dev->tbusy = 1; + + TLan_ReadAndClearStats( dev, TLAN_RECORD ); + outl( TLAN_HC_AD_RST, dev->base_addr + TLAN_HOST_CMD ); + if ( priv->timer.function != NULL ) + del_timer( &priv->timer ); + free_irq( dev->irq, dev ); + TLan_FreeLists( dev ); + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: Device %s closed.\n", dev->name ); + + MOD_DEC_USE_COUNT; + + return 0; + +} /* TLan_Close */ + + + + + /*************************************************************** + * TLan_GetStats + * + * Returns: + * A pointer to the device's statistics structure. + * Parms: + * dev The device structure to return the + * stats for. + * + * This function updates the devices statistics by reading + * the TLAN chip's onboard registers. Then it returns the + * address of the statistics structure. + * + **************************************************************/ + +struct net_device_stats *TLan_GetStats( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + int i; + + /* Should only read stats if open ? */ + TLan_ReadAndClearStats( dev, TLAN_RECORD ); + + TLAN_DBG( TLAN_DEBUG_RX, "TLAN RECEIVE: %s EOC count = %d\n", dev->name, priv->rxEocCount ); + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: %s Busy count = %d\n", dev->name, priv->txBusyCount ); + if ( debug & TLAN_DEBUG_GNRL ) { + TLan_PrintDio( dev->base_addr ); + TLan_PhyPrint( dev ); + } + if ( debug & TLAN_DEBUG_LIST ) { + for ( i = 0; i < TLAN_NUM_RX_LISTS; i++ ) + TLan_PrintList( priv->rxList + i, "RX", i ); + for ( i = 0; i < TLAN_NUM_TX_LISTS; i++ ) + TLan_PrintList( priv->txList + i, "TX", i ); + } + + return ( &( (TLanPrivateInfo *) dev->priv )->stats ); + +} /* TLan_GetStats */ + + + + + /*************************************************************** + * TLan_SetMulticastList + * + * Returns: + * Nothing + * Parms: + * dev The device structure to set the + * multicast list for. + * + * This function sets the TLAN adaptor to various receive + * modes. If the IFF_PROMISC flag is set, promiscuous + * mode is acitviated. Otherwise, promiscuous mode is + * turned off. If the IFF_ALLMULTI flag is set, then + * the hash table is set to receive all group addresses. + * Otherwise, the first three multicast addresses are + * stored in AREG_1-3, and the rest are selected via the + * hash table, as necessary. + * + **************************************************************/ + +void TLan_SetMulticastList( struct device *dev ) +{ + struct dev_mc_list *dmi = dev->mc_list; + u32 hash1 = 0; + u32 hash2 = 0; + int i; + u32 offset; + u8 tmp; + + if ( dev->flags & IFF_PROMISC ) { + tmp = TLan_DioRead8( dev->base_addr, TLAN_NET_CMD ); + TLan_DioWrite8( dev->base_addr, TLAN_NET_CMD, tmp | TLAN_NET_CMD_CAF ); + } else { + tmp = TLan_DioRead8( dev->base_addr, TLAN_NET_CMD ); + TLan_DioWrite8( dev->base_addr, TLAN_NET_CMD, tmp & ~TLAN_NET_CMD_CAF ); + if ( dev->flags & IFF_ALLMULTI ) { + for ( i = 0; i < 3; i++ ) + TLan_SetMac( dev, i + 1, NULL ); + TLan_DioWrite32( dev->base_addr, TLAN_HASH_1, 0xFFFFFFFF ); + TLan_DioWrite32( dev->base_addr, TLAN_HASH_2, 0xFFFFFFFF ); + } else { + for ( i = 0; i < dev->mc_count; i++ ) { + if ( i < 3 ) { + TLan_SetMac( dev, i + 1, (char *) &dmi->dmi_addr ); + } else { + offset = TLan_HashFunc( (u8 *) &dmi->dmi_addr ); + if ( offset < 32 ) + hash1 |= ( 1 << offset ); + else + hash2 |= ( 1 << ( offset - 32 ) ); + } + dmi = dmi->next; + } + for ( ; i < 3; i++ ) + TLan_SetMac( dev, i + 1, NULL ); + TLan_DioWrite32( dev->base_addr, TLAN_HASH_1, hash1 ); + TLan_DioWrite32( dev->base_addr, TLAN_HASH_2, hash2 ); + } + } + +} /* TLan_SetMulticastList */ + + + +/***************************************************************************** +****************************************************************************** + + ThunderLAN Driver Interrupt Vectors and Table + + Please see Chap. 4, "Interrupt Handling" of the "ThunderLAN + Programmer's Guide" for more informations on handling interrupts + generated by TLAN based adapters. + +****************************************************************************** +*****************************************************************************/ + + + /*************************************************************** + * TLan_HandleInvalid + * + * Returns: + * 0 + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This function handles invalid interrupts. This should + * never happen unless some other adapter is trying to use + * the IRQ line assigned to the device. + * + **************************************************************/ + +u32 TLan_HandleInvalid( struct device *dev, u16 host_int ) +{ + host_int = 0; + /* printk( "TLAN: Invalid interrupt on %s.\n", dev->name ); */ + return 0; + +} /* TLan_HandleInvalid */ + + + + + /*************************************************************** + * TLan_HandleTxEOF + * + * Returns: + * 1 + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This function handles Tx EOF interrupts which are raised + * by the adapter when it has completed sending the + * contents of a buffer. If detemines which list/buffer + * was completed and resets it. If the buffer was the last + * in the channel (EOC), then the function checks to see if + * another buffer is ready to send, and if so, sends a Tx + * Go command. Finally, the driver activates/continues the + * activity LED. + * + **************************************************************/ + +u32 TLan_HandleTxEOF( struct device *dev, u16 host_int ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + int eoc = 0; + TLanList *head_list; + u32 ack = 1; + + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: Handling TX EOF (Head=%d Tail=%d)\n", priv->txHead, priv->txTail ); + host_int = 0; + head_list = priv->txList + priv->txHead; + + if ( ! bbuf ) { + dev_kfree_skb( (struct sk_buff *) head_list->buffer[9].address, FREE_WRITE ); + head_list->buffer[9].address = 0; + } + + if ( head_list->cStat & TLAN_CSTAT_EOC ) + eoc = 1; + if ( ! head_list->cStat & TLAN_CSTAT_FRM_CMP ) { + printk( "TLAN: Received interrupt for uncompleted TX frame.\n" ); + } + +#if LINUX_KERNEL_VERSION > 0x20100 + priv->stats->tx_bytes += head_list->frameSize; +#endif + + head_list->cStat = TLAN_CSTAT_UNUSED; + dev->tbusy = 0; + CIRC_INC( priv->txHead, TLAN_NUM_TX_LISTS ); + if ( eoc ) { + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: Handling TX EOC (Head=%d Tail=%d)\n", priv->txHead, priv->txTail ); + head_list = priv->txList + priv->txHead; + if ( ( head_list->cStat & TLAN_CSTAT_READY ) == TLAN_CSTAT_READY ) { + outl( virt_to_bus( head_list ), dev->base_addr + TLAN_CH_PARM ); + ack |= TLAN_HC_GO; + } else { + priv->txInProgress = 0; + } + } + + if ( priv->adapter->flags & TLAN_ADAPTER_ACTIVITY_LED ) { + TLan_DioWrite8( dev->base_addr, TLAN_LED_REG, TLAN_LED_LINK | TLAN_LED_ACT ); + if ( priv->timer.function == NULL ) { + TLan_SetTimer( dev, TLAN_TIMER_ACT_DELAY, TLAN_TIMER_ACTIVITY ); + } else if ( priv->timerType == TLAN_TIMER_ACTIVITY ) { + priv->timerSetAt = jiffies; + } + } + + return ack; + +} /* TLan_HandleTxEOF */ + + + + + /*************************************************************** + * TLan_HandleStatOverflow + * + * Returns: + * 1 + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This function handles the Statistics Overflow interrupt + * which means that one or more of the TLAN statistics + * registers has reached 1/2 capacity and needs to be read. + * + **************************************************************/ + +u32 TLan_HandleStatOverflow( struct device *dev, u16 host_int ) +{ + host_int = 0; + TLan_ReadAndClearStats( dev, TLAN_RECORD ); + + return 1; + +} /* TLan_HandleStatOverflow */ + + + + + /*************************************************************** + * TLan_HandleRxEOF + * + * Returns: + * 1 + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This function handles the Rx EOF interrupt which + * indicates a frame has been received by the adapter from + * the net and the frame has been transferred to memory. + * The function determines the bounce buffer the frame has + * been loaded into, creates a new sk_buff big enough to + * hold the frame, and sends it to protocol stack. It + * then resets the used buffer and appends it to the end + * of the list. If the frame was the last in the Rx + * channel (EOC), the function restarts the receive channel + * by sending an Rx Go command to the adapter. Then it + * activates/continues the activity LED. + * + **************************************************************/ + +u32 TLan_HandleRxEOF( struct device *dev, u16 host_int ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u32 ack = 1; + int eoc = 0; + u8 *head_buffer; + TLanList *head_list; + struct sk_buff *skb; + TLanList *tail_list; + void *t; + + TLAN_DBG( TLAN_DEBUG_RX, "TLAN RECEIVE: Handling RX EOF (Head=%d Tail=%d)\n", priv->rxHead, priv->rxTail ); + host_int = 0; + head_list = priv->rxList + priv->rxHead; + tail_list = priv->rxList + priv->rxTail; + + if ( head_list->cStat & TLAN_CSTAT_EOC ) { + eoc = 1; + } + + if ( ! head_list->cStat & TLAN_CSTAT_FRM_CMP ) { + printk( "TLAN: Received interrupt for uncompleted RX frame.\n" ); + } else if ( bbuf ) { + skb = dev_alloc_skb( head_list->frameSize + 7 ); + if ( skb == NULL ) { + printk( "TLAN: Couldn't allocate memory for received data.\n" ); + } else { + head_buffer = priv->rxBuffer + ( priv->rxHead * TLAN_MAX_FRAME_SIZE ); + skb->dev = dev; + skb_reserve( skb, 2 ); + t = (void *) skb_put( skb, head_list->frameSize ); + +#if LINUX_KERNEL_VERSION > 0x20100 + priv->stats->rx_bytes += head_list->frameSize; +#endif + + memcpy( t, head_buffer, head_list->frameSize ); + skb->protocol = eth_type_trans( skb, dev ); + netif_rx( skb ); + } + } else { + skb = (struct sk_buff *) head_list->buffer[9].address; + head_list->buffer[9].address = 0; + skb_trim( skb, head_list->frameSize ); + +#if LINUX_KERNEL_VERSION > 0x20100 + priv->stats->rx_bytes += head_list->frameSize; +#endif + + skb->protocol = eth_type_trans( skb, dev ); + netif_rx( skb ); + + skb = dev_alloc_skb( TLAN_MAX_FRAME_SIZE + 7 ); + if ( skb == NULL ) { + printk( "TLAN: Couldn't allocate memory for received data.\n" ); + /* If this ever happened it would be a problem */ + } else { + skb->dev = dev; + skb_reserve( skb, 2 ); + t = (void *) skb_put( skb, TLAN_MAX_FRAME_SIZE ); + head_list->buffer[0].address = virt_to_bus( t ); + head_list->buffer[9].address = (u32) skb; + } + } + + head_list->forward = 0; + head_list->frameSize = TLAN_MAX_FRAME_SIZE; + head_list->buffer[0].count = TLAN_MAX_FRAME_SIZE | TLAN_LAST_BUFFER; + tail_list->forward = virt_to_bus( head_list ); + + CIRC_INC( priv->rxHead, TLAN_NUM_RX_LISTS ); + CIRC_INC( priv->rxTail, TLAN_NUM_RX_LISTS ); + + if ( eoc ) { + TLAN_DBG( TLAN_DEBUG_RX, "TLAN RECEIVE: Handling RX EOC (Head=%d Tail=%d)\n", priv->rxHead, priv->rxTail ); + head_list = priv->rxList + priv->rxHead; + outl( virt_to_bus( head_list ), dev->base_addr + TLAN_CH_PARM ); + ack |= TLAN_HC_GO | TLAN_HC_RT; + priv->rxEocCount++; + } + + if ( priv->adapter->flags & TLAN_ADAPTER_ACTIVITY_LED ) { + TLan_DioWrite8( dev->base_addr, TLAN_LED_REG, TLAN_LED_LINK | TLAN_LED_ACT ); + if ( priv->timer.function == NULL ) { + TLan_SetTimer( dev, TLAN_TIMER_ACT_DELAY, TLAN_TIMER_ACTIVITY ); + } else if ( priv->timerType == TLAN_TIMER_ACTIVITY ) { + priv->timerSetAt = jiffies; + } + } + + dev->last_rx = jiffies; + + return ack; + +} /* TLan_HandleRxEOF */ + + + + + /*************************************************************** + * TLan_HandleDummy + * + * Returns: + * 1 + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This function handles the Dummy interrupt, which is + * raised whenever a test interrupt is generated by setting + * the Req_Int bit of HOST_CMD to 1. + * + **************************************************************/ + +u32 TLan_HandleDummy( struct device *dev, u16 host_int ) +{ + host_int = 0; + printk( "TLAN: Test interrupt on %s.\n", dev->name ); + return 1; + +} /* TLan_HandleDummy */ + + + + + /*************************************************************** + * TLan_HandleTxEOC + * + * Returns: + * 1 + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This driver is structured to determine EOC occurances by + * reading the CSTAT member of the list structure. Tx EOC + * interrupts are disabled via the DIO INTDIS register. + * However, TLAN chips before revision 3.0 didn't have this + * functionality, so process EOC events if this is the + * case. + * + **************************************************************/ + +u32 TLan_HandleTxEOC( struct device *dev, u16 host_int ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + TLanList *head_list; + u32 ack = 1; + + host_int = 0; + if ( priv->tlanRev < 0x30 ) { + TLAN_DBG( TLAN_DEBUG_TX, "TLAN TRANSMIT: Handling TX EOC (Head=%d Tail=%d) -- IRQ\n", priv->txHead, priv->txTail ); + head_list = priv->txList + priv->txHead; + if ( ( head_list->cStat & TLAN_CSTAT_READY ) == TLAN_CSTAT_READY ) { + outl( virt_to_bus( head_list ), dev->base_addr + TLAN_CH_PARM ); + ack |= TLAN_HC_GO; + } else { + priv->txInProgress = 0; + } + } + + return ack; + +} /* TLan_HandleTxEOC */ + + + + + /*************************************************************** + * TLan_HandleStatusCheck + * + * Returns: + * 0 if Adapter check, 1 if Network Status check. + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This function handles Adapter Check/Network Status + * interrupts generated by the adapter. It checks the + * vector in the HOST_INT register to determine if it is + * an Adapter Check interrupt. If so, it resets the + * adapter. Otherwise it clears the status registers + * and services the PHY. + * + **************************************************************/ + +u32 TLan_HandleStatusCheck( struct device *dev, u16 host_int ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u32 ack; + u32 error; + u8 net_sts; + u32 phy; + u16 tlphy_ctl; + u16 tlphy_sts; + + ack = 1; + if ( host_int & TLAN_HI_IV_MASK ) { + error = inl( dev->base_addr + TLAN_CH_PARM ); + printk( "TLAN: %s: Adaptor Error = 0x%x\n", dev->name, error ); + TLan_ReadAndClearStats( dev, TLAN_RECORD ); + outl( TLAN_HC_AD_RST, dev->base_addr + TLAN_HOST_CMD ); + TLan_FreeLists( dev ); + TLan_ResetLists( dev ); + TLan_ResetAdapter( dev ); + dev->tbusy = 0; + ack = 0; + } else { + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: %s: Status Check\n", dev->name ); + phy = priv->phy[priv->phyNum]; + + net_sts = TLan_DioRead8( dev->base_addr, TLAN_NET_STS ); + if ( net_sts ) { + TLan_DioWrite8( dev->base_addr, TLAN_NET_STS, net_sts ); + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: %s: Net_Sts = %x\n", dev->name, (unsigned) net_sts ); + } + if ( ( net_sts & TLAN_NET_STS_MIRQ ) && ( priv->phyNum == 0 ) ) { + TLan_MiiReadReg( dev, phy, TLAN_TLPHY_STS, &tlphy_sts ); + TLan_MiiReadReg( dev, phy, TLAN_TLPHY_CTL, &tlphy_ctl ); + if ( ! ( tlphy_sts & TLAN_TS_POLOK ) && ! ( tlphy_ctl & TLAN_TC_SWAPOL ) ) { + tlphy_ctl |= TLAN_TC_SWAPOL; + TLan_MiiWriteReg( dev, phy, TLAN_TLPHY_CTL, tlphy_ctl); + } else if ( ( tlphy_sts & TLAN_TS_POLOK ) && ( tlphy_ctl & TLAN_TC_SWAPOL ) ) { + tlphy_ctl &= ~TLAN_TC_SWAPOL; + TLan_MiiWriteReg( dev, phy, TLAN_TLPHY_CTL, tlphy_ctl); + } + + if (debug) { + TLan_PhyPrint( dev ); + } + } + } + + return ack; + +} /* TLan_HandleStatusCheck */ + + + + + /*************************************************************** + * TLan_HandleRxEOC + * + * Returns: + * 1 + * Parms: + * dev Device assigned the IRQ that was + * raised. + * host_int The contents of the HOST_INT + * port. + * + * This driver is structured to determine EOC occurances by + * reading the CSTAT member of the list structure. Rx EOC + * interrupts are disabled via the DIO INTDIS register. + * However, TLAN chips before revision 3.0 didn't have this + * CSTAT member or a INTDIS register, so if this chip is + * pre-3.0, process EOC interrupts normally. + * + **************************************************************/ + +u32 TLan_HandleRxEOC( struct device *dev, u16 host_int ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + TLanList *head_list; + u32 ack = 1; + + host_int = 0; + if ( priv->tlanRev < 0x30 ) { + TLAN_DBG( TLAN_DEBUG_RX, "TLAN RECEIVE: Handling RX EOC (Head=%d Tail=%d) -- IRQ\n", priv->rxHead, priv->rxTail ); + head_list = priv->rxList + priv->rxHead; + outl( virt_to_bus( head_list ), dev->base_addr + TLAN_CH_PARM ); + ack |= TLAN_HC_GO | TLAN_HC_RT; + priv->rxEocCount++; + } + + return ack; + +} /* TLan_HandleRxEOC */ + + + + +/***************************************************************************** +****************************************************************************** + + ThunderLAN Driver Timer Function + +****************************************************************************** +*****************************************************************************/ + + + /*************************************************************** + * TLan_Timer + * + * Returns: + * Nothing + * Parms: + * data A value given to add timer when + * add_timer was called. + * + * This function handles timed functionality for the + * TLAN driver. The two current timer uses are for + * delaying for autonegotionation and driving the ACT LED. + * - Autonegotiation requires being allowed about + * 2 1/2 seconds before attempting to transmit a + * packet. It would be a very bad thing to hang + * the kernel this long, so the driver doesn't + * allow transmission 'til after this time, for + * certain PHYs. It would be much nicer if all + * PHYs were interrupt-capable like the internal + * PHY. + * - The ACT LED, which shows adapter activity, is + * driven by the driver, and so must be left on + * for a short period to power up the LED so it + * can be seen. This delay can be changed by + * changing the TLAN_TIMER_ACT_DELAY in tlan.h, + * if desired. 10 jiffies produces a slightly + * sluggish response. + * + **************************************************************/ + +void TLan_Timer( unsigned long data ) +{ + struct device *dev = (struct device *) data; + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u32 elapsed; + + priv->timer.function = NULL; + + switch ( priv->timerType ) { + case TLAN_TIMER_PHY_PDOWN: + TLan_PhyPowerDown( dev ); + break; + case TLAN_TIMER_PHY_PUP: + TLan_PhyPowerUp( dev ); + break; + case TLAN_TIMER_PHY_RESET: + TLan_PhyReset( dev ); + break; + case TLAN_TIMER_PHY_START_LINK: + TLan_PhyStartLink( dev ); + break; + case TLAN_TIMER_PHY_FINISH_AN: + TLan_PhyFinishAutoNeg( dev ); + break; + case TLAN_TIMER_FINISH_RESET: + TLan_FinishReset( dev ); + break; + case TLAN_TIMER_ACTIVITY: + cli(); + if ( priv->timer.function == NULL ) { + elapsed = jiffies - priv->timerSetAt; + if ( elapsed >= TLAN_TIMER_ACT_DELAY ) { + TLan_DioWrite8( dev->base_addr, TLAN_LED_REG, TLAN_LED_LINK ); + } else { + priv->timer.function = &TLan_Timer; + priv->timer.expires = priv->timerSetAt + TLAN_TIMER_ACT_DELAY; + sti(); + add_timer( &priv->timer ); + } + } + sti(); + break; + default: + break; + } + +} /* TLan_Timer */ + + + + +/***************************************************************************** +****************************************************************************** + + ThunderLAN Driver Adapter Related Routines + +****************************************************************************** +*****************************************************************************/ + + + /*************************************************************** + * TLan_ResetLists + * + * Returns: + * Nothing + * Parms: + * dev The device structure with the list + * stuctures to be reset. + * + * This routine sets the variables associated with managing + * the TLAN lists to their initial values. + * + **************************************************************/ + +void TLan_ResetLists( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + int i; + TLanList *list; + struct sk_buff *skb; + void *t = NULL; + + priv->txHead = 0; + priv->txTail = 0; + for ( i = 0; i < TLAN_NUM_TX_LISTS; i++ ) { + list = priv->txList + i; + list->cStat = TLAN_CSTAT_UNUSED; + if ( bbuf ) { + list->buffer[0].address = virt_to_bus( priv->txBuffer + ( i * TLAN_MAX_FRAME_SIZE ) ); + } else { + list->buffer[0].address = 0; + } + list->buffer[2].count = 0; + list->buffer[2].address = 0; + } + + priv->rxHead = 0; + priv->rxTail = TLAN_NUM_RX_LISTS - 1; + for ( i = 0; i < TLAN_NUM_RX_LISTS; i++ ) { + list = priv->rxList + i; + list->cStat = TLAN_CSTAT_READY; + list->frameSize = TLAN_MAX_FRAME_SIZE; + list->buffer[0].count = TLAN_MAX_FRAME_SIZE | TLAN_LAST_BUFFER; + if ( bbuf ) { + list->buffer[0].address = virt_to_bus( priv->rxBuffer + ( i * TLAN_MAX_FRAME_SIZE ) ); + } else { + skb = dev_alloc_skb( TLAN_MAX_FRAME_SIZE + 7 ); + if ( skb == NULL ) { + printk( "TLAN: Couldn't allocate memory for received data.\n" ); + /* If this ever happened it would be a problem */ + } else { + skb->dev = dev; + skb_reserve( skb, 2 ); + t = (void *) skb_put( skb, TLAN_MAX_FRAME_SIZE ); + } + list->buffer[0].address = virt_to_bus( t ); + list->buffer[9].address = (u32) skb; + } + list->buffer[1].count = 0; + list->buffer[1].address = 0; + if ( i < TLAN_NUM_RX_LISTS - 1 ) + list->forward = virt_to_bus( list + 1 ); + else + list->forward = 0; + } + +} /* TLan_ResetLists */ + + +void TLan_FreeLists( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + int i; + TLanList *list; + struct sk_buff *skb; + + if ( ! bbuf ) { + for ( i = 0; i < TLAN_NUM_TX_LISTS; i++ ) { + list = priv->txList + i; + skb = (struct sk_buff *) list->buffer[9].address; + if ( skb ) { + dev_kfree_skb( skb, FREE_WRITE ); + list->buffer[9].address = 0; + } + } + + for ( i = 0; i < TLAN_NUM_RX_LISTS; i++ ) { + list = priv->rxList + i; + skb = (struct sk_buff *) list->buffer[9].address; + if ( skb ) { + dev_kfree_skb( skb, FREE_READ ); + list->buffer[9].address = 0; + } + } + } + +} /* TLan_FreeLists */ + + + + + /*************************************************************** + * TLan_PrintDio + * + * Returns: + * Nothing + * Parms: + * io_base Base IO port of the device of + * which to print DIO registers. + * + * This function prints out all the internal (DIO) + * registers of a TLAN chip. + * + **************************************************************/ + +void TLan_PrintDio( u16 io_base ) +{ + u32 data0, data1; + int i; + + printk( "TLAN: Contents of internal registers for io base 0x%04hx.\n", io_base ); + printk( "TLAN: Off. +0 +4\n" ); + for ( i = 0; i < 0x4C; i+= 8 ) { + data0 = TLan_DioRead32( io_base, i ); + data1 = TLan_DioRead32( io_base, i + 0x4 ); + printk( "TLAN: 0x%02x 0x%08x 0x%08x\n", i, data0, data1 ); + } + +} /* TLan_PrintDio */ + + + + + /*************************************************************** + * TLan_PrintList + * + * Returns: + * Nothing + * Parms: + * list A pointer to the TLanList structure to + * be printed. + * type A string to designate type of list, + * "Rx" or "Tx". + * num The index of the list. + * + * This function prints out the contents of the list + * pointed to by the list parameter. + * + **************************************************************/ + +void TLan_PrintList( TLanList *list, char *type, int num) +{ + int i; + + printk( "TLAN: %s List %d at 0x%08x\n", type, num, (u32) list ); + printk( "TLAN: Forward = 0x%08x\n", list->forward ); + printk( "TLAN: CSTAT = 0x%04hx\n", list->cStat ); + printk( "TLAN: Frame Size = 0x%04hx\n", list->frameSize ); + /* for ( i = 0; i < 10; i++ ) { */ + for ( i = 0; i < 2; i++ ) { + printk( "TLAN: Buffer[%d].count, addr = 0x%08x, 0x%08x\n", i, list->buffer[i].count, list->buffer[i].address ); + } + +} /* TLan_PrintList */ + + + + + /*************************************************************** + * TLan_ReadAndClearStats + * + * Returns: + * Nothing + * Parms: + * dev Pointer to device structure of adapter + * to which to read stats. + * record Flag indicating whether to add + * + * This functions reads all the internal status registers + * of the TLAN chip, which clears them as a side effect. + * It then either adds the values to the device's status + * struct, or discards them, depending on whether record + * is TLAN_RECORD (!=0) or TLAN_IGNORE (==0). + * + **************************************************************/ + +void TLan_ReadAndClearStats( struct device *dev, int record ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u32 tx_good, tx_under; + u32 rx_good, rx_over; + u32 def_tx, crc, code; + u32 multi_col, single_col; + u32 excess_col, late_col, loss; + + outw( TLAN_GOOD_TX_FRMS, dev->base_addr + TLAN_DIO_ADR ); + tx_good = inb( dev->base_addr + TLAN_DIO_DATA ); + tx_good += inb( dev->base_addr + TLAN_DIO_DATA + 1 ) << 8; + tx_good += inb( dev->base_addr + TLAN_DIO_DATA + 2 ) << 16; + tx_under = inb( dev->base_addr + TLAN_DIO_DATA + 3 ); + + outw( TLAN_GOOD_RX_FRMS, dev->base_addr + TLAN_DIO_ADR ); + rx_good = inb( dev->base_addr + TLAN_DIO_DATA ); + rx_good += inb( dev->base_addr + TLAN_DIO_DATA + 1 ) << 8; + rx_good += inb( dev->base_addr + TLAN_DIO_DATA + 2 ) << 16; + rx_over = inb( dev->base_addr + TLAN_DIO_DATA + 3 ); + + outw( TLAN_DEFERRED_TX, dev->base_addr + TLAN_DIO_ADR ); + def_tx = inb( dev->base_addr + TLAN_DIO_DATA ); + def_tx += inb( dev->base_addr + TLAN_DIO_DATA + 1 ) << 8; + crc = inb( dev->base_addr + TLAN_DIO_DATA + 2 ); + code = inb( dev->base_addr + TLAN_DIO_DATA + 3 ); + + outw( TLAN_MULTICOL_FRMS, dev->base_addr + TLAN_DIO_ADR ); + multi_col = inb( dev->base_addr + TLAN_DIO_DATA ); + multi_col += inb( dev->base_addr + TLAN_DIO_DATA + 1 ) << 8; + single_col = inb( dev->base_addr + TLAN_DIO_DATA + 2 ); + single_col += inb( dev->base_addr + TLAN_DIO_DATA + 3 ) << 8; + + outw( TLAN_EXCESSCOL_FRMS, dev->base_addr + TLAN_DIO_ADR ); + excess_col = inb( dev->base_addr + TLAN_DIO_DATA ); + late_col = inb( dev->base_addr + TLAN_DIO_DATA + 1 ); + loss = inb( dev->base_addr + TLAN_DIO_DATA + 2 ); + + if ( record ) { + priv->stats.rx_packets += rx_good; + priv->stats.rx_errors += rx_over + crc + code; + priv->stats.tx_packets += tx_good; + priv->stats.tx_errors += tx_under + loss; + priv->stats.collisions += multi_col + single_col + excess_col + late_col; + + priv->stats.rx_over_errors += rx_over; + priv->stats.rx_crc_errors += crc; + priv->stats.rx_frame_errors += code; + + priv->stats.tx_aborted_errors += tx_under; + priv->stats.tx_carrier_errors += loss; + } + +} /* TLan_ReadAndClearStats */ + + + + + /*************************************************************** + * TLan_Reset + * + * Returns: + * 0 + * Parms: + * dev Pointer to device structure of adapter + * to be reset. + * + * This function resets the adapter and it's physical + * device. See Chap. 3, pp. 9-10 of the "ThunderLAN + * Programmer's Guide" for details. The routine tries to + * implement what is detailed there, though adjustments + * have been made. + * + **************************************************************/ + +void +TLan_ResetAdapter( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + int i; + u32 addr; + u32 data; + u8 data8; + + priv->tlanFullDuplex = FALSE; +/* 1. Assert reset bit. */ + + data = inl(dev->base_addr + TLAN_HOST_CMD); + data |= TLAN_HC_AD_RST; + outl(data, dev->base_addr + TLAN_HOST_CMD); + + udelay(1000); + +/* 2. Turn off interrupts. ( Probably isn't necessary ) */ + + data = inl(dev->base_addr + TLAN_HOST_CMD); + data |= TLAN_HC_INT_OFF; + outl(data, dev->base_addr + TLAN_HOST_CMD); + +/* 3. Clear AREGs and HASHs. */ + + for ( i = TLAN_AREG_0; i <= TLAN_HASH_2; i += 4 ) { + TLan_DioWrite32( dev->base_addr, (u16) i, 0 ); + } + +/* 4. Setup NetConfig register. */ + + data = TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN | TLAN_NET_CFG_PHY_EN; + TLan_DioWrite16( dev->base_addr, TLAN_NET_CONFIG, (u16) data ); + +/* 5. Load Ld_Tmr and Ld_Thr in HOST_CMD. */ + + outl( TLAN_HC_LD_TMR | 0x0, dev->base_addr + TLAN_HOST_CMD ); + outl( TLAN_HC_LD_THR | 0x1, dev->base_addr + TLAN_HOST_CMD ); + +/* 6. Unreset the MII by setting NMRST (in NetSio) to 1. */ + + outw( TLAN_NET_SIO, dev->base_addr + TLAN_DIO_ADR ); + addr = dev->base_addr + TLAN_DIO_DATA + TLAN_NET_SIO; + TLan_SetBit( TLAN_NET_SIO_NMRST, addr ); + +/* 7. Setup the remaining registers. */ + + if ( priv->tlanRev >= 0x30 ) { + data8 = TLAN_ID_TX_EOC | TLAN_ID_RX_EOC; + TLan_DioWrite8( dev->base_addr, TLAN_INT_DIS, data8 ); + } + TLan_PhyDetect( dev ); + data = TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN; + if ( priv->adapter->flags & TLAN_ADAPTER_BIT_RATE_PHY ) { + data |= TLAN_NET_CFG_BIT; + if ( priv->aui == 1 ) { + TLan_DioWrite8( dev->base_addr, TLAN_ACOMMIT, 0x0a ); + } else if ( priv->duplex == TLAN_DUPLEX_FULL ) { + TLan_DioWrite8( dev->base_addr, TLAN_ACOMMIT, 0x00 ); + priv->tlanFullDuplex = TRUE; + } else { + TLan_DioWrite8( dev->base_addr, TLAN_ACOMMIT, 0x08 ); + } + } + if ( priv->phyNum == 0 ) { + data |= TLAN_NET_CFG_PHY_EN; + } + TLan_DioWrite16( dev->base_addr, TLAN_NET_CONFIG, (u16) data ); + + if ( priv->adapter->flags & TLAN_ADAPTER_UNMANAGED_PHY ) { + TLan_FinishReset( dev ); + } else { + TLan_PhyPowerDown( dev ); + } + +} /* TLan_ResetAdapter */ + + + + +void +TLan_FinishReset( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u8 data; + u32 phy; + u8 sio; + u16 status; + u16 tlphy_ctl; + + phy = priv->phy[priv->phyNum]; + + data = TLAN_NET_CMD_NRESET | TLAN_NET_CMD_NWRAP; + if ( priv->tlanFullDuplex ) { + data |= TLAN_NET_CMD_DUPLEX; + } + TLan_DioWrite8( dev->base_addr, TLAN_NET_CMD, data ); + data = TLAN_NET_MASK_MASK4 | TLAN_NET_MASK_MASK5; + if ( priv->phyNum == 0 ) { + data |= TLAN_NET_MASK_MASK7; + } + TLan_DioWrite8( dev->base_addr, TLAN_NET_MASK, data ); + TLan_DioWrite16( dev->base_addr, TLAN_MAX_RX, TLAN_MAX_FRAME_SIZE ); + + if ( ( priv->adapter->flags & TLAN_ADAPTER_UNMANAGED_PHY ) || ( priv->aui ) ) { + status = MII_GS_LINK; + printk( "TLAN: %s: Link forced.\n", dev->name ); + } else { + TLan_MiiReadReg( dev, phy, MII_GEN_STS, &status ); + udelay( 1000 ); + TLan_MiiReadReg( dev, phy, MII_GEN_STS, &status ); + if ( status & MII_GS_LINK ) { + printk( "TLAN: %s: Link active.\n", dev->name ); + TLan_DioWrite8( dev->base_addr, TLAN_LED_REG, TLAN_LED_LINK ); + } + } + + if ( priv->phyNum == 0 ) { + TLan_MiiReadReg( dev, phy, TLAN_TLPHY_CTL, &tlphy_ctl ); + tlphy_ctl |= TLAN_TC_INTEN; + TLan_MiiWriteReg( dev, phy, TLAN_TLPHY_CTL, tlphy_ctl ); + sio = TLan_DioRead8( dev->base_addr, TLAN_NET_SIO ); + sio |= TLAN_NET_SIO_MINTEN; + TLan_DioWrite8( dev->base_addr, TLAN_NET_SIO, sio ); + } + + if ( status & MII_GS_LINK ) { + TLan_SetMac( dev, 0, dev->dev_addr ); + priv->phyOnline = 1; + outb( ( TLAN_HC_INT_ON >> 8 ), dev->base_addr + TLAN_HOST_CMD + 1 ); + if ( debug >= 1 ) { + outb( ( TLAN_HC_REQ_INT >> 8 ), dev->base_addr + TLAN_HOST_CMD + 1 ); + } + outl( virt_to_bus( priv->rxList ), dev->base_addr + TLAN_CH_PARM ); + outl( TLAN_HC_GO | TLAN_HC_RT, dev->base_addr + TLAN_HOST_CMD ); + } else { + printk( "TLAN: %s: Link inactive, will retry in 10 secs...\n", dev->name ); + TLan_SetTimer( dev, 1000, TLAN_TIMER_FINISH_RESET ); + return; + } + +} /* TLan_FinishReset */ + + + + + /*************************************************************** + * TLan_SetMac + * + * Returns: + * Nothing + * Parms: + * dev Pointer to device structure of adapter + * on which to change the AREG. + * areg The AREG to set the address in (0 - 3). + * mac A pointer to an array of chars. Each + * element stores one byte of the address. + * IE, it isn't in ascii. + * + * This function transfers a MAC address to one of the + * TLAN AREGs (address registers). The TLAN chip locks + * the register on writing to offset 0 and unlocks the + * register after writing to offset 5. If NULL is passed + * in mac, then the AREG is filled with 0's. + * + **************************************************************/ + +void TLan_SetMac( struct device *dev, int areg, char *mac ) +{ + int i; + + areg *= 6; + + if ( mac != NULL ) { + for ( i = 0; i < 6; i++ ) + TLan_DioWrite8( dev->base_addr, TLAN_AREG_0 + areg + i, mac[i] ); + } else { + for ( i = 0; i < 6; i++ ) + TLan_DioWrite8( dev->base_addr, TLAN_AREG_0 + areg + i, 0 ); + } + +} /* TLan_SetMac */ + + + + +/***************************************************************************** +****************************************************************************** + + ThunderLAN Driver PHY Layer Routines + +****************************************************************************** +*****************************************************************************/ + + + + /********************************************************************* + * TLan_PhyPrint + * + * Returns: + * Nothing + * Parms: + * dev A pointer to the device structure of the + * TLAN device having the PHYs to be detailed. + * + * This function prints the registers a PHY (aka tranceiver). + * + ********************************************************************/ + +void TLan_PhyPrint( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u16 i, data0, data1, data2, data3, phy; + + phy = priv->phy[priv->phyNum]; + + if ( priv->adapter->flags & TLAN_ADAPTER_UNMANAGED_PHY ) { + printk( "TLAN: Device %s, Unmanaged PHY.\n", dev->name ); + } else if ( phy <= TLAN_PHY_MAX_ADDR ) { + printk( "TLAN: Device %s, PHY 0x%02x.\n", dev->name, phy ); + printk( "TLAN: Off. +0 +1 +2 +3 \n" ); + for ( i = 0; i < 0x20; i+= 4 ) { + printk( "TLAN: 0x%02x", i ); + TLan_MiiReadReg( dev, phy, i, &data0 ); + printk( " 0x%04hx", data0 ); + TLan_MiiReadReg( dev, phy, i + 1, &data1 ); + printk( " 0x%04hx", data1 ); + TLan_MiiReadReg( dev, phy, i + 2, &data2 ); + printk( " 0x%04hx", data2 ); + TLan_MiiReadReg( dev, phy, i + 3, &data3 ); + printk( " 0x%04hx\n", data3 ); + } + } else { + printk( "TLAN: Device %s, Invalid PHY.\n", dev->name ); + } + +} /* TLan_PhyPrint */ + + + + + /********************************************************************* + * TLan_PhyDetect + * + * Returns: + * Nothing + * Parms: + * dev A pointer to the device structure of the adapter + * for which the PHY needs determined. + * + * So far I've found that adapters which have external PHYs + * may also use the internal PHY for part of the functionality. + * (eg, AUI/Thinnet). This function finds out if this TLAN + * chip has an internal PHY, and then finds the first external + * PHY (starting from address 0) if it exists). + * + ********************************************************************/ + +void TLan_PhyDetect( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u16 control; + u16 hi; + u16 lo; + u32 phy; + + if ( priv->adapter->flags & TLAN_ADAPTER_UNMANAGED_PHY ) { + priv->phyNum = 0xFFFF; + return; + } + + TLan_MiiReadReg( dev, TLAN_PHY_MAX_ADDR, MII_GEN_ID_HI, &hi ); + + if ( hi != 0xFFFF ) { + priv->phy[0] = TLAN_PHY_MAX_ADDR; + } else { + priv->phy[0] = TLAN_PHY_NONE; + } + + priv->phy[1] = TLAN_PHY_NONE; + for ( phy = 0; phy <= TLAN_PHY_MAX_ADDR; phy++ ) { + TLan_MiiReadReg( dev, phy, MII_GEN_CTL, &control ); + TLan_MiiReadReg( dev, phy, MII_GEN_ID_HI, &hi ); + TLan_MiiReadReg( dev, phy, MII_GEN_ID_LO, &lo ); + if ( ( control != 0xFFFF ) || ( hi != 0xFFFF ) || ( lo != 0xFFFF ) ) { + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: PHY found at %02x %04x %04x %04x\n", phy, control, hi, lo ); + if ( ( priv->phy[1] == TLAN_PHY_NONE ) && ( phy != TLAN_PHY_MAX_ADDR ) ) { + priv->phy[1] = phy; + } + } + } + + if ( priv->phy[1] != TLAN_PHY_NONE ) { + priv->phyNum = 1; + } else if ( priv->phy[0] != TLAN_PHY_NONE ) { + priv->phyNum = 0; + } else { + printk( "TLAN: Cannot initialize device, no PHY was found!\n" ); + } + +} /* TLan_PhyDetect */ + + + + +void TLan_PhyPowerDown( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u16 value; + + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: %s: Powering down PHY(s).\n", dev->name ); + value = MII_GC_PDOWN | MII_GC_LOOPBK | MII_GC_ISOLATE; + TLan_MiiSync( dev->base_addr ); + TLan_MiiWriteReg( dev, priv->phy[priv->phyNum], MII_GEN_CTL, value ); + if ( ( priv->phyNum == 0 ) && ( priv->phy[1] != TLAN_PHY_NONE ) && ( ! ( priv->adapter->flags & TLAN_ADAPTER_USE_INTERN_10 ) ) ) { + TLan_MiiSync( dev->base_addr ); + TLan_MiiWriteReg( dev, priv->phy[1], MII_GEN_CTL, value ); + } + + /* Wait for 5 jiffies (50 ms) and powerup + * This is abitrary. It is intended to make sure the + * tranceiver settles. + */ + TLan_SetTimer( dev, 5, TLAN_TIMER_PHY_PUP ); + +} /* TLan_PhyPowerDown */ + + + + +void TLan_PhyPowerUp( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u16 value; + + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: %s: Powering up PHY.\n", dev->name ); + TLan_MiiSync( dev->base_addr ); + value = MII_GC_LOOPBK; + TLan_MiiWriteReg( dev, priv->phy[priv->phyNum], MII_GEN_CTL, value ); + + /* Wait for 50 jiffies (500 ms) and reset the + * tranceiver. The TLAN docs say both 50 ms and + * 500 ms, so do the longer, just in case + */ + TLan_SetTimer( dev, 50, TLAN_TIMER_PHY_RESET ); + +} /* TLan_PhyPowerUp */ + + + + +void TLan_PhyReset( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u16 phy; + u16 value; + + phy = priv->phy[priv->phyNum]; + + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: %s: Reseting PHY.\n", dev->name ); + TLan_MiiSync( dev->base_addr ); + value = MII_GC_LOOPBK | MII_GC_RESET; + TLan_MiiWriteReg( dev, phy, MII_GEN_CTL, value ); + TLan_MiiReadReg( dev, phy, MII_GEN_CTL, &value ); + while ( value & MII_GC_RESET ) { + TLan_MiiReadReg( dev, phy, MII_GEN_CTL, &value ); + } + TLan_MiiWriteReg( dev, phy, MII_GEN_CTL, 0 ); + + /* Wait for 50 jiffies (500 ms) and initialize. + * I don't remember why I wait this long. + */ + TLan_SetTimer( dev, 50, TLAN_TIMER_PHY_START_LINK ); + +} /* TLan_PhyReset */ + + + + +void TLan_PhyStartLink( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u16 ability; + u16 control; + u16 data; + u16 phy; + u16 status; + u16 tctl; + + phy = priv->phy[priv->phyNum]; + + TLAN_DBG( TLAN_DEBUG_GNRL, "TLAN: %s: Trying to activate link.\n", dev->name ); + TLan_MiiReadReg( dev, phy, MII_GEN_STS, &status ); + if ( ( status & MII_GS_AUTONEG ) && + ( priv->duplex == TLAN_DUPLEX_DEFAULT ) && + ( priv->speed == TLAN_SPEED_DEFAULT ) && + ( ! priv->aui ) ) { + ability = status >> 11; + + if ( priv->speed == TLAN_SPEED_10 ) { + ability &= 0x0003; + } else if ( priv->speed == TLAN_SPEED_100 ) { + ability &= 0x001C; + } + + if ( priv->duplex == TLAN_DUPLEX_FULL ) { + ability &= 0x000A; + } else if ( priv->duplex == TLAN_DUPLEX_HALF ) { + ability &= 0x0005; + } + + TLan_MiiWriteReg( dev, phy, MII_AN_ADV, ( ability << 5 ) | 1 ); + TLan_MiiWriteReg( dev, phy, MII_GEN_CTL, 0x1000 ); + TLan_MiiWriteReg( dev, phy, MII_GEN_CTL, 0x1200 ); + + /* Wait for 400 jiffies (4 sec) for autonegotiation + * to complete. The max spec time is less than this + * but the card need additional time to start AN. + * .5 sec should be plenty extra. + */ + printk( "TLAN: %s: Starting autonegotiation.\n", dev->name ); + TLan_SetTimer( dev, 400, TLAN_TIMER_PHY_FINISH_AN ); + return; + } + + if ( ( priv->aui ) && ( priv->phyNum != 0 ) ) { + priv->phyNum = 0; + data = TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN | TLAN_NET_CFG_PHY_EN; + TLan_DioWrite16( dev->base_addr, TLAN_NET_CONFIG, data ); + TLan_SetTimer( dev, 4, TLAN_TIMER_PHY_PDOWN ); + return; + } else if ( priv->phyNum == 0 ) { + TLan_MiiReadReg( dev, phy, TLAN_TLPHY_CTL, &tctl ); + if ( priv->aui ) { + tctl |= TLAN_TC_AUISEL; + } else { + tctl &= ~TLAN_TC_AUISEL; + control = 0; + if ( priv->duplex == TLAN_DUPLEX_FULL ) { + control |= MII_GC_DUPLEX; + priv->tlanFullDuplex = TRUE; + } + if ( priv->speed == TLAN_SPEED_100 ) { + control |= MII_GC_SPEEDSEL; + } + TLan_MiiWriteReg( dev, phy, MII_GEN_CTL, control ); + } + TLan_MiiWriteReg( dev, phy, TLAN_TLPHY_CTL, tctl ); + } + + /* Wait for 100 jiffies (1 sec) to give the tranceiver time + * to establish link. + */ + TLan_SetTimer( dev, 100, TLAN_TIMER_FINISH_RESET ); + +} /* TLan_PhyStartLink */ + + + + +void TLan_PhyFinishAutoNeg( struct device *dev ) +{ + TLanPrivateInfo *priv = (TLanPrivateInfo *) dev->priv; + u16 an_adv; + u16 an_lpa; + u16 data; + u16 mode; + u16 phy; + u16 status; + + phy = priv->phy[priv->phyNum]; + + TLan_MiiReadReg( dev, phy, MII_GEN_STS, &status ); + if ( ! ( status & MII_GS_AUTOCMPLT ) ) { + /* Wait for 800 jiffies (8 sec) to give the process + * more time. Perhaps we should fail after a while. + */ + printk( "TLAN: Giving autonegotiation more time.\n" ); + TLan_SetTimer( dev, 800, TLAN_TIMER_PHY_FINISH_AN ); + return; + } + + printk( "TLAN: %s: Autonegotiation complete.\n", dev->name ); + TLan_MiiReadReg( dev, phy, MII_AN_ADV, &an_adv ); + TLan_MiiReadReg( dev, phy, MII_AN_LPA, &an_lpa ); + mode = an_adv & an_lpa & 0x03E0; + if ( mode & 0x0100 ) { + priv->tlanFullDuplex = TRUE; + } else if ( ! ( mode & 0x0080 ) && ( mode & 0x0040 ) ) { + priv->tlanFullDuplex = TRUE; + } + + if ( ( ! ( mode & 0x0180 ) ) && ( priv->adapter->flags & TLAN_ADAPTER_USE_INTERN_10 ) && ( priv->phyNum != 0 ) ) { + priv->phyNum = 0; + data = TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN | TLAN_NET_CFG_PHY_EN; + TLan_DioWrite16( dev->base_addr, TLAN_NET_CONFIG, data ); + TLan_SetTimer( dev, 40, TLAN_TIMER_PHY_PDOWN ); + return; + } + + if ( priv->phyNum == 0 ) { + if ( ( priv->duplex == TLAN_DUPLEX_FULL ) || ( an_adv & an_lpa & 0x0040 ) ) { + TLan_MiiWriteReg( dev, phy, MII_GEN_CTL, MII_GC_AUTOENB | MII_GC_DUPLEX ); + printk( "TLAN: Starting internal PHY with DUPLEX\n" ); + } else { + TLan_MiiWriteReg( dev, phy, MII_GEN_CTL, MII_GC_AUTOENB ); + printk( "TLAN: Starting internal PHY with HALF-DUPLEX\n" ); + } + } + + /* Wait for 10 jiffies (100 ms). No reason in partiticular. + */ + TLan_SetTimer( dev, 10, TLAN_TIMER_FINISH_RESET ); + +} /* TLan_PhyFinishAutoNeg */ + + + + +/***************************************************************************** +****************************************************************************** + + ThunderLAN Driver MII Routines + + These routines are based on the information in Chap. 2 of the + "ThunderLAN Programmer's Guide", pp. 15-24. + +****************************************************************************** +*****************************************************************************/ + + + /*************************************************************** + * TLan_MiiReadReg + * + * Returns: + * 0 if ack received ok + * 1 otherwise. + * + * Parms: + * dev The device structure containing + * The io address and interrupt count + * for this device. + * phy The address of the PHY to be queried. + * reg The register whose contents are to be + * retreived. + * val A pointer to a variable to store the + * retrieved value. + * + * This function uses the TLAN's MII bus to retreive the contents + * of a given register on a PHY. It sends the appropriate info + * and then reads the 16-bit register value from the MII bus via + * the TLAN SIO register. + * + **************************************************************/ + +int TLan_MiiReadReg( struct device *dev, u16 phy, u16 reg, u16 *val ) +{ + u8 nack; + u16 sio, tmp; + u32 i; + int err; + int minten; + + err = FALSE; + outw(TLAN_NET_SIO, dev->base_addr + TLAN_DIO_ADR); + sio = dev->base_addr + TLAN_DIO_DATA + TLAN_NET_SIO; + + if ( dev->interrupt == 0 ) + cli(); + dev->interrupt++; + + TLan_MiiSync(dev->base_addr); + + minten = TLan_GetBit( TLAN_NET_SIO_MINTEN, sio ); + if ( minten ) + TLan_ClearBit(TLAN_NET_SIO_MINTEN, sio); + + TLan_MiiSendData( dev->base_addr, 0x1, 2 ); /* Start ( 01b ) */ + TLan_MiiSendData( dev->base_addr, 0x2, 2 ); /* Read ( 10b ) */ + TLan_MiiSendData( dev->base_addr, phy, 5 ); /* Device # */ + TLan_MiiSendData( dev->base_addr, reg, 5 ); /* Register # */ + + + TLan_ClearBit(TLAN_NET_SIO_MTXEN, sio); /* Change direction */ + + TLan_ClearBit(TLAN_NET_SIO_MCLK, sio); /* Clock Idle bit */ + TLan_SetBit(TLAN_NET_SIO_MCLK, sio); + TLan_ClearBit(TLAN_NET_SIO_MCLK, sio); /* Wait 300ns */ + + nack = TLan_GetBit(TLAN_NET_SIO_MDATA, sio); /* Check for ACK */ + TLan_SetBit(TLAN_NET_SIO_MCLK, sio); /* Finish ACK */ + if (nack) { /* No ACK, so fake it */ + for (i = 0; i < 16; i++) { + TLan_ClearBit(TLAN_NET_SIO_MCLK, sio); + TLan_SetBit(TLAN_NET_SIO_MCLK, sio); + } + tmp = 0xffff; + err = TRUE; + } else { /* ACK, so read data */ + for (tmp = 0, i = 0x8000; i; i >>= 1) { + TLan_ClearBit(TLAN_NET_SIO_MCLK, sio); + if (TLan_GetBit(TLAN_NET_SIO_MDATA, sio)) + tmp |= i; + TLan_SetBit(TLAN_NET_SIO_MCLK, sio); + } + } + + + TLan_ClearBit(TLAN_NET_SIO_MCLK, sio); /* Idle cycle */ + TLan_SetBit(TLAN_NET_SIO_MCLK, sio); + + if ( minten ) + TLan_SetBit(TLAN_NET_SIO_MINTEN, sio); + + *val = tmp; + + dev->interrupt--; + if ( dev->interrupt == 0 ) + sti(); + + return err; + +} /* TLan_MiiReadReg */ + + + + + /*************************************************************** + * TLan_MiiSendData + * + * Returns: + * Nothing + * Parms: + * base_port The base IO port of the adapter in + * question. + * dev The address of the PHY to be queried. + * data The value to be placed on the MII bus. + * num_bits The number of bits in data that are to + * be placed on the MII bus. + * + * This function sends on sequence of bits on the MII + * configuration bus. + * + **************************************************************/ + +void TLan_MiiSendData( u16 base_port, u32 data, unsigned num_bits ) +{ + u16 sio; + u32 i; + + if ( num_bits == 0 ) + return; + + outw( TLAN_NET_SIO, base_port + TLAN_DIO_ADR ); + sio = base_port + TLAN_DIO_DATA + TLAN_NET_SIO; + TLan_SetBit( TLAN_NET_SIO_MTXEN, sio ); + + for ( i = ( 0x1 << ( num_bits - 1 ) ); i; i >>= 1 ) { + TLan_ClearBit( TLAN_NET_SIO_MCLK, sio ); + TLan_GetBit( TLAN_NET_SIO_MCLK, sio ); + if ( data & i ) + TLan_SetBit( TLAN_NET_SIO_MDATA, sio ); + else + TLan_ClearBit( TLAN_NET_SIO_MDATA, sio ); + TLan_SetBit( TLAN_NET_SIO_MCLK, sio ); + TLan_GetBit( TLAN_NET_SIO_MCLK, sio ); + } + +} /* TLan_MiiSendData */ + + + + + /*************************************************************** + * TLan_MiiSync + * + * Returns: + * Nothing + * Parms: + * base_port The base IO port of the adapter in + * question. + * + * This functions syncs all PHYs in terms of the MII configuration + * bus. + * + **************************************************************/ + +void TLan_MiiSync( u16 base_port ) +{ + int i; + u16 sio; + + outw( TLAN_NET_SIO, base_port + TLAN_DIO_ADR ); + sio = base_port + TLAN_DIO_DATA + TLAN_NET_SIO; + + TLan_ClearBit( TLAN_NET_SIO_MTXEN, sio ); + for ( i = 0; i < 32; i++ ) { + TLan_ClearBit( TLAN_NET_SIO_MCLK, sio ); + TLan_SetBit( TLAN_NET_SIO_MCLK, sio ); + } + +} /* TLan_MiiSync */ + + + + + /*************************************************************** + * TLan_MiiWriteReg + * + * Returns: + * Nothing + * Parms: + * dev The device structure for the device + * to write to. + * phy The address of the PHY to be written to. + * reg The register whose contents are to be + * written. + * val The value to be written to the register. + * + * This function uses the TLAN's MII bus to write the contents of a + * given register on a PHY. It sends the appropriate info and then + * writes the 16-bit register value from the MII configuration bus + * via the TLAN SIO register. + * + **************************************************************/ + +void TLan_MiiWriteReg( struct device *dev, u16 phy, u16 reg, u16 val ) +{ + u16 sio; + int minten; + + outw(TLAN_NET_SIO, dev->base_addr + TLAN_DIO_ADR); + sio = dev->base_addr + TLAN_DIO_DATA + TLAN_NET_SIO; + + if ( dev->interrupt == 0 ) + cli(); + dev->interrupt++; + + TLan_MiiSync( dev->base_addr ); + + minten = TLan_GetBit( TLAN_NET_SIO_MINTEN, sio ); + if ( minten ) + TLan_ClearBit( TLAN_NET_SIO_MINTEN, sio ); + + TLan_MiiSendData( dev->base_addr, 0x1, 2 ); /* Start ( 01b ) */ + TLan_MiiSendData( dev->base_addr, 0x1, 2 ); /* Write ( 01b ) */ + TLan_MiiSendData( dev->base_addr, phy, 5 ); /* Device # */ + TLan_MiiSendData( dev->base_addr, reg, 5 ); /* Register # */ + + TLan_MiiSendData( dev->base_addr, 0x2, 2 ); /* Send ACK */ + TLan_MiiSendData( dev->base_addr, val, 16 ); /* Send Data */ + + TLan_ClearBit( TLAN_NET_SIO_MCLK, sio ); /* Idle cycle */ + TLan_SetBit( TLAN_NET_SIO_MCLK, sio ); + + if ( minten ) + TLan_SetBit( TLAN_NET_SIO_MINTEN, sio ); + + dev->interrupt--; + if ( dev->interrupt == 0 ) + sti(); + +} /* TLan_MiiWriteReg */ + + + + +/***************************************************************************** +****************************************************************************** + + ThunderLAN Driver Eeprom routines + + The Compaq Netelligent 10 and 10/100 cards use a Microchip 24C02A + EEPROM. These functions are based on information in Microchip's + data sheet. I don't know how well this functions will work with + other EEPROMs. + +****************************************************************************** +*****************************************************************************/ + + + /*************************************************************** + * TLan_EeSendStart + * + * Returns: + * Nothing + * Parms: + * io_base The IO port base address for the + * TLAN device with the EEPROM to + * use. + * + * This function sends a start cycle to an EEPROM attached + * to a TLAN chip. + * + **************************************************************/ + +void TLan_EeSendStart( u16 io_base ) +{ + u16 sio; + + outw( TLAN_NET_SIO, io_base + TLAN_DIO_ADR ); + sio = io_base + TLAN_DIO_DATA + TLAN_NET_SIO; + + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_SetBit( TLAN_NET_SIO_EDATA, sio ); + TLan_SetBit( TLAN_NET_SIO_ETXEN, sio ); + TLan_ClearBit( TLAN_NET_SIO_EDATA, sio ); + TLan_ClearBit( TLAN_NET_SIO_ECLOK, sio ); + +} /* TLan_EeSendStart */ + + + + + /*************************************************************** + * TLan_EeSendByte + * + * Returns: + * If the correct ack was received, 0, otherwise 1 + * Parms: io_base The IO port base address for the + * TLAN device with the EEPROM to + * use. + * data The 8 bits of information to + * send to the EEPROM. + * stop If TLAN_EEPROM_STOP is passed, a + * stop cycle is sent after the + * byte is sent after the ack is + * read. + * + * This function sends a byte on the serial EEPROM line, + * driving the clock to send each bit. The function then + * reverses transmission direction and reads an acknowledge + * bit. + * + **************************************************************/ + +int TLan_EeSendByte( u16 io_base, u8 data, int stop ) +{ + int err; + u8 place; + u16 sio; + + outw( TLAN_NET_SIO, io_base + TLAN_DIO_ADR ); + sio = io_base + TLAN_DIO_DATA + TLAN_NET_SIO; + + /* Assume clock is low, tx is enabled; */ + for ( place = 0x80; place != 0; place >>= 1 ) { + if ( place & data ) + TLan_SetBit( TLAN_NET_SIO_EDATA, sio ); + else + TLan_ClearBit( TLAN_NET_SIO_EDATA, sio ); + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_ClearBit( TLAN_NET_SIO_ECLOK, sio ); + } + TLan_ClearBit( TLAN_NET_SIO_ETXEN, sio ); + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + err = TLan_GetBit( TLAN_NET_SIO_EDATA, sio ); + TLan_ClearBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_SetBit( TLAN_NET_SIO_ETXEN, sio ); + + if ( ( ! err ) && stop ) { + TLan_ClearBit( TLAN_NET_SIO_EDATA, sio ); /* STOP, raise data while clock is high */ + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_SetBit( TLAN_NET_SIO_EDATA, sio ); + } + + return ( err ); + +} /* TLan_EeSendByte */ + + + + + /*************************************************************** + * TLan_EeReceiveByte + * + * Returns: + * Nothing + * Parms: + * io_base The IO port base address for the + * TLAN device with the EEPROM to + * use. + * data An address to a char to hold the + * data sent from the EEPROM. + * stop If TLAN_EEPROM_STOP is passed, a + * stop cycle is sent after the + * byte is received, and no ack is + * sent. + * + * This function receives 8 bits of data from the EEPROM + * over the serial link. It then sends and ack bit, or no + * ack and a stop bit. This function is used to retrieve + * data after the address of a byte in the EEPROM has been + * sent. + * + **************************************************************/ + +void TLan_EeReceiveByte( u16 io_base, u8 *data, int stop ) +{ + u8 place; + u16 sio; + + outw( TLAN_NET_SIO, io_base + TLAN_DIO_ADR ); + sio = io_base + TLAN_DIO_DATA + TLAN_NET_SIO; + *data = 0; + + /* Assume clock is low, tx is enabled; */ + TLan_ClearBit( TLAN_NET_SIO_ETXEN, sio ); + for ( place = 0x80; place; place >>= 1 ) { + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + if ( TLan_GetBit( TLAN_NET_SIO_EDATA, sio ) ) + *data |= place; + TLan_ClearBit( TLAN_NET_SIO_ECLOK, sio ); + } + + TLan_SetBit( TLAN_NET_SIO_ETXEN, sio ); + if ( ! stop ) { + TLan_ClearBit( TLAN_NET_SIO_EDATA, sio ); /* Ack = 0 */ + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_ClearBit( TLAN_NET_SIO_ECLOK, sio ); + } else { + TLan_SetBit( TLAN_NET_SIO_EDATA, sio ); /* No ack = 1 (?) */ + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_ClearBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_ClearBit( TLAN_NET_SIO_EDATA, sio ); /* STOP, raise data while clock is high */ + TLan_SetBit( TLAN_NET_SIO_ECLOK, sio ); + TLan_SetBit( TLAN_NET_SIO_EDATA, sio ); + } + +} /* TLan_EeReceiveByte */ + + + + + /*************************************************************** + * TLan_EeReadByte + * + * Returns: + * No error = 0, else, the stage at which the error + * occured. + * Parms: + * io_base The IO port base address for the + * TLAN device with the EEPROM to + * use. + * ee_addr The address of the byte in the + * EEPROM whose contents are to be + * retrieved. + * data An address to a char to hold the + * data obtained from the EEPROM. + * + * This function reads a byte of information from an byte + * cell in the EEPROM. + * + **************************************************************/ + +int TLan_EeReadByte( struct device *dev, u8 ee_addr, u8 *data ) +{ + int err; + + if ( dev->interrupt == 0 ) + cli(); + dev->interrupt++; + + TLan_EeSendStart( dev->base_addr ); + err = TLan_EeSendByte( dev->base_addr, 0xA0, TLAN_EEPROM_ACK ); + if (err) + return 1; + err = TLan_EeSendByte( dev->base_addr, ee_addr, TLAN_EEPROM_ACK ); + if (err) + return 2; + TLan_EeSendStart( dev->base_addr ); + err = TLan_EeSendByte( dev->base_addr, 0xA1, TLAN_EEPROM_ACK ); + if (err) + return 3; + TLan_EeReceiveByte( dev->base_addr, data, TLAN_EEPROM_STOP ); + + dev->interrupt--; + if ( dev->interrupt == 0 ) + sti(); + + return 0; + +} /* TLan_EeReadByte */ + + + + + diff --git a/linux/src/drivers/net/tlan.h b/linux/src/drivers/net/tlan.h new file mode 100644 index 00000000..a66e26c2 --- /dev/null +++ b/linux/src/drivers/net/tlan.h @@ -0,0 +1,525 @@ +#ifndef TLAN_H +#define TLAN_H +/******************************************************************** + * + * Linux ThunderLAN Driver + * + * tlan.h + * by James Banks + * + * (C) 1997-1998 Caldera, Inc. + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * + ** This file is best viewed/edited with tabstop=4, colums>=132 + * + ********************************************************************/ + + +#include <asm/io.h> +#include <asm/types.h> +#include <linux/netdevice.h> + +#if LINUX_VERSION_CODE <= 0x20100 +#define net_device_stats enet_statistics +#endif + + + + + /***************************************************************** + * TLan Definitions + * + ****************************************************************/ + +#define FALSE 0 +#define TRUE 1 + +#define TLAN_MIN_FRAME_SIZE 64 +#define TLAN_MAX_FRAME_SIZE 1600 + +#define TLAN_NUM_RX_LISTS 4 +#define TLAN_NUM_TX_LISTS 8 + +#define TLAN_IGNORE 0 +#define TLAN_RECORD 1 + +#define TLAN_DBG(lvl, format, args...) if (debug&lvl) printk( format, ##args ); +#define TLAN_DEBUG_GNRL 0x0001 +#define TLAN_DEBUG_TX 0x0002 +#define TLAN_DEBUG_RX 0x0004 +#define TLAN_DEBUG_LIST 0x0008 + + + + + /***************************************************************** + * Device Identification Definitions + * + ****************************************************************/ + +#define PCI_DEVICE_ID_NETELLIGENT_10 0xAE34 +#define PCI_DEVICE_ID_NETELLIGENT_10_100 0xAE32 +#define PCI_DEVICE_ID_NETFLEX_3P_INTEGRATED 0xAE35 +#define PCI_DEVICE_ID_NETFLEX_3P 0xF130 +#define PCI_DEVICE_ID_NETFLEX_3P_BNC 0xF150 +#define PCI_DEVICE_ID_NETELLIGENT_10_100_PROLIANT 0xAE43 +#define PCI_DEVICE_ID_NETELLIGENT_10_100_DUAL 0xAE40 +#define PCI_DEVICE_ID_DESKPRO_4000_5233MMX 0xB011 +#define PCI_DEVICE_ID_NETELLIGENT_10_T2 0xB012 +#define PCI_DEVICE_ID_NETELLIGENT_10_100_WS_5100 0xB030 +#ifndef PCI_DEVICE_ID_OLICOM_OC2183 +#define PCI_DEVICE_ID_OLICOM_OC2183 0x0013 +#endif +#ifndef PCI_DEVICE_ID_OLICOM_OC2325 +#define PCI_DEVICE_ID_OLICOM_OC2325 0x0012 +#endif +#ifndef PCI_DEVICE_ID_OLICOM_OC2326 +#define PCI_DEVICE_ID_OLICOM_OC2326 0x0014 +#endif + +typedef struct tlan_adapter_entry { + u16 vendorId; + u16 deviceId; + char *deviceLabel; + u32 flags; + u16 addrOfs; +} TLanAdapterEntry; + +#define TLAN_ADAPTER_NONE 0x00000000 +#define TLAN_ADAPTER_UNMANAGED_PHY 0x00000001 +#define TLAN_ADAPTER_BIT_RATE_PHY 0x00000002 +#define TLAN_ADAPTER_USE_INTERN_10 0x00000004 +#define TLAN_ADAPTER_ACTIVITY_LED 0x00000008 + +#define TLAN_SPEED_DEFAULT 0 +#define TLAN_SPEED_10 10 +#define TLAN_SPEED_100 100 + +#define TLAN_DUPLEX_DEFAULT 0 +#define TLAN_DUPLEX_HALF 1 +#define TLAN_DUPLEX_FULL 2 + + + + + /***************************************************************** + * Rx/Tx List Definitions + * + ****************************************************************/ + +#define TLAN_BUFFERS_PER_LIST 10 +#define TLAN_LAST_BUFFER 0x80000000 +#define TLAN_CSTAT_UNUSED 0x8000 +#define TLAN_CSTAT_FRM_CMP 0x4000 +#define TLAN_CSTAT_READY 0x3000 +#define TLAN_CSTAT_EOC 0x0800 +#define TLAN_CSTAT_RX_ERROR 0x0400 +#define TLAN_CSTAT_PASS_CRC 0x0200 +#define TLAN_CSTAT_DP_PR 0x0100 + + +typedef struct tlan_buffer_ref_tag { + u32 count; + u32 address; +} TLanBufferRef; + + +typedef struct tlan_list_tag { + u32 forward; + u16 cStat; + u16 frameSize; + TLanBufferRef buffer[TLAN_BUFFERS_PER_LIST]; +} TLanList; + + +typedef u8 TLanBuffer[TLAN_MAX_FRAME_SIZE]; + + + + + /***************************************************************** + * PHY definitions + * + ****************************************************************/ + +#define TLAN_PHY_MAX_ADDR 0x1F +#define TLAN_PHY_NONE 0x20 + + + + + /***************************************************************** + * TLAN Private Information Structure + * + ****************************************************************/ + +typedef struct tlan_private_tag { + struct device *nextDevice; + void *dmaStorage; + u8 *padBuffer; + TLanList *rxList; + u8 *rxBuffer; + u32 rxHead; + u32 rxTail; + u32 rxEocCount; + TLanList *txList; + u8 *txBuffer; + u32 txHead; + u32 txInProgress; + u32 txTail; + u32 txBusyCount; + u32 phyOnline; + u32 timerSetAt; + u32 timerType; + struct timer_list timer; + struct net_device_stats stats; + TLanAdapterEntry *adapter; + u32 adapterRev; + u32 aui; + u32 debug; + u32 duplex; + u32 phy[2]; + u32 phyNum; + u32 sa_int; + u32 speed; + u8 tlanRev; + u8 tlanFullDuplex; + char devName[8]; +} TLanPrivateInfo; + + + + + /***************************************************************** + * TLan Driver Timer Definitions + * + ****************************************************************/ + +#define TLAN_TIMER_LINK 1 +#define TLAN_TIMER_ACTIVITY 2 +#define TLAN_TIMER_PHY_PDOWN 3 +#define TLAN_TIMER_PHY_PUP 4 +#define TLAN_TIMER_PHY_RESET 5 +#define TLAN_TIMER_PHY_START_LINK 6 +#define TLAN_TIMER_PHY_FINISH_AN 7 +#define TLAN_TIMER_FINISH_RESET 8 + +#define TLAN_TIMER_ACT_DELAY 10 + + + + + /***************************************************************** + * TLan Driver Eeprom Definitions + * + ****************************************************************/ + +#define TLAN_EEPROM_ACK 0 +#define TLAN_EEPROM_STOP 1 + + + + + /***************************************************************** + * Host Register Offsets and Contents + * + ****************************************************************/ + +#define TLAN_HOST_CMD 0x00 +#define TLAN_HC_GO 0x80000000 +#define TLAN_HC_STOP 0x40000000 +#define TLAN_HC_ACK 0x20000000 +#define TLAN_HC_CS_MASK 0x1FE00000 +#define TLAN_HC_EOC 0x00100000 +#define TLAN_HC_RT 0x00080000 +#define TLAN_HC_NES 0x00040000 +#define TLAN_HC_AD_RST 0x00008000 +#define TLAN_HC_LD_TMR 0x00004000 +#define TLAN_HC_LD_THR 0x00002000 +#define TLAN_HC_REQ_INT 0x00001000 +#define TLAN_HC_INT_OFF 0x00000800 +#define TLAN_HC_INT_ON 0x00000400 +#define TLAN_HC_AC_MASK 0x000000FF +#define TLAN_CH_PARM 0x04 +#define TLAN_DIO_ADR 0x08 +#define TLAN_DA_ADR_INC 0x8000 +#define TLAN_DA_RAM_ADR 0x4000 +#define TLAN_HOST_INT 0x0A +#define TLAN_HI_IV_MASK 0x1FE0 +#define TLAN_HI_IT_MASK 0x001C +#define TLAN_DIO_DATA 0x0C + + +/* ThunderLAN Internal Register DIO Offsets */ + +#define TLAN_NET_CMD 0x00 +#define TLAN_NET_CMD_NRESET 0x80 +#define TLAN_NET_CMD_NWRAP 0x40 +#define TLAN_NET_CMD_CSF 0x20 +#define TLAN_NET_CMD_CAF 0x10 +#define TLAN_NET_CMD_NOBRX 0x08 +#define TLAN_NET_CMD_DUPLEX 0x04 +#define TLAN_NET_CMD_TRFRAM 0x02 +#define TLAN_NET_CMD_TXPACE 0x01 +#define TLAN_NET_SIO 0x01 +#define TLAN_NET_SIO_MINTEN 0x80 +#define TLAN_NET_SIO_ECLOK 0x40 +#define TLAN_NET_SIO_ETXEN 0x20 +#define TLAN_NET_SIO_EDATA 0x10 +#define TLAN_NET_SIO_NMRST 0x08 +#define TLAN_NET_SIO_MCLK 0x04 +#define TLAN_NET_SIO_MTXEN 0x02 +#define TLAN_NET_SIO_MDATA 0x01 +#define TLAN_NET_STS 0x02 +#define TLAN_NET_STS_MIRQ 0x80 +#define TLAN_NET_STS_HBEAT 0x40 +#define TLAN_NET_STS_TXSTOP 0x20 +#define TLAN_NET_STS_RXSTOP 0x10 +#define TLAN_NET_STS_RSRVD 0x0F +#define TLAN_NET_MASK 0x03 +#define TLAN_NET_MASK_MASK7 0x80 +#define TLAN_NET_MASK_MASK6 0x40 +#define TLAN_NET_MASK_MASK5 0x20 +#define TLAN_NET_MASK_MASK4 0x10 +#define TLAN_NET_MASK_RSRVD 0x0F +#define TLAN_NET_CONFIG 0x04 +#define TLAN_NET_CFG_RCLK 0x8000 +#define TLAN_NET_CFG_TCLK 0x4000 +#define TLAN_NET_CFG_BIT 0x2000 +#define TLAN_NET_CFG_RXCRC 0x1000 +#define TLAN_NET_CFG_PEF 0x0800 +#define TLAN_NET_CFG_1FRAG 0x0400 +#define TLAN_NET_CFG_1CHAN 0x0200 +#define TLAN_NET_CFG_MTEST 0x0100 +#define TLAN_NET_CFG_PHY_EN 0x0080 +#define TLAN_NET_CFG_MSMASK 0x007F +#define TLAN_MAN_TEST 0x06 +#define TLAN_DEF_VENDOR_ID 0x08 +#define TLAN_DEF_DEVICE_ID 0x0A +#define TLAN_DEF_REVISION 0x0C +#define TLAN_DEF_SUBCLASS 0x0D +#define TLAN_DEF_MIN_LAT 0x0E +#define TLAN_DEF_MAX_LAT 0x0F +#define TLAN_AREG_0 0x10 +#define TLAN_AREG_1 0x16 +#define TLAN_AREG_2 0x1C +#define TLAN_AREG_3 0x22 +#define TLAN_HASH_1 0x28 +#define TLAN_HASH_2 0x2C +#define TLAN_GOOD_TX_FRMS 0x30 +#define TLAN_TX_UNDERUNS 0x33 +#define TLAN_GOOD_RX_FRMS 0x34 +#define TLAN_RX_OVERRUNS 0x37 +#define TLAN_DEFERRED_TX 0x38 +#define TLAN_CRC_ERRORS 0x3A +#define TLAN_CODE_ERRORS 0x3B +#define TLAN_MULTICOL_FRMS 0x3C +#define TLAN_SINGLECOL_FRMS 0x3E +#define TLAN_EXCESSCOL_FRMS 0x40 +#define TLAN_LATE_COLS 0x41 +#define TLAN_CARRIER_LOSS 0x42 +#define TLAN_ACOMMIT 0x43 +#define TLAN_LED_REG 0x44 +#define TLAN_LED_ACT 0x10 +#define TLAN_LED_LINK 0x01 +#define TLAN_BSIZE_REG 0x45 +#define TLAN_MAX_RX 0x46 +#define TLAN_INT_DIS 0x48 +#define TLAN_ID_TX_EOC 0x04 +#define TLAN_ID_RX_EOF 0x02 +#define TLAN_ID_RX_EOC 0x01 + + + +/* ThunderLAN Interrupt Codes */ + +#define TLAN_INT_NUMBER_OF_INTS 8 + +#define TLAN_INT_NONE 0x0000 +#define TLAN_INT_TX_EOF 0x0001 +#define TLAN_INT_STAT_OVERFLOW 0x0002 +#define TLAN_INT_RX_EOF 0x0003 +#define TLAN_INT_DUMMY 0x0004 +#define TLAN_INT_TX_EOC 0x0005 +#define TLAN_INT_STATUS_CHECK 0x0006 +#define TLAN_INT_RX_EOC 0x0007 + + + +/* ThunderLAN MII Registers */ + +/* Generic MII/PHY Registers */ + +#define MII_GEN_CTL 0x00 +#define MII_GC_RESET 0x8000 +#define MII_GC_LOOPBK 0x4000 +#define MII_GC_SPEEDSEL 0x2000 +#define MII_GC_AUTOENB 0x1000 +#define MII_GC_PDOWN 0x0800 +#define MII_GC_ISOLATE 0x0400 +#define MII_GC_AUTORSRT 0x0200 +#define MII_GC_DUPLEX 0x0100 +#define MII_GC_COLTEST 0x0080 +#define MII_GC_RESERVED 0x007F +#define MII_GEN_STS 0x01 +#define MII_GS_100BT4 0x8000 +#define MII_GS_100BTXFD 0x4000 +#define MII_GS_100BTXHD 0x2000 +#define MII_GS_10BTFD 0x1000 +#define MII_GS_10BTHD 0x0800 +#define MII_GS_RESERVED 0x07C0 +#define MII_GS_AUTOCMPLT 0x0020 +#define MII_GS_RFLT 0x0010 +#define MII_GS_AUTONEG 0x0008 +#define MII_GS_LINK 0x0004 +#define MII_GS_JABBER 0x0002 +#define MII_GS_EXTCAP 0x0001 +#define MII_GEN_ID_HI 0x02 +#define MII_GEN_ID_LO 0x03 +#define MII_GIL_OUI 0xFC00 +#define MII_GIL_MODEL 0x03F0 +#define MII_GIL_REVISION 0x000F +#define MII_AN_ADV 0x04 +#define MII_AN_LPA 0x05 +#define MII_AN_EXP 0x06 + +/* ThunderLAN Specific MII/PHY Registers */ + +#define TLAN_TLPHY_ID 0x10 +#define TLAN_TLPHY_CTL 0x11 +#define TLAN_TC_IGLINK 0x8000 +#define TLAN_TC_SWAPOL 0x4000 +#define TLAN_TC_AUISEL 0x2000 +#define TLAN_TC_SQEEN 0x1000 +#define TLAN_TC_MTEST 0x0800 +#define TLAN_TC_RESERVED 0x07F8 +#define TLAN_TC_NFEW 0x0004 +#define TLAN_TC_INTEN 0x0002 +#define TLAN_TC_TINT 0x0001 +#define TLAN_TLPHY_STS 0x12 +#define TLAN_TS_MINT 0x8000 +#define TLAN_TS_PHOK 0x4000 +#define TLAN_TS_POLOK 0x2000 +#define TLAN_TS_TPENERGY 0x1000 +#define TLAN_TS_RESERVED 0x0FFF + + +#define CIRC_INC( a, b ) if ( ++a >= b ) a = 0 + +/* Routines to access internal registers. */ + +inline u8 TLan_DioRead8(u16 base_addr, u16 internal_addr) +{ + outw(internal_addr, base_addr + TLAN_DIO_ADR); + return (inb((base_addr + TLAN_DIO_DATA) + (internal_addr & 0x3))); + +} /* TLan_DioRead8 */ + + + + +inline u16 TLan_DioRead16(u16 base_addr, u16 internal_addr) +{ + outw(internal_addr, base_addr + TLAN_DIO_ADR); + return (inw((base_addr + TLAN_DIO_DATA) + (internal_addr & 0x2))); + +} /* TLan_DioRead16 */ + + + + +inline u32 TLan_DioRead32(u16 base_addr, u16 internal_addr) +{ + outw(internal_addr, base_addr + TLAN_DIO_ADR); + return (inl(base_addr + TLAN_DIO_DATA)); + +} /* TLan_DioRead32 */ + + + + +inline void TLan_DioWrite8(u16 base_addr, u16 internal_addr, u8 data) +{ + outw(internal_addr, base_addr + TLAN_DIO_ADR); + outb(data, base_addr + TLAN_DIO_DATA + (internal_addr & 0x3)); + +} + + + + +inline void TLan_DioWrite16(u16 base_addr, u16 internal_addr, u16 data) +{ + outw(internal_addr, base_addr + TLAN_DIO_ADR); + outw(data, base_addr + TLAN_DIO_DATA + (internal_addr & 0x2)); + +} + + + + +inline void TLan_DioWrite32(u16 base_addr, u16 internal_addr, u32 data) +{ + outw(internal_addr, base_addr + TLAN_DIO_ADR); + outl(data, base_addr + TLAN_DIO_DATA + (internal_addr & 0x2)); + +} + + + +#if 0 +inline void TLan_ClearBit(u8 bit, u16 port) +{ + outb_p(inb_p(port) & ~bit, port); +} + + + + +inline int TLan_GetBit(u8 bit, u16 port) +{ + return ((int) (inb_p(port) & bit)); +} + + + + +inline void TLan_SetBit(u8 bit, u16 port) +{ + outb_p(inb_p(port) | bit, port); +} +#endif + +#define TLan_ClearBit( bit, port ) outb_p(inb_p(port) & ~bit, port) +#define TLan_GetBit( bit, port ) ((int) (inb_p(port) & bit)) +#define TLan_SetBit( bit, port ) outb_p(inb_p(port) | bit, port) + + +inline u32 xor( u32 a, u32 b ) +{ + return ( ( a && ! b ) || ( ! a && b ) ); +} +#define XOR8( a, b, c, d, e, f, g, h ) xor( a, xor( b, xor( c, xor( d, xor( e, xor( f, xor( g, h ) ) ) ) ) ) ) +#define DA( a, bit ) ( ( (u8) a[bit/8] ) & ( (u8) ( 1 << bit%8 ) ) ) + +inline u32 TLan_HashFunc( u8 *a ) +{ + u32 hash; + + hash = XOR8( DA(a,0), DA(a, 6), DA(a,12), DA(a,18), DA(a,24), DA(a,30), DA(a,36), DA(a,42) ); + hash |= XOR8( DA(a,1), DA(a, 7), DA(a,13), DA(a,19), DA(a,25), DA(a,31), DA(a,37), DA(a,43) ) << 1; + hash |= XOR8( DA(a,2), DA(a, 8), DA(a,14), DA(a,20), DA(a,26), DA(a,32), DA(a,38), DA(a,44) ) << 2; + hash |= XOR8( DA(a,3), DA(a, 9), DA(a,15), DA(a,21), DA(a,27), DA(a,33), DA(a,39), DA(a,45) ) << 3; + hash |= XOR8( DA(a,4), DA(a,10), DA(a,16), DA(a,22), DA(a,28), DA(a,34), DA(a,40), DA(a,46) ) << 4; + hash |= XOR8( DA(a,5), DA(a,11), DA(a,17), DA(a,23), DA(a,29), DA(a,35), DA(a,41), DA(a,47) ) << 5; + + return hash; + +} + + + + +#endif diff --git a/linux/src/drivers/net/tulip.c b/linux/src/drivers/net/tulip.c new file mode 100644 index 00000000..4aba1af5 --- /dev/null +++ b/linux/src/drivers/net/tulip.c @@ -0,0 +1,2874 @@ +/* tulip.c: A DEC 21040-family ethernet driver for Linux. */ +/* + Written 1994-1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + This driver is for the Digital "Tulip" ethernet adapter interface. + It should work with most DEC 21*4*-based chips/ethercards, as well as + PNIC and MXIC chips. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Support and updates available at + http://cesdis.gsfc.nasa.gov/linux/drivers/tulip.html +*/ + +#define SMP_CHECK +static const char version[] = "tulip.c:v0.89H 5/23/98 becker@cesdis.gsfc.nasa.gov\n"; + +/* A few user-configurable values. */ + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 25; + +#define MAX_UNITS 8 +/* Used to pass the full-duplex flag, etc. */ +static int full_duplex[MAX_UNITS] = {0, }; +static int options[MAX_UNITS] = {0, }; +static int mtu[MAX_UNITS] = {0, }; /* Jumbo MTU for interfaces. */ + +/* The possible media types that can be set in options[] are: */ +static const char * const medianame[] = { + "10baseT", "10base2", "AUI", "100baseTx", + "10baseT-FD", "100baseTx-FD", "100baseT4", "100baseFx", + "100baseFx-FD", "MII 10baseT", "MII 10baseT-FD", "MII", + "10baseT(forced)", "MII 100baseTx", "MII 100baseTx-FD", "MII 100baseT4", +}; + +/* Set if the PCI BIOS detects the chips on a multiport board backwards. */ +#ifdef REVERSE_PROBE_ORDER +static int reverse_probe = 1; +#else +static int reverse_probe = 0; +#endif + +/* Keep the ring sizes a power of two for efficiency. + Making the Tx ring too large decreases the effectiveness of channel + bonding and packet priority. + There are no ill effects from too-large receive rings. */ +#define TX_RING_SIZE 16 +#define RX_RING_SIZE 32 + +/* Set the copy breakpoint for the copy-only-tiny-buffer Rx structure. */ +#ifdef __alpha__ +static int rx_copybreak = 1518; +#else +static int rx_copybreak = 100; +#endif + +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT (4*HZ) + +#include <linux/config.h> +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* Kernel compatibility defines, common to David Hind's PCMCIA package. + This is only in the support-all-kernels source code. */ +#include <linux/version.h> /* Evil, but neccessary */ + +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10300 +#define RUN_AT(x) (x) /* What to put in timer->expires. */ +#define DEV_ALLOC_SKB(len) alloc_skb(len, GFP_ATOMIC) +#define virt_to_bus(addr) ((unsigned long)addr) +#define bus_to_virt(addr) ((void*)addr) + +#else /* 1.3.0 and later */ +#define RUN_AT(x) (jiffies + (x)) +#define DEV_ALLOC_SKB(len) dev_alloc_skb(len + 2) +#endif + +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10338 +#ifdef MODULE +#if !defined(CONFIG_MODVERSIONS) && !defined(__NO_VERSION__) +char kernel_version[] = UTS_RELEASE; +#endif +#else +#undef MOD_INC_USE_COUNT +#define MOD_INC_USE_COUNT +#undef MOD_DEC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif +#endif /* 1.3.38 */ + +#if (LINUX_VERSION_CODE >= 0x10344) +#define NEW_MULTICAST +#include <linux/delay.h> +#endif +#if (LINUX_VERSION_CODE >= 0x20100) +char kernel_version[] = UTS_RELEASE; +#endif +#ifdef SA_SHIRQ +#define IRQ(irq, dev_id, pt_regs) (irq, dev_id, pt_regs) +#else +#define IRQ(irq, dev_id, pt_regs) (irq, pt_regs) +#endif + +#if (LINUX_VERSION_CODE < 0x20123) +#define hard_smp_processor_id() smp_processor_id() +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif + +/* This my implementation of shared IRQs, now only used for 1.2.13. */ +#ifdef HAVE_SHARED_IRQ +#define USE_SHARED_IRQ +#include <linux/shared_irq.h> +#endif + +/* The total size is unusually large: The 21040 aligns each of its 16 + longword-wide registers on a quadword boundary. */ +#define TULIP_TOTAL_SIZE 0x80 + +#ifdef HAVE_DEVLIST +struct netdev_entry tulip_drv = +{"Tulip", tulip_pci_probe, TULIP_TOTAL_SIZE, NULL}; +#endif + +#ifdef TULIP_DEBUG +int tulip_debug = TULIP_DEBUG; +#else +int tulip_debug = 1; +#endif + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the DECchip "Tulip", Digital's +single-chip ethernet controllers for PCI. Supported members of the family +are the 21040, 21041, 21140, 21140A, 21142, and 21143. These chips are used on +many PCI boards including the SMC EtherPower series. + + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board. The system BIOS preferably should assign the +PCI INTA signal to an otherwise unused system IRQ line. +Note: Kernel versions earlier than 1.3.73 do not support shared PCI +interrupt lines. + +III. Driver operation + +IIIa. Ring buffers + +The Tulip can use either ring buffers or lists of Tx and Rx descriptors. +This driver uses statically allocated rings of Rx and Tx descriptors, set at +compile time by RX/TX_RING_SIZE. This version of the driver allocates skbuffs +for the Rx ring buffers at open() time and passes the skb->data field to the +Tulip as receive data buffers. When an incoming frame is less than +RX_COPYBREAK bytes long, a fresh skbuff is allocated and the frame is +copied to the new skbuff. When the incoming frame is larger, the skbuff is +passed directly up the protocol stack and replaced by a newly allocated +skbuff. + +The RX_COPYBREAK value is chosen to trade-off the memory wasted by +using a full-sized skbuff for small frames vs. the copying costs of larger +frames. For small frames the copying cost is negligible (esp. considering +that we are pre-loading the cache with immediately useful header +information). For large frames the copying cost is non-trivial, and the +larger copy might flush the cache of useful data. A subtle aspect of this +choice is that the Tulip only receives into longword aligned buffers, thus +the IP header at offset 14 isn't longword aligned for further processing. +Copied frames are put into the new skbuff at an offset of "+2", thus copying +has the beneficial effect of aligning the IP header and preloading the +cache. + +IIIC. Synchronization +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +The send packet thread has partial control over the Tx ring and 'dev->tbusy' +flag. It sets the tbusy flag whenever it's queuing a Tx packet. If the next +queue slot is empty, it clears the tbusy flag when finished otherwise it sets +the 'tp->tx_full' flag. + +The interrupt handler has exclusive control over the Rx ring and records stats +from the Tx ring. (The Tx-done interrupt can't be selectively turned off, so +we can't avoid the interrupt overhead by having the Tx routine reap the Tx +stats.) After reaping the stats, it marks the queue entry as empty by setting +the 'base' to zero. Iff the 'tp->tx_full' flag is set, it clears both the +tx_full and tbusy flags. + +IV. Notes + +Thanks to Duke Kamstra of SMC for providing an EtherPower board. + +IVb. References + +http://cesdis.gsfc.nasa.gov/linux/misc/NWay.html +http://www.digital.com (search for current 21*4* datasheets and "21X4 SROM") +http://www.national.com/pf/DP/DP83840.html + +IVc. Errata + +The DEC databook doesn't document which Rx filter settings accept broadcast +packets. Nor does it document how to configure the part to configure the +serial subsystem for normal (vs. loopback) operation or how to have it +autoswitch between internal 10baseT, SIA and AUI transceivers. + +The 21040 databook claims that CSR13, CSR14, and CSR15 should each be the last +register of the set CSR12-15 written. Hmmm, now how is that possible? */ + + +/* A few values that may be tweaked. */ +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +/* This is a mysterious value that can be written to CSR11 in the 21040 (only) + to support a pre-NWay full-duplex signaling mechanism using short frames. + No one knows what it should be, but if left at its default value some + 10base2(!) packets trigger a full-duplex-request interrupt. */ +#define FULL_DUPLEX_MAGIC 0x6969 + +#ifndef PCI_VENDOR_ID_DEC /* Now defined in linux/pci.h */ +#define PCI_VENDOR_ID_DEC 0x1011 +#define PCI_DEVICE_ID_TULIP 0x0002 /* 21040. */ +#define PCI_DEVICE_ID_TULIP_FAST 0x0009 /* 21140. */ +#endif + +#ifndef PCI_DEVICE_ID_DEC_TULIP_PLUS +#define PCI_DEVICE_ID_DEC_TULIP_PLUS 0x0014 /* 21041. */ +#endif +#ifndef PCI_DEVICE_ID_DEC_TULIP_21142 +#define PCI_DEVICE_ID_DEC_TULIP_21142 0x0019 +#endif + +#ifndef PCI_VENDOR_ID_LITEON +#define PCI_VENDOR_ID_LITEON 0x11AD +#endif + +#ifndef PCI_VENDOR_ID_MXIC +#define PCI_VENDOR_ID_MXIC 0x10d9 +#define PCI_DEVICE_ID_MX98713 0x0512 +#define PCI_DEVICE_ID_MX98715 0x0531 +#define PCI_DEVICE_ID_MX98725 0x0531 +#endif + +/* The rest of these values should never change. */ + +static void tulip_timer(unsigned long data); +static void t21142_timer(unsigned long data); +static void mxic_timer(unsigned long data); +static void pnic_timer(unsigned long data); + +/* A table describing the chip types. */ +enum tbl_flag { HAS_MII=1, HAS_MEDIA_TABLE = 2, CSR12_IN_SROM = 4,}; +static struct tulip_chip_table { + int vendor_id, device_id; + char *chip_name; + int io_size; + int valid_intrs; /* CSR7 interrupt enable settings */ + int flags; + void (*media_timer)(unsigned long data); +} tulip_tbl[] = { + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP, + "Digital DC21040 Tulip", 128, 0x0001ebef, 0, tulip_timer }, + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_PLUS, + "Digital DC21041 Tulip", 128, 0x0001ebef, HAS_MEDIA_TABLE, tulip_timer }, + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_FAST, + "Digital DS21140 Tulip", 128, 0x0001ebef, + HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM, + tulip_timer }, + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_21142, + "Digital DS21142/3 Tulip", 256, 0x0801fbff, + HAS_MII | HAS_MEDIA_TABLE, t21142_timer }, + { PCI_VENDOR_ID_LITEON, 0x0002, + "Lite-On 82c168 PNIC", 256, 0x0001ebef, HAS_MII, pnic_timer }, + { PCI_VENDOR_ID_MXIC, PCI_DEVICE_ID_MX98713, + "Macronix 98713 PMAC", 128, 0x0001ebef, + HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM, tulip_timer /* Tulip-like! */ }, + { PCI_VENDOR_ID_MXIC, PCI_DEVICE_ID_MX98715, + "Macronix 98715 PMAC", 256, 0x0001ebef, HAS_MEDIA_TABLE, mxic_timer }, + { PCI_VENDOR_ID_MXIC, PCI_DEVICE_ID_MX98725, + "Macronix 98725 PMAC", 256, 0x0001ebef, HAS_MEDIA_TABLE, mxic_timer }, + { 0x125B, 0x1400, "ASIX AX88140", 128, 0x0001fbff, + HAS_MII | HAS_MEDIA_TABLE | CSR12_IN_SROM, tulip_timer }, + {0, 0, 0, 0}, +}; +/* This matches the table above. */ +enum chips { DC21040=0, DC21041=1, DC21140=2, DC21142=3, DC21143=3, + LC82C168, MX98713, MX98715, MX98725}; + +/* A full-duplex map for media types. */ +enum MediaIs {MediaIsFD = 1, MediaAlwaysFD=2, MediaIsMII=4, MediaIsFx=8, + MediaIs100=16}; +static const char media_cap[] = +{0,0,0,16, 3,19,16,24, 27,4,7,5, 0,20,23,20 }; +/* 21041 transceiver register settings: 10-T, 10-2, AUI, 10-T, 10T-FD*/ +static u16 t21041_csr13[] = { 0xEF05, 0xEF09, 0xEF09, 0xEF01, 0xEF09, }; +static u16 t21041_csr14[] = { 0x7F3F, 0xF7FD, 0xF7FD, 0x7F3F, 0x7F3D, }; +static u16 t21041_csr15[] = { 0x0008, 0x0006, 0x000E, 0x0008, 0x0008, }; + +static u16 t21142_csr13[] = { 0x0001, 0x0009, 0x0009, 0x0000, 0x0001, }; +static u16 t21142_csr14[] = { 0xFFFF, 0x0705, 0x0705, 0x0000, 0x7F3D, }; +static u16 t21142_csr15[] = { 0x0008, 0x0006, 0x000E, 0x0008, 0x0008, }; + +/* Offsets to the Command and Status Registers, "CSRs". All accesses + must be longword instructions and quadword aligned. */ +enum tulip_offsets { + CSR0=0, CSR1=0x08, CSR2=0x10, CSR3=0x18, CSR4=0x20, CSR5=0x28, + CSR6=0x30, CSR7=0x38, CSR8=0x40, CSR9=0x48, CSR10=0x50, CSR11=0x58, + CSR12=0x60, CSR13=0x68, CSR14=0x70, CSR15=0x78 }; + +/* The bits in the CSR5 status registers, mostly interrupt sources. */ +enum status_bits { + TimerInt=0x800, TPLnkFail=0x1000, TPLnkPass=0x10, + NormalIntr=0x10000, AbnormalIntr=0x8000, + RxJabber=0x200, RxDied=0x100, RxNoBuf=0x80, RxIntr=0x40, + TxFIFOUnderflow=0x20, TxJabber=0x08, TxNoBuf=0x04, TxDied=0x02, TxIntr=0x01, +}; + +/* The Tulip Rx and Tx buffer descriptors. */ +struct tulip_rx_desc { + s32 status; + s32 length; + u32 buffer1, buffer2; +}; + +struct tulip_tx_desc { + s32 status; + s32 length; + u32 buffer1, buffer2; /* We use only buffer 1. */ +}; + +struct medialeaf { + u8 type; + u8 media; + unsigned char *leafdata; +}; + +struct mediatable { + u16 defaultmedia; + u8 leafcount, csr12dir; /* General purpose pin directions. */ + unsigned has_mii:1, has_nonmii:1; + struct medialeaf mleaf[0]; +}; + +struct mediainfo { + struct mediainfo *next; + int info_type; + int index; + unsigned char *info; +}; + +struct tulip_private { + char devname[8]; /* Used only for kernel debugging. */ + const char *product_name; + struct device *next_module; + struct tulip_rx_desc rx_ring[RX_RING_SIZE]; + struct tulip_tx_desc tx_ring[TX_RING_SIZE]; + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + /* The addresses of receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + char *rx_buffs; /* Address of temporary Rx buffers. */ + u32 setup_frame[48]; /* Pseudo-Tx frame to init address table. */ + int chip_id; + int revision; +#if LINUX_VERSION_CODE > 0x20139 + struct net_device_stats stats; +#else + struct enet_statistics stats; +#endif + struct timer_list timer; /* Media selection timer. */ + int interrupt; /* In-interrupt flag. */ +#ifdef SMP_CHECK + int smp_proc_id; /* Which processor in IRQ handler. */ +#endif + unsigned int cur_rx, cur_tx; /* The next free ring entry */ + unsigned int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + unsigned int tx_full:1; /* The Tx queue is full. */ + unsigned int full_duplex:1; /* Full-duplex operation requested. */ + unsigned int full_duplex_lock:1; + unsigned int fake_addr:1; /* Multiport board faked address. */ + unsigned int default_port:4; /* Last dev->if_port value. */ + unsigned int media2:4; /* Secondary monitored media port. */ + unsigned int medialock:1; /* Don't sense media type. */ + unsigned int mediasense:1; /* Media sensing in progress. */ + unsigned int csr6; /* Current CSR6 control settings. */ + unsigned char eeprom[128]; /* Serial EEPROM contents. */ + u16 to_advertise; /* NWay capabilities advertised. */ + u16 advertising[4]; + signed char phys[4], mii_cnt; /* MII device addresses. */ + struct mediatable *mtable; + int cur_index; /* Current media index. */ + unsigned char pci_bus, pci_dev_fn; + int pad0, pad1; /* Used for 8-byte alignment */ +}; + +static struct device *tulip_probe1(int pci_bus, int pci_devfn, + struct device *dev, + int chip_id, int options); +static void parse_eeprom(struct device *dev); +static int read_eeprom(long ioaddr, int location); +static int mdio_read(struct device *dev, int phy_id, int location); +static void mdio_write(struct device *dev, int phy_id, int location, int value); +static void select_media(struct device *dev, int startup); +static int tulip_open(struct device *dev); +static void tulip_timer(unsigned long data); +static void tulip_tx_timeout(struct device *dev); +static void tulip_init_ring(struct device *dev); +static int tulip_start_xmit(struct sk_buff *skb, struct device *dev); +static int tulip_rx(struct device *dev); +static void tulip_interrupt IRQ(int irq, void *dev_instance, struct pt_regs *regs); +static int tulip_close(struct device *dev); +static struct enet_statistics *tulip_get_stats(struct device *dev); +#ifdef HAVE_PRIVATE_IOCTL +static int private_ioctl(struct device *dev, struct ifreq *rq, int cmd); +#endif +#ifdef NEW_MULTICAST +static void set_rx_mode(struct device *dev); +#else +static void set_rx_mode(struct device *dev, int num_addrs, void *addrs); +#endif + + + +/* A list of all installed Tulip devices, for removing the driver module. */ +static struct device *root_tulip_dev = NULL; + +/* This 21040 probe no longer uses a large fixed contiguous Rx buffer region, + but now receives directly into full-sized skbuffs that are allocated + at open() time. + This allows the probe routine to use the old driver initialization + interface. */ + +int tulip_probe(struct device *dev) +{ + int cards_found = 0; + static int pci_index = 0; /* Static, for multiple probe calls. */ + unsigned char pci_bus, pci_device_fn; + + /* Ideally we would detect all network cards in slot order. That would + be best done a central PCI probe dispatch, which wouldn't work + well with the current structure. So instead we detect just the + Tulip cards in slot order. */ + +#if LINUX_VERSION_CODE >= 0x20155 + if (! pci_present()) + return -ENODEV; +#else + if (! pcibios_present()) + return -ENODEV; +#endif + for (;pci_index < 0xff; pci_index++) { + u16 vendor, device, pci_command, new_command; + u32 pci_ioaddr; + int chip_idx = 0; + + if (pcibios_find_class + (PCI_CLASS_NETWORK_ETHERNET << 8, + reverse_probe ? 0xfe - pci_index : pci_index, + &pci_bus, &pci_device_fn) != PCIBIOS_SUCCESSFUL) + if (reverse_probe) + continue; + else + break; + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + + for (chip_idx = 0; tulip_tbl[chip_idx].chip_name; chip_idx++) + if (vendor == tulip_tbl[chip_idx].vendor_id && + device == tulip_tbl[chip_idx].device_id) + break; + if (tulip_tbl[chip_idx].chip_name == 0) { + if (vendor == PCI_VENDOR_ID_DEC || + vendor == PCI_VENDOR_ID_LITEON) + printk(KERN_INFO "Unknown Tulip-style PCI ethernet chip type" + " %4.4x %4.4x"" detected: not configured.\n", + vendor, device); + continue; + } +#if LINUX_VERSION_CODE >= 0x20155 + pci_ioaddr = pci_find_slot(pci_bus, pci_device_fn)->base_address[0]; +#else + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_0, + &pci_ioaddr); +#endif + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + + if (tulip_debug > 2) + printk(KERN_DEBUG "Found %s at I/O %#x.\n", + tulip_tbl[chip_idx].chip_name, pci_ioaddr); + + if (check_region(pci_ioaddr, tulip_tbl[chip_idx].io_size)) + continue; + + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO; + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled this" + " device! Updating PCI command %4.4x->%4.4x.\n", + pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + + dev = tulip_probe1(pci_bus, pci_device_fn, dev, chip_idx, cards_found); + + /* Get and check the bus-master and latency values. */ + if (dev) { + unsigned char pci_latency; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < 10) { + printk(KERN_INFO " PCI latency timer (CFLT) is " + "unreasonably low at %d. Setting to 64 clocks.\n", + pci_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, 64); + } else if (tulip_debug > 1) + printk(KERN_INFO " PCI latency timer (CFLT) is %#x, " + " PCI command is %4.4x.\n", + pci_latency, new_command); + /* Bring the 21143 out power-down mode. */ + if (device == PCI_DEVICE_ID_DEC_TULIP_21142) + pcibios_write_config_dword(pci_bus, pci_device_fn, + 0x40, 0x40000000); + dev = 0; + cards_found++; + } + } + + return cards_found ? 0 : -ENODEV; +} + +static struct device *tulip_probe1(int pci_bus, int pci_device_fn, + struct device *dev, + int chip_id, int board_idx) +{ + static int did_version = 0; /* Already printed version info. */ + struct tulip_private *tp; + long ioaddr; + int irq; + /* See note below on the multiport cards. */ + static unsigned char last_phys_addr[6] = {0x00, 'L', 'i', 'n', 'u', 'x'}; + static int last_irq = 0; + static int multiport_cnt = 0; /* For four-port boards w/one EEPROM */ + int i; + unsigned short sum; + + if (tulip_debug > 0 && did_version++ == 0) + printk(KERN_INFO "%s", version); + + dev = init_etherdev(dev, 0); + +#if LINUX_VERSION_CODE >= 0x20155 + irq = pci_find_slot(pci_bus, pci_device_fn)->irq; + ioaddr = pci_find_slot(pci_bus, pci_device_fn)->base_address[0]; +#else + { + u8 pci_irq_line; + u32 pci_ioaddr; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_0, + &pci_ioaddr); + irq = pci_irq_line; + ioaddr = pci_ioaddr; + } +#endif + /* Remove I/O space marker in bit 0. */ + ioaddr &= ~3; + + printk(KERN_INFO "%s: %s at %#3lx,", + dev->name, tulip_tbl[chip_id].chip_name, ioaddr); + + /* Stop the chip's Tx and Rx processes. */ + outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6); + /* Clear the missed-packet counter. */ + (volatile int)inl(ioaddr + CSR8); + + if (chip_id == DC21041) { + if (inl(ioaddr + CSR9) & 0x8000) { + printk(" 21040 compatible mode,"); + chip_id = DC21040; + } else { + printk(" 21041 mode,"); + } + } + + /* The station address ROM is read byte serially. The register must + be polled, waiting for the value to be read bit serially from the + EEPROM. + */ + sum = 0; + if (chip_id == DC21040) { + outl(0, ioaddr + CSR9); /* Reset the pointer with a dummy write. */ + for (i = 0; i < 6; i++) { + int value, boguscnt = 100000; + do + value = inl(ioaddr + CSR9); + while (value < 0 && --boguscnt > 0); + dev->dev_addr[i] = value; + sum += value & 0xff; + } + } else if (chip_id == LC82C168) { + for (i = 0; i < 3; i++) { + int value, boguscnt = 100000; + outl(0x600 | i, ioaddr + 0x98); + do + value = inl(ioaddr + CSR9); + while (value < 0 && --boguscnt > 0); + ((u16*)dev->dev_addr)[i] = value; + sum += value & 0xffff; + } + } else { /* Must be a new chip, with a serial EEPROM interface. */ + /* We read the whole EEPROM, and sort it out later. DEC has a + specification _Digital Semiconductor 21X4 Serial ROM Format_ + but early vendor boards just put the address in the first six + EEPROM locations. */ + unsigned char ee_data[128]; + int sa_offset = 0; + + for (i = 0; i < sizeof(ee_data)/2; i++) + ((u16 *)ee_data)[i] = read_eeprom(ioaddr, i); + + /* Detect the simple EEPROM format by the duplicated station addr. */ + for (i = 0; i < 8; i ++) + if (ee_data[i] != ee_data[16+i]) + sa_offset = 20; + if (ee_data[0] == 0xff && ee_data[1] == 0xff && ee_data[2] == 0) { + sa_offset = 2; /* Grrr, damn Matrox boards. */ + multiport_cnt = 4; + } + for (i = 0; i < 6; i ++) { + dev->dev_addr[i] = ee_data[i + sa_offset]; + sum += ee_data[i + sa_offset]; + } + } + /* Lite-On boards have the address byte-swapped. */ + if (dev->dev_addr[0] == 0xA0 && dev->dev_addr[1] == 0x00) + for (i = 0; i < 6; i+=2) { + char tmp = dev->dev_addr[i]; + dev->dev_addr[i] = dev->dev_addr[i+1]; + dev->dev_addr[i+1] = tmp; + } + /* On the Zynx 315 Etherarray and other multiport boards only the + first Tulip has an EEPROM. + The addresses of the subsequent ports are derived from the first. + Many PCI BIOSes also incorrectly report the IRQ line, so we correct + that here as well. */ + if (sum == 0 || sum == 6*0xff) { + printk(" EEPROM not present,"); + for (i = 0; i < 5; i++) + dev->dev_addr[i] = last_phys_addr[i]; + dev->dev_addr[i] = last_phys_addr[i] + 1; +#if defined(__i386__) /* This BIOS bug doesn't exist on Alphas. */ + irq = last_irq; +#endif + } + + for (i = 0; i < 6; i++) + printk(" %2.2x", last_phys_addr[i] = dev->dev_addr[i]); + printk(", IRQ %d.\n", irq); + last_irq = irq; + + /* We do a request_region() only to register /proc/ioports info. */ + /* Note that proper size is tulip_tbl[chip_id].chip_name, but... */ + request_region(ioaddr, TULIP_TOTAL_SIZE, dev->name); + + dev->base_addr = ioaddr; + dev->irq = irq; + + /* Make certain the data structures are quadword aligned. */ + tp = (void *)(((long)kmalloc(sizeof(*tp), GFP_KERNEL | GFP_DMA) + 7) & ~7); + memset(tp, 0, sizeof(*tp)); + dev->priv = tp; + + tp->next_module = root_tulip_dev; + root_tulip_dev = dev; + + tp->pci_bus = pci_bus; + tp->pci_dev_fn = pci_device_fn; + tp->chip_id = chip_id; + +#ifdef TULIP_FULL_DUPLEX + tp->full_duplex = 1; + tp->full_duplex_lock = 1; +#endif +#ifdef TULIP_DEFAULT_MEDIA + tp->default_port = TULIP_DEFAULT_MEDIA; +#endif +#ifdef TULIP_NO_MEDIA_SWITCH + tp->medialock = 1; +#endif + + /* The lower four bits are the media type. */ + if (board_idx >= 0 && board_idx < MAX_UNITS) { + tp->default_port = options[board_idx] & 15; + if ((options[board_idx] & 0x90) || full_duplex[board_idx] > 0) + tp->full_duplex = 1; + if (mtu[board_idx] > 0) + dev->mtu = mtu[board_idx]; + } + if (dev->mem_start) + tp->default_port = dev->mem_start; + if (tp->default_port) { + tp->medialock = 1; + if (media_cap[tp->default_port] & MediaAlwaysFD) + tp->full_duplex = 1; + } + if (tp->full_duplex) + tp->full_duplex_lock = 1; + + /* This is logically part of probe1(), but too complex to write inline. */ + if (tulip_tbl[chip_id].flags & HAS_MEDIA_TABLE) + parse_eeprom(dev); + + if (media_cap[tp->default_port] & MediaIsMII) { + u16 media2advert[] = { 0x20, 0x40, 0x03e0, 0x60, 0x80, 0x100, 0x200 }; + tp->to_advertise = media2advert[tp->default_port - 9]; + } else + tp->to_advertise = 0x03e1; + + if ((tp->mtable && tp->mtable->has_mii) || + ( ! tp->mtable && (tulip_tbl[tp->chip_id].flags & HAS_MII))) { + int phy, phy_idx; + /* Find the connected MII xcvrs. + Doing this in open() would allow detecting external xcvrs later, + but takes much time. */ + for (phy = 0, phy_idx = 0; phy < 32 && phy_idx < sizeof(tp->phys); + phy++) { + int mii_status = mdio_read(dev, phy, 1); + if (mii_status != 0xffff && mii_status != 0x0000) { + int mii_reg0 = mdio_read(dev, phy, 0); + int reg4 = ((mii_status>>6) & tp->to_advertise) | 1; + tp->phys[phy_idx] = phy; + tp->advertising[phy_idx++] = reg4; + printk(KERN_INFO "%s: MII transceiver found at MDIO address " + "%d, config %4.4x status %4.4x.\n", + dev->name, phy, mii_reg0, mii_status); + if (1 || (media_cap[tp->default_port] & MediaIsMII)) { + printk(KERN_DEBUG "%s: Advertising %4.4x on PHY %d," + " previously advertising %4.4x.\n", + dev->name, reg4, phy, mdio_read(dev, phy, 4)); + mdio_write(dev, phy, 4, reg4); + } + /* Enable autonegotiation: some boards default to off. */ + mdio_write(dev, phy, 0, mii_reg0 | + (tp->full_duplex ? 0x1100 : 0x1000) | + (media_cap[tp->default_port]&MediaIs100 ? 0x2000:0)); + } + } + tp->mii_cnt = phy_idx; + if (tp->mtable && tp->mtable->has_mii && phy_idx == 0) { + printk(KERN_INFO "%s: ***WARNING***: No MII transceiver found!\n", + dev->name); + tp->phys[0] = 1; + } + } + + /* The Tulip-specific entries in the device structure. */ + dev->open = &tulip_open; + dev->hard_start_xmit = &tulip_start_xmit; + dev->stop = &tulip_close; + dev->get_stats = &tulip_get_stats; +#ifdef HAVE_PRIVATE_IOCTL + dev->do_ioctl = &private_ioctl; +#endif +#ifdef HAVE_MULTICAST + dev->set_multicast_list = &set_rx_mode; +#endif + + /* Reset the xcvr interface and turn on heartbeat. */ + switch (chip_id) { + case DC21041: + outl(0x00000000, ioaddr + CSR13); + outl(0xFFFFFFFF, ioaddr + CSR14); + outl(0x00000008, ioaddr + CSR15); /* Listen on AUI also. */ + outl(inl(ioaddr + CSR6) | 0x0200, ioaddr + CSR6); + outl(0x0000EF05, ioaddr + CSR13); + break; + case DC21040: + outl(0x00000000, ioaddr + CSR13); + outl(0x00000004, ioaddr + CSR13); + break; + case DC21140: default: + if (tp->mtable) + outl(tp->mtable->csr12dir | 0x100, ioaddr + CSR12); + break; + case DC21142: + outl(0x82420200, ioaddr + CSR6); + outl(0x0001, ioaddr + CSR13); + outl(0x0003FFFF, ioaddr + CSR14); + outl(0x0008, ioaddr + CSR15); + outl(0x0001, ioaddr + CSR13); + outl(0x1301, ioaddr + CSR12); /* Start NWay. */ + break; + case LC82C168: + if ( ! tp->mii_cnt) { + outl(0x00420000, ioaddr + CSR6); + outl(0x30, ioaddr + CSR12); + outl(0x0001F078, ioaddr + 0xB8); + outl(0x0201F078, ioaddr + 0xB8); /* Turn on autonegotiation. */ + } + break; + case MX98713: case MX98715: case MX98725: + outl(0x00000000, ioaddr + CSR6); + outl(0x000711C0, ioaddr + CSR14); /* Turn on NWay. */ + outl(0x00000001, ioaddr + CSR13); + break; + } + + return dev; +} + +/* Serial EEPROM section. */ +/* The main routine to parse the very complicated SROM structure. + Search www.digital.com for "21X4 SROM" to get details. + This code is very complex, and will require changes to support + additional cards, so I'll be verbose about what is going on. + */ + +/* Known cards that have old-style EEPROMs. */ +static struct fixups { + char *name; + unsigned char addr0, addr1, addr2; + u16 newtable[32]; /* Max length below. */ +} eeprom_fixups[] = { + {"Asante", 0, 0, 0x94, {0x1e00, 0x0000, 0x0800, 0x0100, 0x018c, + 0x0000, 0x0000, 0xe078, 0x0001, 0x0050, 0x0018 }}, + {"SMC9332DST", 0, 0, 0xC0, { 0x1e00, 0x0000, 0x0800, 0x021f, + 0x0000, 0x009E, /* 10baseT */ + 0x0903, 0x006D, /* 100baseTx */ }}, + {"Cogent EM100", 0, 0, 0x92, { 0x1e00, 0x0000, 0x0800, 0x033f, + 0x0107, 0x8021, /* 100baseFx */ + 0x0108, 0x8021, /* 100baseFx-FD */ + 0x0103, 0x006D, /* 100baseTx */ }}, + {"Maxtech NX-110", 0, 0, 0xE8, { 0x1e00, 0x0000, 0x0800, 0x0313, + 0x1001, 0x009E, /* 10base2, CSR12 0x10*/ + 0x0000, 0x009E, /* 10baseT */ + 0x0303, 0x006D, /* 100baseTx, CSR12 0x03 */ }}, + {"Accton EN1207", 0, 0, 0xE8, { 0x1e00, 0x0000, 0x0800, 0x031F, + 0x1B01, 0x0000, /* 10base2, CSR12 0x1B */ + 0x1B03, 0x006D, /* 100baseTx, CSR12 0x1B */ + 0x0B00, 0x009E, /* 10baseT, CSR12 0x0B */ + }}, + {0, 0, 0, 0, {}}}; + +static const char * block_name[] = {"21140 non-MII", "21140 MII PHY", + "21142 Serial PHY", "21142 MII PHY", "21143 SYM PHY", "21143 reset method"}; + +#define EEPROM_SIZE 128 +#if defined(__i386__) +#define get_u16(ptr) (*(u16 *)(ptr)) +#else +#define get_u16(ptr) (((u8*)(ptr))[0] + (((u8*)(ptr))[1]<<8)) +#endif + +static void parse_eeprom(struct device *dev) +{ + /* The last media info list parsed, for multiport boards. */ + static struct mediatable *last_mediatable = NULL; + static unsigned char *last_ee_data = NULL; + static int controller_index = 0; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + unsigned char *ee_data = tp->eeprom; + int i; + + tp->mtable = 0; + for (i = 0; i < EEPROM_SIZE/2; i++) + ((u16 *)ee_data)[i] = read_eeprom(ioaddr, i); + + /* Detect an old-style (SA only) EEPROM layout: + memcmp(eedata, eedata+16, 8). */ + for (i = 0; i < 8; i ++) + if (ee_data[i] != ee_data[16+i]) + break; + if (i >= 8) { + if (ee_data[0] == 0xff) { + if (last_mediatable) { + controller_index++; + printk(KERN_INFO "%s: Controller %d of multiport board.\n", + dev->name, controller_index); + tp->mtable = last_mediatable; + ee_data = last_ee_data; + goto subsequent_board; + } else + printk(KERN_INFO "%s: Missing EEPROM, this interface may " + "not work correctly!\n", + dev->name); + return; + } + /* Do a fix-up based on the vendor half of the station address prefix. */ + for (i = 0; eeprom_fixups[i].name; i++) { + if (dev->dev_addr[0] == eeprom_fixups[i].addr0 + && dev->dev_addr[1] == eeprom_fixups[i].addr1 + && dev->dev_addr[2] == eeprom_fixups[i].addr2) { + if (dev->dev_addr[2] == 0xE8 && ee_data[0x1a] == 0x55) + i++; /* An Accton EN1207, not an outlaw Maxtech. */ + memcpy(ee_data + 26, eeprom_fixups[i].newtable, + sizeof(eeprom_fixups[i].newtable)); + printk(KERN_INFO "%s: Old format EEPROM on '%s' board. Using" + " substitute media control info.\n", + dev->name, eeprom_fixups[i].name); + break; + } + } + if (eeprom_fixups[i].name == NULL) { /* No fixup found. */ + printk(KERN_INFO "%s: Old style EEPROM -- no media selection information.\n", + dev->name); + return; + } + } + if (tulip_debug > 1) { + printk(KERN_DEBUG "read_eeprom:"); + for (i = 0; i < 64; i++) { + printk("%s%4.4x", (i & 7) == 0 ? "\n" KERN_DEBUG : " ", + read_eeprom(ioaddr, i)); + } + printk("\n"); + } + + controller_index = 0; + if (ee_data[19] > 1) { /* Multiport board. */ + last_ee_data = ee_data; + } +subsequent_board: + + if (ee_data[27] == 0) { /* No valid media table. */ + } else if (tp->chip_id == DC21041) { + unsigned char *p = (void *)ee_data + ee_data[27 + controller_index*3]; + short media; + int count; + + media = get_u16(p); + p += 2; + count = *p++; + + printk(KERN_INFO "%s:21041 Media information at %d, default media " + "%4.4x (%s).\n", dev->name, ee_data[27], media, + media & 0x0800 ? "Autosense" : medianame[media & 15]); + for (i = 0; i < count; i++) { + unsigned char media_code = *p++; + u16 csrvals[3]; + int idx; + for (idx = 0; idx < 3; idx++) { + csrvals[idx] = get_u16(p); + p += 2; + } + if (media_code & 0x40) { + printk(KERN_INFO "%s: 21041 media %2.2x (%s)," + " csr13 %4.4x csr14 %4.4x csr15 %4.4x.\n", + dev->name, media_code & 15, medianame[media_code & 15], + csrvals[0], csrvals[1], csrvals[2]); + } else + printk(KERN_INFO "%s: 21041 media #%d, %s.\n", + dev->name, media_code & 15, medianame[media_code & 15]); + } + } else { + unsigned char *p = (void *)ee_data + ee_data[27]; + unsigned char csr12dir = 0; + int count; + struct mediatable *mtable; + u16 media = get_u16(p); + + p += 2; + if (tulip_tbl[tp->chip_id].flags & CSR12_IN_SROM) + csr12dir = *p++; + count = *p++; + mtable = (struct mediatable *) + kmalloc(sizeof(struct mediatable) + count*sizeof(struct medialeaf), + GFP_KERNEL); + if (mtable == NULL) + return; /* Horrible, impossible failure. */ + last_mediatable = tp->mtable = mtable; + mtable->defaultmedia = media; + mtable->leafcount = count; + mtable->csr12dir = csr12dir; + mtable->has_nonmii = mtable->has_mii = 0; + + printk(KERN_INFO "%s: EEPROM default media type %s.\n", dev->name, + media & 0x0800 ? "Autosense" : medianame[media & 15]); + for (i = 0; i < count; i++) { + struct medialeaf *leaf = &mtable->mleaf[i]; + + if ((p[0] & 0x80) == 0) { /* 21140 Compact block. */ + leaf->type = 0; + leaf->media = p[0] & 0x3f; + leaf->leafdata = p; + if ((p[2] & 0x61) == 0x01) /* Bogus, but Znyx boards do it. */ + mtable->has_mii = 1; + p += 4; + } else { + leaf->type = p[1]; + if (p[1] & 1) { + mtable->has_mii = 1; + leaf->media = 11; + } else { + mtable->has_nonmii = 1; + leaf->media = p[2] & 0x0f; + } + leaf->leafdata = p + 2; + p += (p[0] & 0x3f) + 1; + } + if (tulip_debug > 1 && leaf->media == 11) { + unsigned char *bp = leaf->leafdata; + printk(KERN_INFO "%s: MII interface PHY %d, setup/reset " + "sequences %d/%d long, capabilities %2.2x %2.2x.\n", + dev->name, bp[0], bp[1], bp[1 + bp[1]*2], + bp[5 + bp[2 + bp[1]*2]*2], bp[4 + bp[2 + bp[1]*2]*2]); + } + printk(KERN_INFO "%s: Index #%d - Media %s (#%d) described " + "by a %s (%d) block.\n", + dev->name, i, medianame[leaf->media], leaf->media, + block_name[leaf->type], leaf->type); + } + } +} +/* Reading a serial EEPROM is a "bit" grungy, but we work our way through:->.*/ + +/* EEPROM_Ctrl bits. */ +#define EE_SHIFT_CLK 0x02 /* EEPROM shift clock. */ +#define EE_CS 0x01 /* EEPROM chip select. */ +#define EE_DATA_WRITE 0x04 /* EEPROM chip data in. */ +#define EE_WRITE_0 0x01 +#define EE_WRITE_1 0x05 +#define EE_DATA_READ 0x08 /* EEPROM chip data out. */ +#define EE_ENB (0x4800 | EE_CS) + +/* Delay between EEPROM clock transitions. + The 1.2 code is a "nasty" timing loop, but PC compatible machines are + *supposed* to delay an ISA-compatible period for the SLOW_DOWN_IO macro. */ +#ifdef _LINUX_DELAY_H +#define eeprom_delay(nanosec) udelay((nanosec + 999)/1000) +#else +#define eeprom_delay(nanosec) do { int _i = 3; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0) +#endif + +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD (5 << 6) +#define EE_READ_CMD (6 << 6) +#define EE_ERASE_CMD (7 << 6) + +static int read_eeprom(long ioaddr, int location) +{ + int i; + unsigned short retval = 0; + long ee_addr = ioaddr + CSR9; + int read_cmd = location | EE_READ_CMD; + + outl(EE_ENB & ~EE_CS, ee_addr); + outl(EE_ENB, ee_addr); + + /* Shift the read command bits out. */ + for (i = 10; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0; + outl(EE_ENB | dataval, ee_addr); + eeprom_delay(100); + outl(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr); + eeprom_delay(150); + outl(EE_ENB | dataval, ee_addr); /* Finish EEPROM a clock tick. */ + eeprom_delay(250); + } + outl(EE_ENB, ee_addr); + + for (i = 16; i > 0; i--) { + outl(EE_ENB | EE_SHIFT_CLK, ee_addr); + eeprom_delay(100); + retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0); + outl(EE_ENB, ee_addr); + eeprom_delay(100); + } + + /* Terminate the EEPROM access. */ + outl(EE_ENB & ~EE_CS, ee_addr); + return retval; +} + +/* MII transceiver control section. + Read and write the MII registers using software-generated serial + MDIO protocol. See the MII specifications or DP83840A data sheet + for details. */ + +/* The maximum data clock rate is 2.5 Mhz. The minimum timing is usually + met by back-to-back PCI I/O cycles, but we insert a delay to avoid + "overclocking" issues or future 66Mhz PCI. */ +#define mdio_delay() inl(mdio_addr) + +/* Read and write the MII registers using software-generated serial + MDIO protocol. It is just different enough from the EEPROM protocol + to not share code. The maxium data clock rate is 2.5 Mhz. */ +#define MDIO_SHIFT_CLK 0x10000 +#define MDIO_DATA_WRITE0 0x00000 +#define MDIO_DATA_WRITE1 0x20000 +#define MDIO_ENB 0x00000 /* Ignore the 0x02000 databook setting. */ +#define MDIO_ENB_IN 0x40000 +#define MDIO_DATA_READ 0x80000 + +static int mdio_read(struct device *dev, int phy_id, int location) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int i; + int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; + int retval = 0; + long mdio_addr = dev->base_addr + CSR9; + + if (tp->chip_id == LC82C168) { + long ioaddr = dev->base_addr; + int i = 1000; + outl(0x60020000 + (phy_id<<23) + (location<<18), ioaddr + 0xA0); + while (--i > 0) + if ( ! ((retval = inl(ioaddr + 0xA0)) & 0x80000000)) + return retval & 0xffff; + return 0xffff; + } + + /* Establish sync by sending at least 32 logic ones. */ + for (i = 32; i >= 0; i--) { + outl(MDIO_ENB | MDIO_DATA_WRITE1, mdio_addr); + mdio_delay(); + outl(MDIO_ENB | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + /* Shift the read command bits out. */ + for (i = 15; i >= 0; i--) { + int dataval = (read_cmd & (1 << i)) ? MDIO_DATA_WRITE1 : 0; + + outl(MDIO_ENB | dataval, mdio_addr); + mdio_delay(); + outl(MDIO_ENB | dataval | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + /* Read the two transition, 16 data, and wire-idle bits. */ + for (i = 19; i > 0; i--) { + outl(MDIO_ENB_IN, mdio_addr); + mdio_delay(); + retval = (retval << 1) | ((inl(mdio_addr) & MDIO_DATA_READ) ? 1 : 0); + outl(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(struct device *dev, int phy_id, int location, int value) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int i; + int cmd = (0x5002 << 16) | (phy_id << 23) | (location<<18) | value; + long mdio_addr = dev->base_addr + CSR9; + + if (tp->chip_id == LC82C168) { + long ioaddr = dev->base_addr; + int i = 1000; + outl(cmd, ioaddr + 0xA0); + do + if ( ! (inl(ioaddr + 0xA0) & 0x80000000)) + break; + while (--i > 0); + return; + } + + /* Establish sync by sending 32 logic ones. */ + for (i = 32; i >= 0; i--) { + outl(MDIO_ENB | MDIO_DATA_WRITE1, mdio_addr); + mdio_delay(); + outl(MDIO_ENB | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + /* Shift the command bits out. */ + for (i = 31; i >= 0; i--) { + int dataval = (cmd & (1 << i)) ? MDIO_DATA_WRITE1 : 0; + outl(MDIO_ENB | dataval, mdio_addr); + mdio_delay(); + outl(MDIO_ENB | dataval | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + /* Clear out extra bits. */ + for (i = 2; i > 0; i--) { + outl(MDIO_ENB_IN, mdio_addr); + mdio_delay(); + outl(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + mdio_delay(); + } + return; +} + + +static int +tulip_open(struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + int i = 0; + + /* On some chip revs we must set the MII/SYM port before the reset!? */ + if (tp->mii_cnt || (tp->mtable && tp->mtable->has_mii)) + outl(0x00040000, ioaddr + CSR6); + + /* Reset the chip, holding bit 0 set at least 50 PCI cycles. */ + outl(0x00000001, ioaddr + CSR0); +#ifdef _LINUX_DELAY_H + udelay(2); +#else + SLOW_DOWN_IO; +#endif + /* Deassert reset. + 486: Set 8 longword cache alignment, 8 longword burst. + 586: Set 16 longword cache alignment, no burst limit. + Cache alignment bits 15:14 Burst length 13:8 + 0000 No alignment 0x00000000 unlimited 0800 8 longwords + 4000 8 longwords 0100 1 longword 1000 16 longwords + 8000 16 longwords 0200 2 longwords 2000 32 longwords + C000 32 longwords 0400 4 longwords + Wait the specified 50 PCI cycles after a reset by initializing + Tx and Rx queues and the address filter list. */ +#if defined(__alpha__) + /* ToDo: Alpha setting could be better. */ + outl(0x01A00000 | 0xE000, ioaddr + CSR0); +#elif defined(__powerpc__) + outl(0x01A00080 | 0x8000, ioaddr + CSR0); +#elif defined(__i386__) +#if defined(MODULE) + /* When a module we don't have 'x86' to check. */ + outl(0x01A00000 | 0x4800, ioaddr + CSR0); +#else +#if (LINUX_VERSION_CODE > 0x2014c) +#define x86 boot_cpu_data.x86 +#endif + outl(0x01A00000 | (x86 <= 4 ? 0x4800 : 0x8000), ioaddr + CSR0); + if (x86 <= 4) + printk(KERN_INFO "%s: This is a 386/486 PCI system, setting cache " + "alignment to %x.\n", dev->name, + 0x01A00000 | (x86 <= 4 ? 0x4800 : 0x8000)); +#endif +#else + outl(0x01A00000 | 0x4800, ioaddr + CSR0); +#warning Processor architecture undefined! +#endif + +#ifdef SA_SHIRQ + if (request_irq(dev->irq, &tulip_interrupt, SA_SHIRQ, dev->name, dev)) { + return -EAGAIN; + } +#else + if (irq2dev_map[dev->irq] != NULL + || (irq2dev_map[dev->irq] = dev) == NULL + || dev->irq == 0 + || request_irq(dev->irq, &tulip_interrupt, 0, + tulip_tbl[tp->chip_id].chip_name)) { + return -EAGAIN; + } +#endif + + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: tulip_open() irq %d.\n", dev->name, dev->irq); + + MOD_INC_USE_COUNT; + + tulip_init_ring(dev); + + /* This is set_rx_mode(), but without starting the transmitter. */ + /* Fill the whole address filter table with our physical address. */ + { + u16 *eaddrs = (u16 *)dev->dev_addr; + u32 *setup_frm = tp->setup_frame, i; + + /* You must add the broadcast address when doing perfect filtering! */ + *setup_frm++ = 0xffff; + *setup_frm++ = 0xffff; + *setup_frm++ = 0xffff; + /* Fill the rest of the accept table with our physical address. */ + for (i = 1; i < 16; i++) { + *setup_frm++ = eaddrs[0]; + *setup_frm++ = eaddrs[1]; + *setup_frm++ = eaddrs[2]; + } + /* Put the setup frame on the Tx list. */ + tp->tx_ring[0].length = 0x08000000 | 192; + tp->tx_ring[0].buffer1 = virt_to_bus(tp->setup_frame); + tp->tx_ring[0].status = 0x80000000; + + tp->cur_tx++; + } + + outl(virt_to_bus(tp->rx_ring), ioaddr + CSR3); + outl(virt_to_bus(tp->tx_ring), ioaddr + CSR4); + + if (dev->if_port == 0) + dev->if_port = tp->default_port; + if (tp->chip_id == DC21041 && dev->if_port > 4) + /* Invalid: Select initial TP, autosense, autonegotiate. */ + dev->if_port = 4; + + /* Allow selecting a default media. */ + if (tp->mtable == NULL) + goto media_picked; + if (dev->if_port) { + int looking_for = media_cap[dev->if_port] & MediaIsMII ? 11 : + (dev->if_port == 12 ? 0 : dev->if_port); + for (i = 0; i < tp->mtable->leafcount; i++) + if (tp->mtable->mleaf[i].media == looking_for) { + printk(KERN_INFO "%s: Using user-specified media %s.\n", + dev->name, medianame[dev->if_port]); + goto media_picked; + } + } + if ((tp->mtable->defaultmedia & 0x0800) == 0) + for (i = 0; i < tp->mtable->leafcount; i++) + if (tp->mtable->mleaf[i].media == (tp->mtable->defaultmedia & 15)) { + printk(KERN_INFO "%s: Using EEPROM-set media %s.\n", + dev->name, medianame[tp->mtable->mleaf[i].media]); + goto media_picked; + } + /* Start sensing first non-full-duplex media. */ + for (i = tp->mtable->leafcount - 1; + (media_cap[tp->mtable->mleaf[i].media] & MediaAlwaysFD) && i > 0; i--) + ; +media_picked: + + tp->csr6 = 0; + tp->cur_index = i; + if (dev->if_port == 0 && tp->chip_id == DC21142) { + tp->csr6 = 0x82420200; + outl(0x0003FFFF, ioaddr + CSR14); + outl(0x0008, ioaddr + CSR15); + outl(0x0001, ioaddr + CSR13); + outl(0x1301, ioaddr + CSR12); + } else if (tp->chip_id == LC82C168 && tp->mii_cnt && ! tp->medialock) { + dev->if_port = 11; + tp->csr6 = 0x816C0000 | (tp->full_duplex ? 0x0200 : 0); + outl(0x0001, ioaddr + CSR15); + } else + select_media(dev, 1); + + /* Start the chip's Tx to process setup frame. */ + outl(tp->csr6, ioaddr + CSR6); + outl(tp->csr6 | 0x2000, ioaddr + CSR6); + + dev->tbusy = 0; + tp->interrupt = 0; + dev->start = 1; + + /* Enable interrupts by setting the interrupt mask. */ + outl(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR5); + outl(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR7); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + outl(0, ioaddr + CSR2); /* Rx poll demand */ + + if (tulip_debug > 2) { + printk(KERN_DEBUG "%s: Done tulip_open(), CSR0 %8.8x, CSR5 %8.8x CSR6 %8.8x.\n", + dev->name, inl(ioaddr + CSR0), inl(ioaddr + CSR5), + inl(ioaddr + CSR6)); + } + /* Set the timer to switch to check for link beat and perhaps switch + to an alternate media type. */ + init_timer(&tp->timer); + tp->timer.expires = RUN_AT(5*HZ); + tp->timer.data = (unsigned long)dev; + tp->timer.function = tulip_tbl[tp->chip_id].media_timer; + add_timer(&tp->timer); + + return 0; +} + +/* Set up the transceiver control registers for the selected media type. */ +static void select_media(struct device *dev, int startup) +{ + long ioaddr = dev->base_addr; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + struct mediatable *mtable = tp->mtable; + u32 new_csr6; + int check_mii =0, i; + + if (mtable) { + struct medialeaf *mleaf = &mtable->mleaf[tp->cur_index]; + unsigned char *p = mleaf->leafdata; + switch (mleaf->type) { + case 0: /* 21140 non-MII xcvr. */ + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: Using a 21140 non-MII transceiver" + " with control setting %2.2x.\n", + dev->name, p[1]); + dev->if_port = p[0]; + if (startup) + outl(mtable->csr12dir | 0x100, ioaddr + CSR12); + outl(p[1], ioaddr + CSR12); + new_csr6 = 0x02000000 | ((p[2] & 0x71) << 18); + break; + case 2: case 4: { + u16 setup[3]; + for (i = 0; i < 3; i++) + setup[i] = get_u16(&p[i*2 + 1]); + + dev->if_port = p[0] & 15; + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: 21142 non-MII %s transceiver control %4.4x/%4.4x.\n", + dev->name, medianame[dev->if_port], setup[0], setup[1]); + if (p[0] & 0x40) { /* SIA (CSR13-15) setup values are provided. */ + outl(0, ioaddr + CSR13); + outl(setup[1], ioaddr + CSR14); + outl(setup[2], ioaddr + CSR15); + outl(setup[0], ioaddr + CSR13); + for (i = 0; i < 3; i++) /* Re-fill setup[] */ + setup[i] = get_u16(&p[i*2 + 7]); + } else if (dev->if_port <= 4) { + outl(0, ioaddr + CSR13); + outl(t21142_csr14[dev->if_port], ioaddr + CSR14); + outl(t21142_csr15[dev->if_port], ioaddr + CSR15); + outl(t21142_csr13[dev->if_port], ioaddr + CSR13); + } else { + outl(0, ioaddr + CSR14); + outl(8, ioaddr + CSR15); + outl(0, ioaddr + CSR13); + } + outl(setup[0]<<16, ioaddr + CSR15); /* Direction */ + outl(setup[1]<<16, ioaddr + CSR15); /* Data */ + if (mleaf->type == 4) + new_csr6 = 0x82020000 | ((setup[2] & 0x71) << 18); + else + new_csr6 = 0x82420000; + break; + } + case 1: case 3: { + int phy_num = p[0]; + int init_length = p[1]; + u16 *misc_info; + u16 to_advertise; + + dev->if_port = 11; + check_mii = 1; + new_csr6 = 0x020E0000; + if (mleaf->type == 3) { /* 21142 */ + u16 *init_sequence = (u16*)(p+2); + u16 *reset_sequence = &((u16*)(p+3))[init_length]; + int reset_length = p[2 + init_length*2]; + misc_info = reset_sequence + reset_length; + if (startup) + for (i = 0; i < reset_length; i++) + outl(get_u16(&reset_sequence[i]) << 16, ioaddr + CSR15); + for (i = 0; i < init_length; i++) + outl(get_u16(&init_sequence[i]) << 16, ioaddr + CSR15); + } else { + u8 *init_sequence = p + 2; + u8 *reset_sequence = p + 3 + init_length; + int reset_length = p[2 + init_length]; + misc_info = (u16*)(reset_sequence + reset_length); + if (startup) { + outl(mtable->csr12dir | 0x100, ioaddr + CSR12); + for (i = 0; i < reset_length; i++) + outl(reset_sequence[i], ioaddr + CSR12); + } + for (i = 0; i < init_length; i++) + outl(init_sequence[i], ioaddr + CSR12); + } + to_advertise = (get_u16(&misc_info[1]) & tp->to_advertise) | 1; + tp->advertising[phy_num] = to_advertise; + if (tulip_debug > 1 || 1) + printk(KERN_DEBUG "%s: Advertising %4.4x on PHY %d (%d).\n", + dev->name, to_advertise, phy_num, tp->phys[phy_num]); + /* Bogus: put in by a committee? */ + mdio_write(dev, tp->phys[phy_num], 4, to_advertise); + break; + } + default: + new_csr6 = 0x020E0000; + } + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: Using media type %s, CSR12 is %2.2x.\n", + dev->name, medianame[dev->if_port], + inl(ioaddr + CSR12) & 0xff); + } else if (tp->chip_id == DC21041) { + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: 21041 using media %s, CSR12 is %4.4x.\n", + dev->name, medianame[dev->if_port & 15], + inl(ioaddr + CSR12) & 0xffff); + outl(0x00000000, ioaddr + CSR13); /* Reset the serial interface */ + outl(t21041_csr14[dev->if_port], ioaddr + CSR14); + outl(t21041_csr15[dev->if_port], ioaddr + CSR15); + outl(t21041_csr13[dev->if_port], ioaddr + CSR13); + new_csr6 = 0x80020000; + } else if (tp->chip_id == LC82C168) { + if (startup && ! tp->medialock) + dev->if_port = tp->mii_cnt ? 11 : 0; + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: PNIC PHY status is %3.3x, CSR12 %4.4x," + " media %s.\n", + dev->name, inl(ioaddr + 0xB8), inl(ioaddr + CSR12), + medianame[dev->if_port]); + if (tp->mii_cnt) { + new_csr6 = 0x812C0000; + outl(0x0001, ioaddr + CSR15); + outl(0x0201B07A, ioaddr + 0xB8); + } else if (startup) { + /* Start with 10mbps to do autonegotiation. */ + outl(0x32, ioaddr + CSR12); + new_csr6 = 0x00420000; + outl(0x0001B078, ioaddr + 0xB8); + outl(0x0201B078, ioaddr + 0xB8); + } else if (dev->if_port == 3 || dev->if_port == 5) { + outl(0x33, ioaddr + CSR12); + new_csr6 = 0x01860000; + if (startup) + outl(0x0201F868, ioaddr + 0xB8); /* Trigger autonegotiation. */ + else + outl(0x1F868, ioaddr + 0xB8); + } else { + outl(0x32, ioaddr + CSR12); + new_csr6 = 0x00420000; + outl(0x1F078, ioaddr + 0xB8); + } + } else if (tp->chip_id == DC21040) { /* 21040 */ + /* Turn on the xcvr interface. */ + int csr12 = inl(ioaddr + CSR12); + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: 21040 media type is %s, CSR12 is %2.2x.\n", + dev->name, dev->if_port ? "AUI" : "10baseT", csr12); + new_csr6 = (dev->if_port ? 0x01860000 : 0x00420000); + /* Set the full duplux match frame. */ + outl(FULL_DUPLEX_MAGIC, ioaddr + CSR11); + outl(0x00000000, ioaddr + CSR13); /* Reset the serial interface */ + outl(dev->if_port ? 0x0000000C : 0x00000004, ioaddr + CSR13); + } else { /* Unknown chip type with no media table. */ + if (tp->default_port == 0) + if (tp->mii_cnt) { + dev->if_port = 11; + } else + dev->if_port = 3; + if (media_cap[dev->if_port] & MediaIsMII) { + new_csr6 = 0x020E0000; + } else if (media_cap[dev->if_port] & MediaIsFx) { + new_csr6 = 0x028600000; + } else + new_csr6 = 0x038600000; + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: No media description table, assuming " + "%s transceiver, CSR12 %2.2x.\n", + dev->name, medianame[dev->if_port], + inl(ioaddr + CSR12)); + } + + tp->csr6 = new_csr6 | (tp->csr6 & 0xfdff) | (tp->full_duplex ? 0x0200 : 0); + return; +} + +static void tulip_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + u32 csr12 = inl(ioaddr + CSR12); + int next_tick = 0; + + if (tulip_debug > 3) { + printk(KERN_DEBUG "%s: Media selection tick, status %8.8x mode %8.8x " + "SIA %8.8x %8.8x %8.8x %8.8x.\n", + dev->name, inl(ioaddr + CSR5), inl(ioaddr + CSR6), + csr12, inl(ioaddr + CSR13), + inl(ioaddr + CSR14), inl(ioaddr + CSR15)); + } + switch (tp->chip_id) { + case DC21040: + if (csr12 & 0x0002) { /* Network error */ + printk(KERN_INFO "%s: No 10baseT link beat found, switching to %s media.\n", + dev->name, dev->if_port ? "10baseT" : "AUI"); + dev->if_port ^= 1; + outl(dev->if_port ? 0x0000000C : 0x00000004, ioaddr + CSR13); + dev->trans_start = jiffies; + } + break; + case DC21041: + if (tulip_debug > 2) + printk(KERN_DEBUG "%s: 21041 media tick CSR12 %8.8x.\n", + dev->name, csr12); + switch (dev->if_port) { + case 0: case 3: case 4: + if (csr12 & 0x0004) { /*LnkFail */ + /* 10baseT is dead. Check for activity on alternate port. */ + tp->mediasense = 1; + if (csr12 & 0x0200) + dev->if_port = 2; + else + dev->if_port = 1; + printk(KERN_INFO "%s: No 21041 10baseT link beat, Media switched to %s.\n", + dev->name, medianame[dev->if_port]); + outl(0, ioaddr + CSR13); /* Reset */ + outl(t21041_csr14[dev->if_port], ioaddr + CSR14); + outl(t21041_csr15[dev->if_port], ioaddr + CSR15); + outl(t21041_csr13[dev->if_port], ioaddr + CSR13); + next_tick = 10*HZ; /* 2.4 sec. */ + } else + next_tick = 30*HZ; + break; + case 1: /* 10base2 */ + case 2: /* AUI */ + if (csr12 & 0x0100) { + next_tick = (30*HZ); /* 30 sec. */ + tp->mediasense = 0; + } else if ((csr12 & 0x0004) == 0) { + printk(KERN_INFO "%s: 21041 media switched to 10baseT.\n", dev->name); + dev->if_port = 0; + select_media(dev, 0); + next_tick = (24*HZ)/10; /* 2.4 sec. */ + } else if (tp->mediasense || (csr12 & 0x0002)) { + dev->if_port = 3 - dev->if_port; /* Swap ports. */ + select_media(dev, 0); + next_tick = 20*HZ; + } else { + next_tick = 20*HZ; + } + break; + } + break; + case DC21140: case DC21142: case MX98713: default: { + struct medialeaf *mleaf; + unsigned char *p; + if (tp->mtable == NULL) { /* No EEPROM info, use generic code. */ + /* Not much that can be done. + Assume this a generic MII or SYM transceiver. */ + next_tick = 60*HZ; + if (tulip_debug > 2) + printk(KERN_DEBUG "%s: network media monitor CSR6 %8.8x " + "CSR12 0x%2.2x.\n", + dev->name, inl(ioaddr + CSR6), csr12 & 0xff); + break; + } + mleaf = &tp->mtable->mleaf[tp->cur_index]; + p = mleaf->leafdata; + switch (mleaf->type) { + case 0: case 4: { + /* Type 0 serial or 4 SYM transceiver. Check the link beat bit. */ + int offset = mleaf->type == 4 ? 5 : 2; + s8 bitnum = p[offset]; + if (p[offset+1] & 0x80) { + if (tulip_debug > 1) + printk(KERN_DEBUG"%s: Transceiver monitor tick " + "CSR12=%#2.2x, no media sense.\n", + dev->name, csr12); + if (mleaf->type == 4) { + if (mleaf->media == 3 && (csr12 & 0x02)) + goto select_next_media; + } + break; + } + if (tulip_debug > 2) + printk(KERN_DEBUG "%s: Transceiver monitor tick: CSR12=%#2.2x" + " bit %d is %d, expecting %d.\n", + dev->name, csr12, (bitnum >> 1) & 7, + (csr12 & (1 << ((bitnum >> 1) & 7))) != 0, + (bitnum >= 0)); + /* Check that the specified bit has the proper value. */ + if ((bitnum < 0) != + ((csr12 & (1 << ((bitnum >> 1) & 7))) != 0)) { + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: Link beat detected for %s.\n", dev->name, + medianame[mleaf->media]); + if ((p[2] & 0x61) == 0x01) /* Bogus Znyx board. */ + goto actually_mii; + break; + } + if (tp->medialock) + break; + select_next_media: + if (--tp->cur_index < 0) { + /* We start again, but should instead look for default. */ + tp->cur_index = tp->mtable->leafcount - 1; + } + dev->if_port = tp->mtable->mleaf[tp->cur_index].media; + if (media_cap[dev->if_port] & MediaIsFD) + goto select_next_media; /* Skip FD entries. */ + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: No link beat on media %s," + " trying transceiver type %s.\n", + dev->name, medianame[mleaf->media & 15], + medianame[tp->mtable->mleaf[tp->cur_index].media]); + select_media(dev, 0); + /* Restart the transmit process. */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + next_tick = (24*HZ)/10; + break; + } + case 1: case 3: { /* 21140, 21142 MII */ + int mii_reg1, mii_reg5; + actually_mii: + mii_reg1 = mdio_read(dev, tp->phys[0], 1); + mii_reg5 = mdio_read(dev, tp->phys[0], 5); + if (tulip_debug > 1) + printk(KERN_INFO "%s: MII status %4.4x, Link partner report " + "%4.4x, CSR12 %2.2x, %cD.\n", + dev->name, mii_reg1, mii_reg5, csr12, + tp->full_duplex ? 'F' : 'H'); + if (mii_reg1 != 0xffff && (mii_reg1 & 0x0004) == 0) { + int new_reg1 = mdio_read(dev, tp->phys[0], 1); + if ((new_reg1 & 0x0004) == 0) { + printk(KERN_INFO "%s: No link beat on the MII interface," + " status then %4.4x now %4.4x.\n", + dev->name, mii_reg1, new_reg1); + if (tp->mtable && tp->mtable->has_nonmii) + goto select_next_media; + } + } + if (mii_reg5 == 0xffff || mii_reg5 == 0x0000) + ; /* No MII device or no link partner report */ + else if (tp->full_duplex_lock) + ; + else { + int negotiated = mii_reg5 & tp->advertising[0]; + int duplex = ((negotiated & 0x0100) != 0 + || (negotiated & 0x00C0) == 0x0040); + /* 100baseTx-FD or 10T-FD, but not 100-HD */ + if (tp->full_duplex != duplex) { + tp->full_duplex = duplex; + if (tp->full_duplex) + tp->csr6 |= 0x0200; + else + tp->csr6 &= ~0x0200; + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + if (tulip_debug > 0) /* Gurppp, should be >1 */ + printk(KERN_INFO "%s: Setting %s-duplex based on MII" + " Xcvr #%d parter capability of %4.4x.\n", + dev->name, tp->full_duplex ? "full" : "half", + tp->phys[0], mii_reg5); + } + } + next_tick = 60*HZ; + break; + } + case 2: /* 21142 serial block has no link beat. */ + default: + break; + } + } + break; + } + if (next_tick) { + tp->timer.expires = RUN_AT(next_tick); + add_timer(&tp->timer); + } +} + +/* Handle the 21143 uniquely: do autoselect with NWay, not the EEPROM list + of available transceivers. */ +static void t21142_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + int csr12 = inl(ioaddr + CSR12); + int next_tick = 60*HZ; + int new_csr6 = 0; + + if (tulip_debug > 1) + printk(KERN_INFO"%s: 21142 negotiation status %8.8x, %s.\n", + dev->name, csr12, medianame[dev->if_port]); + if (dev->if_port == 3) { + if (csr12 & 2) { /* No 100mbps link beat, revert to 10mbps. */ + new_csr6 = 0x82420200; + outl(new_csr6, ioaddr + CSR6); + outl(0x0000, ioaddr + CSR13); + outl(0x0003FFFF, ioaddr + CSR14); + outl(0x0008, ioaddr + CSR15); + outl(0x0001, ioaddr + CSR13); + outl(0x1301, ioaddr + CSR12); /* Start NWay. */ + } + } else if ((csr12 & 0x7000) != 0x5000) { + /* Negotiation failed. Search media types. */ + if (tulip_debug > 1) + printk(KERN_INFO"%s: 21142 negotiation failed, status %8.8x.\n", + dev->name, csr12); + if (!(csr12 & 4)) { /* 10mbps link beat good. */ + new_csr6 = 0x82420000; + dev->if_port = 0; + outl(0, ioaddr + CSR13); + outl(0x0003FFFF, ioaddr + CSR14); + outl(t21142_csr15[dev->if_port], ioaddr + CSR15); + outl(t21142_csr13[dev->if_port], ioaddr + CSR13); + } else if (csr12 & 0x100) { + new_csr6 = 0x82420200; + dev->if_port = 2; + outl(0, ioaddr + CSR13); + outl(0x0003FFFF, ioaddr + CSR14); + outl(0x0008, ioaddr + CSR15); + outl(0x0001, ioaddr + CSR13); + } else { + /* Select 100mbps port to check for link beat. */ + new_csr6 = 0x83860000; + dev->if_port = 3; + outl(0, ioaddr + CSR13); + outl(0x0003FF7F, ioaddr + CSR14); + outl(8, ioaddr + CSR15); + outl(1, ioaddr + CSR13); + } + if (tulip_debug > 1) + printk(KERN_INFO"%s: Testing new 21142 media %s.\n", + dev->name, medianame[dev->if_port]); + if (new_csr6 != (tp->csr6 & ~0x00D5)) { + tp->csr6 &= 0x00D5; + tp->csr6 |= new_csr6; + outl(0x0301, ioaddr + CSR12); + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + } + } + tp->timer.expires = RUN_AT(next_tick); + add_timer(&tp->timer); +} + +static void t21142_lnk_change( struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + int csr12 = inl(ioaddr + CSR12); + + if (tulip_debug > 1) + printk(KERN_INFO"%s: 21142 link status interrupt %8.8x, CSR5 %x.\n", + dev->name, csr12, inl(ioaddr + CSR5)); + + if ((csr12 & 0x7000) == 0x5000) { + if (csr12 & 0x01800000) { + /* Switch to 100mbps mode. */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + if (csr12 & 0x01000000) { + dev->if_port = 5; + tp->csr6 = 0x83860200; + } else { + dev->if_port = 3; + tp->csr6 = 0x83860000; + } + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + } /* Else 10baseT-FD is handled automatically. */ + } else if (dev->if_port == 3) { + if (!(csr12 & 2)) + printk(KERN_INFO"%s: 21142 100baseTx link beat good.\n", + dev->name); + else + dev->if_port = 0; + } else if (dev->if_port == 0) { + if (!(csr12 & 4)) + printk(KERN_INFO"%s: 21142 10baseT link beat good.\n", + dev->name); + } else if (!(csr12 & 4)) { /* 10mbps link beat good. */ + printk(KERN_INFO"%s: 21142 10mpbs sensed media.\n", + dev->name); + dev->if_port = 0; + } else { /* 100mbps link beat good. */ + printk(KERN_INFO"%s: 21142 100baseTx sensed media.\n", + dev->name); + dev->if_port = 3; + tp->csr6 = 0x83860000; + outl(0x0003FF7F, ioaddr + CSR14); + outl(0x0301, ioaddr + CSR12); + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + } +} + + +static void mxic_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + int next_tick = 60*HZ; + + if (tulip_debug > 3) { + printk(KERN_INFO"%s: MXIC negotiation status %8.8x.\n", dev->name, + inl(ioaddr + CSR12)); + } + if (next_tick) { + tp->timer.expires = RUN_AT(next_tick); + add_timer(&tp->timer); + } +} + +static void pnic_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + int csr12 = inl(ioaddr + CSR12); + int next_tick = 60*HZ; + int new_csr6 = tp->csr6 & ~0x40C40200; + + if (media_cap[dev->if_port] & MediaIsMII) { + int negotiated = mdio_read(dev, tp->phys[0], 5) & tp->advertising[0]; + + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: LC82C168 negotiated capability %8.8x, " + "CSR5 %8.8x.\n", + dev->name, negotiated, inl(ioaddr + CSR5)); + + if (negotiated & 0x0380) /* 10 vs 100mbps */ + new_csr6 |= 0x812E0000; + else + new_csr6 |= 0x816E0000; + if (((negotiated & 0x0300) == 0x0100) /* Duplex */ + || (negotiated & 0x00C0) == 0x0040 + || tp->full_duplex_lock) { + tp->full_duplex = 1; + new_csr6 |= 0x0200; + } + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: LC82C168 MII PHY status %4.4x, Link " + "partner report %4.4x, csr6 %8.8x/%8.8x.\n", + dev->name, mdio_read(dev, tp->phys[0], 1), negotiated, + tp->csr6, inl(ioaddr + CSR6)); + } else { + int phy_reg = inl(ioaddr + 0xB8); + int csr5 = inl(ioaddr + CSR5); + + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: LC82C168 phy status %8.8x, CSR5 %8.8x.\n", + dev->name, phy_reg, csr5); + + if (phy_reg & 0x04000000) { /* Remote link fault */ + /*outl(0x0201F078, ioaddr + 0xB8);*/ + next_tick = 3*HZ; + } + if (inl(ioaddr + CSR5) & TPLnkFail) { /* 100baseTx link beat */ + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: %s link beat failed, CSR12 %4.4x, " + "CSR5 %8.8x, PHY %3.3x.\n", + dev->name, medianame[dev->if_port], csr12, + inl(ioaddr + CSR5), inl(ioaddr + 0xB8)); + if (tp->medialock) { + } else if (dev->if_port == 0) { + dev->if_port = 3; + outl(0x33, ioaddr + CSR12); + new_csr6 = 0x01860000; + outl(0x1F868, ioaddr + 0xB8); + } else { + dev->if_port = 0; + outl(0x32, ioaddr + CSR12); + new_csr6 = 0x00420000; + outl(0x1F078, ioaddr + 0xB8); + } + new_csr6 |= (tp->csr6 & 0xfdff); + next_tick = 3*HZ; + } else + new_csr6 = tp->csr6; + if (tp->full_duplex_lock || (phy_reg & 0x30000000) != 0) { + tp->full_duplex = 1; + new_csr6 |= 0x00000200; + } + } + if (tp->csr6 != new_csr6) { + tp->csr6 = new_csr6; + outl(tp->csr6 | 0x0002, ioaddr + CSR6); /* Restart Tx */ + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + dev->trans_start = jiffies; + if (tulip_debug > 0) /* Gurppp, should be >1 */ + printk(KERN_INFO "%s: Changing PNIC configuration to %s-duplex, " + "CSR6 %8.8x.\n", + dev->name, tp->full_duplex ? "full" : "half", new_csr6); + } + tp->timer.expires = RUN_AT(next_tick); + add_timer(&tp->timer); +} + +static void tulip_tx_timeout(struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + + if (media_cap[dev->if_port] & MediaIsMII) { + /* Do nothing -- the media monitor should handle this. */ + if (tulip_debug > 1) + printk(KERN_WARNING "%s: Transmit timeout using MII device.\n", + dev->name); + dev->trans_start = jiffies; + return; + } else if (tp->chip_id == DC21040) { + if (inl(ioaddr + CSR12) & 0x0002) { + printk(KERN_INFO "%s: transmit timed out, switching to %s media.\n", + dev->name, dev->if_port ? "10baseT" : "AUI"); + dev->if_port ^= 1; + outl(dev->if_port ? 0x0000000C : 0x00000004, ioaddr + CSR13); + } + dev->trans_start = jiffies; + return; + } else if (tp->chip_id == DC21041) { + u32 csr12 = inl(ioaddr + CSR12); + + printk(KERN_WARNING "%s: 21041 transmit timed out, status %8.8x, CSR12 %8.8x," + " CSR13 %8.8x, CSR14 %8.8x, resetting...\n", + dev->name, inl(ioaddr + CSR5), csr12, + inl(ioaddr + CSR13), inl(ioaddr + CSR14)); + tp->mediasense = 1; + if (dev->if_port == 1 || dev->if_port == 2) + if (csr12 & 0x0004) { + dev->if_port = 2 - dev->if_port; + } else + dev->if_port = 0; + else + dev->if_port = 1; + select_media(dev, 0); + tp->stats.tx_errors++; + dev->trans_start = jiffies; + return; + } else if (tp->chip_id == DC21140 || tp->chip_id == DC21142 + || tp->chip_id == MX98713) { + /* Stop the transmit process. */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + printk(KERN_WARNING "%s: 21140 transmit timed out, status %8.8x, " + "SIA %8.8x %8.8x %8.8x %8.8x, resetting...\n", + dev->name, inl(ioaddr + CSR5), inl(ioaddr + CSR12), + inl(ioaddr + CSR13), inl(ioaddr + CSR14), inl(ioaddr + CSR15)); + if (tp->mtable) { + if (--tp->cur_index < 0) { + /* We start again, but should instead look for default. */ + tp->cur_index = tp->mtable->leafcount - 1; + } + select_media(dev, 0); + printk(KERN_WARNING "%s: transmit timed out, switching to %s media.\n", + dev->name, dev->if_port ? "100baseTx" : "10baseT"); + } + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + tp->stats.tx_errors++; + dev->trans_start = jiffies; + return; + } else + printk(KERN_WARNING "%s: transmit timed out, status %8.8x, CSR12 %8.8x," + " resetting...\n", + dev->name, inl(ioaddr + CSR5), inl(ioaddr + CSR12)); +#ifdef way_too_many_messages + printk(" Rx ring %8.8x: ", (int)tp->rx_ring); + for (i = 0; i < RX_RING_SIZE; i++) + printk(" %8.8x", (unsigned int)tp->rx_ring[i].status); + printk("\n Tx ring %8.8x: ", (int)tp->tx_ring); + for (i = 0; i < TX_RING_SIZE; i++) + printk(" %8.8x", (unsigned int)tp->tx_ring[i].status); + printk("\n"); +#endif + + /* Perhaps we should reinitialize the hardware here. */ + dev->if_port = 0; + /* Stop and restart the chip's Tx processes . */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + /* Trigger an immediate transmit demand. */ + outl(0, ioaddr + CSR1); + + dev->trans_start = jiffies; + tp->stats.tx_errors++; + return; +} + + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void +tulip_init_ring(struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int i; + + tp->tx_full = 0; + tp->cur_rx = tp->cur_tx = 0; + tp->dirty_rx = tp->dirty_tx = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + tp->rx_ring[i].status = 0x80000000; /* Owned by Tulip chip */ + tp->rx_ring[i].length = PKT_BUF_SZ; + { + /* Note the receive buffer must be longword aligned. + dev_alloc_skb() provides 16 byte alignment. But do *not* + use skb_reserve() to align the IP header! */ + struct sk_buff *skb; + skb = DEV_ALLOC_SKB(PKT_BUF_SZ); + tp->rx_skbuff[i] = skb; + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ +#if LINUX_VERSION_CODE > 0x10300 + tp->rx_ring[i].buffer1 = virt_to_bus(skb->tail); +#else + tp->rx_ring[i].buffer1 = virt_to_bus(skb->data); +#endif + } + tp->rx_ring[i].buffer2 = virt_to_bus(&tp->rx_ring[i+1]); + } + /* Mark the last entry as wrapping the ring. */ + tp->rx_ring[i-1].length = PKT_BUF_SZ | 0x02000000; + tp->rx_ring[i-1].buffer2 = virt_to_bus(&tp->rx_ring[0]); + + /* The Tx buffer descriptor is filled in as needed, but we + do need to clear the ownership bit. */ + for (i = 0; i < TX_RING_SIZE; i++) { + tp->tx_skbuff[i] = 0; + tp->tx_ring[i].status = 0x00000000; + tp->tx_ring[i].buffer2 = virt_to_bus(&tp->tx_ring[i+1]); + } + tp->tx_ring[i-1].buffer2 = virt_to_bus(&tp->tx_ring[0]); +} + +static int +tulip_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int entry; + u32 flag; + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start < TX_TIMEOUT) + return 1; + tulip_tx_timeout(dev); + return 1; + } + + /* Caution: the write order is important here, set the base address + with the "ownership" bits last. */ + + /* Calculate the next Tx descriptor entry. */ + entry = tp->cur_tx % TX_RING_SIZE; + + tp->tx_skbuff[entry] = skb; + tp->tx_ring[entry].buffer1 = virt_to_bus(skb->data); + + if (tp->cur_tx - tp->dirty_tx < TX_RING_SIZE/2) {/* Typical path */ + flag = 0x60000000; /* No interrupt */ + dev->tbusy = 0; + } else if (tp->cur_tx - tp->dirty_tx == TX_RING_SIZE/2) { + flag = 0xe0000000; /* Tx-done intr. */ + dev->tbusy = 0; + } else if (tp->cur_tx - tp->dirty_tx < TX_RING_SIZE - 2) { + flag = 0x60000000; /* No Tx-done intr. */ + dev->tbusy = 0; + } else { + /* Leave room for set_rx_mode() to fill entries. */ + flag = 0xe0000000; /* Tx-done intr. */ + tp->tx_full = 1; + } + if (entry == TX_RING_SIZE-1) + flag |= 0xe2000000; + + tp->tx_ring[entry].length = skb->len | flag; + tp->tx_ring[entry].status = 0x80000000; /* Pass ownership to the chip. */ + tp->cur_tx++; + /* Trigger an immediate transmit demand. */ + outl(0, dev->base_addr + CSR1); + + dev->trans_start = jiffies; + + return 0; +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void tulip_interrupt IRQ(int irq, void *dev_instance, struct pt_regs *regs) +{ +#ifdef SA_SHIRQ /* Use the now-standard shared IRQ implementation. */ + struct device *dev = (struct device *)dev_instance; +#else + struct device *dev = (struct device *)(irq2dev_map[irq]); +#endif + + struct tulip_private *tp; + long ioaddr; + int csr5, work_budget = max_interrupt_work; + + if (dev == NULL) { + printk (KERN_ERR" tulip_interrupt(): irq %d for unknown device.\n", + irq); + return; + } + + ioaddr = dev->base_addr; + tp = (struct tulip_private *)dev->priv; + if (test_and_set_bit(0, (void*)&tp->interrupt)) { +#ifdef SMP_CHECK + printk(KERN_ERR "%s: Re-entering the interrupt handler with proc %d," + " proc %d already handling.\n", dev->name, + tp->smp_proc_id, hard_smp_processor_id()); +#else + printk(KERN_ERR "%s: Re-entering the interrupt handler.\n", dev->name); +#endif + return; + } + dev->interrupt = 1; +#ifdef SMP_CHECK + tp->smp_proc_id = hard_smp_processor_id(); +#endif + + do { + csr5 = inl(ioaddr + CSR5); + /* Acknowledge all of the current interrupt sources ASAP. */ + outl(csr5 & 0x0001ffff, ioaddr + CSR5); + + if (tulip_debug > 4) + printk(KERN_DEBUG "%s: interrupt csr5=%#8.8x new csr5=%#8.8x.\n", + dev->name, csr5, inl(dev->base_addr + CSR5)); + + if ((csr5 & (NormalIntr|AbnormalIntr)) == 0) + break; + + if (csr5 & (RxIntr | RxNoBuf)) + work_budget -= tulip_rx(dev); + + if (csr5 & (TxNoBuf | TxDied | TxIntr)) { + unsigned int dirty_tx; + + for (dirty_tx = tp->dirty_tx; tp->cur_tx - dirty_tx > 0; + dirty_tx++) { + int entry = dirty_tx % TX_RING_SIZE; + int status = tp->tx_ring[entry].status; + + if (status < 0) + break; /* It still hasn't been Txed */ + /* Check for Rx filter setup frames. */ + if (tp->tx_skbuff[entry] == NULL) + continue; + + if (status & 0x8000) { + /* There was an major error, log it. */ +#ifndef final_version + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: Transmit error, Tx status %8.8x.\n", + dev->name, status); +#endif + tp->stats.tx_errors++; + if (status & 0x4104) tp->stats.tx_aborted_errors++; + if (status & 0x0C00) tp->stats.tx_carrier_errors++; + if (status & 0x0200) tp->stats.tx_window_errors++; + if (status & 0x0002) tp->stats.tx_fifo_errors++; + if ((status & 0x0080) && tp->full_duplex == 0) + tp->stats.tx_heartbeat_errors++; +#ifdef ETHER_STATS + if (status & 0x0100) tp->stats.collisions16++; +#endif + } else { +#ifdef ETHER_STATS + if (status & 0x0001) tp->stats.tx_deferred++; +#endif +#if LINUX_VERSION_CODE > 0x20127 + tp->stats.tx_bytes += tp->tx_ring[entry].length & 0x7ff; +#endif + tp->stats.collisions += (status >> 3) & 15; + tp->stats.tx_packets++; + } + + /* Free the original skb. */ +#if (LINUX_VERSION_CODE > 0x20155) + dev_kfree_skb(tp->tx_skbuff[entry]); +#else + dev_kfree_skb(tp->tx_skbuff[entry], FREE_WRITE); +#endif + tp->tx_skbuff[entry] = 0; + } + +#ifndef final_version + if (tp->cur_tx - dirty_tx > TX_RING_SIZE) { + printk(KERN_ERR "%s: Out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dev->name, dirty_tx, tp->cur_tx, tp->tx_full); + dirty_tx += TX_RING_SIZE; + } +#endif + + if (tp->tx_full && dev->tbusy + && tp->cur_tx - dirty_tx < TX_RING_SIZE - 2) { + /* The ring is no longer full, clear tbusy. */ + tp->tx_full = 0; + dev->tbusy = 0; + mark_bh(NET_BH); + } + + tp->dirty_tx = dirty_tx; + if (csr5 & TxDied) { + if (tulip_debug > 1) + printk(KERN_WARNING "%s: The transmitter stopped!" + " CSR5 is %x, CSR6 %x.\n", + dev->name, csr5, inl(ioaddr + CSR6)); + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + } + } + + /* Log errors. */ + if (csr5 & AbnormalIntr) { /* Abnormal error summary bit. */ + if (csr5 & TxJabber) tp->stats.tx_errors++; + if (csr5 & TxFIFOUnderflow) { + if ((tp->csr6 & 0xC000) != 0xC000) + tp->csr6 += 0x4000; /* Bump up the Tx threshold */ + else + tp->csr6 |= 0x00200000; /* Store-n-forward. */ + /* Restart the transmit process. */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + } + if (csr5 & RxDied) { /* Missed a Rx frame. */ + tp->stats.rx_errors++; + tp->stats.rx_missed_errors += inl(ioaddr + CSR8) & 0xffff; + } + if (csr5 & TimerInt) { + printk(KERN_ERR "%s: Something Wicked happened! %8.8x.\n", + dev->name, csr5); + /* Hmmmmm, it's not clear what to do here. */ + } + if (csr5 & (TPLnkPass | TPLnkFail | 0x08000000) + && tp->chip_id == DC21142) { + if (tulip_debug > 1) + printk(KERN_INFO"%s: 21142 link change, CSR5 = %8.8x.\n", + dev->name, csr5); + t21142_lnk_change(dev); + } + /* Clear all error sources, included undocumented ones! */ + outl(0x0800f7ba, ioaddr + CSR5); + } + if (--work_budget < 0) { + if (tulip_debug > 1) + printk(KERN_WARNING "%s: Too much work at interrupt, " + "csr5=0x%8.8x.\n", dev->name, csr5); + /* Acknowledge all interrupt sources. */ + outl(0x8001ffff, ioaddr + CSR5); +#ifdef notdef + /* Clear all but standard interrupt sources. */ + outl((~csr5) & 0x0001ebef, ioaddr + CSR7); +#endif + break; + } + } while (1); + + if (tulip_debug > 3) + printk(KERN_DEBUG "%s: exiting interrupt, csr5=%#4.4x.\n", + dev->name, inl(ioaddr + CSR5)); + + dev->interrupt = 0; + clear_bit(0, (void*)&tp->interrupt); + return; +} + +static int +tulip_rx(struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int entry = tp->cur_rx % RX_RING_SIZE; + int rx_work_limit = tp->dirty_rx + RX_RING_SIZE - tp->cur_rx; + int work_done = 0; + + if (tulip_debug > 4) + printk(KERN_DEBUG " In tulip_rx(), entry %d %8.8x.\n", entry, + tp->rx_ring[entry].status); + /* If we own the next entry, it's a new packet. Send it up. */ + while (tp->rx_ring[entry].status >= 0) { + s32 status = tp->rx_ring[entry].status; + + if (--rx_work_limit < 0) + break; + if ((status & 0x0300) != 0x0300) { + if ((status & 0xffff) != 0x7fff) { /* Ingore earlier buffers. */ + if (tulip_debug > 1) + printk(KERN_WARNING "%s: Oversized Ethernet frame spanned " + "multiple buffers, status %8.8x!\n", + dev->name, status); + tp->stats.rx_length_errors++; + } + } else if (status & 0x8000) { + /* There was a fatal error. */ + if (tulip_debug > 2) + printk(KERN_DEBUG "%s: Receive error, Rx status %8.8x.\n", + dev->name, status); + tp->stats.rx_errors++; /* end of a packet.*/ + if (status & 0x0890) tp->stats.rx_length_errors++; + if (status & 0x0004) tp->stats.rx_frame_errors++; + if (status & 0x0002) tp->stats.rx_crc_errors++; + if (status & 0x0001) tp->stats.rx_fifo_errors++; + } else { + /* Omit the four octet CRC from the length. */ + short pkt_len = ((status >> 16) & 0x7FF) - 4; + struct sk_buff *skb; + + /* Check if the packet is long enough to just accept without + copying to a properly sized skbuff. */ + if (pkt_len < rx_copybreak + && (skb = DEV_ALLOC_SKB(pkt_len+2)) != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); /* 16 byte align the IP header */ +#if LINUX_VERSION_CODE < 0x10300 + memcpy(skb->data, tp->rx_ring[entry].buffer1, pkt_len); +#elif LINUX_VERSION_CODE < 0x20200 || defined(__alpha__) + memcpy(skb_put(skb, pkt_len), + bus_to_virt(tp->rx_ring[entry].buffer1), pkt_len); +#else +#warning Code untested + eth_copy_and_sum(skb, bus_to_virt(tp->rx_ring[entry].buffer1), + pkt_len, 0); + skb_put(skb, pkt_len); +#endif + work_done++; + } else { /* Pass up the skb already on the Rx ring. */ + skb = tp->rx_skbuff[entry]; + tp->rx_skbuff[entry] = NULL; +#ifndef final_version + { + void *temp = skb_put(skb, pkt_len); + if (bus_to_virt(tp->rx_ring[entry].buffer1) != temp) + printk(KERN_ERR "%s: Internal consistency error! The " + "skbuff addresses do not match in tulip_rx:" + " %p vs. %p / %p.\n", dev->name, + bus_to_virt(tp->rx_ring[entry].buffer1), + skb->head, temp); + } +#else + skb_put(skb, pkt_len); +#endif + } +#if LINUX_VERSION_CODE > 0x10300 + skb->protocol = eth_type_trans(skb, dev); +#else + skb->len = pkt_len; +#endif + netif_rx(skb); + dev->last_rx = jiffies; + tp->stats.rx_packets++; +#if LINUX_VERSION_CODE > 0x20127 + tp->stats.rx_bytes += pkt_len; +#endif + } + entry = (++tp->cur_rx) % RX_RING_SIZE; + } + + /* Refill the Rx ring buffers. */ + for (; tp->cur_rx - tp->dirty_rx > 0; tp->dirty_rx++) { + entry = tp->dirty_rx % RX_RING_SIZE; + if (tp->rx_skbuff[entry] == NULL) { + struct sk_buff *skb; + skb = tp->rx_skbuff[entry] = DEV_ALLOC_SKB(PKT_BUF_SZ); + if (skb == NULL) + break; + skb->dev = dev; /* Mark as being used by this device. */ +#if LINUX_VERSION_CODE > 0x10300 + tp->rx_ring[entry].buffer1 = virt_to_bus(skb->tail); +#else + tp->rx_ring[entry].buffer1 = virt_to_bus(skb->data); +#endif + work_done++; + } + tp->rx_ring[entry].status = 0x80000000; + } + + return work_done; +} + +static int +tulip_close(struct device *dev) +{ + long ioaddr = dev->base_addr; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (tulip_debug > 1) + printk(KERN_DEBUG "%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, inl(ioaddr + CSR5)); + + /* Disable interrupts by clearing the interrupt mask. */ + outl(0x00000000, ioaddr + CSR7); + /* Stop the chip's Tx and Rx processes. */ + outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6); + /* 21040 -- Leave the card in 10baseT state. */ + if (tp->chip_id == DC21040) + outl(0x00000004, ioaddr + CSR13); + + tp->stats.rx_missed_errors += inl(ioaddr + CSR8) & 0xffff; + + del_timer(&tp->timer); + +#ifdef SA_SHIRQ + free_irq(dev->irq, dev); +#else + free_irq(dev->irq); + irq2dev_map[dev->irq] = 0; +#endif + + /* Free all the skbuffs in the Rx queue. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = tp->rx_skbuff[i]; + tp->rx_skbuff[i] = 0; + tp->rx_ring[i].status = 0; /* Not owned by Tulip chip. */ + tp->rx_ring[i].length = 0; + tp->rx_ring[i].buffer1 = 0xBADF00D0; /* An invalid address. */ + if (skb) { +#if LINUX_VERSION_CODE < 0x20100 + skb->free = 1; +#endif +#if (LINUX_VERSION_CODE > 0x20155) + dev_kfree_skb(skb); +#else + dev_kfree_skb(skb, FREE_WRITE); +#endif + } + } + for (i = 0; i < TX_RING_SIZE; i++) { + if (tp->tx_skbuff[i]) +#if (LINUX_VERSION_CODE > 0x20155) + dev_kfree_skb(tp->tx_skbuff[i]); +#else + dev_kfree_skb(tp->tx_skbuff[i], FREE_WRITE); +#endif + tp->tx_skbuff[i] = 0; + } + + + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics * +tulip_get_stats(struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + + if (dev->start) + tp->stats.rx_missed_errors += inl(ioaddr + CSR8) & 0xffff; + + return &tp->stats; +} + +#ifdef HAVE_PRIVATE_IOCTL +/* Provide ioctl() calls to examine the MII xcvr state. */ +static int private_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + long ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_data; + int phy = tp->phys[0] & 0x1f; + long flags; + + switch(cmd) { + case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */ + if (tp->mtable && tp->mtable->has_mii) + data[0] = phy; + else if (tp->chip_id == DC21142) + data[0] = 32; + else + return -ENODEV; + return 0; + case SIOCDEVPRIVATE+1: /* Read the specified MII register. */ + if (data[0] == 32) { /* 21142 pseudo-MII */ + int csr12 = inl(ioaddr + CSR12); + int csr14 = inl(ioaddr + CSR14); + switch (data[1]) { + case 0: { + data[3] = ((csr14<<13)&0x4000) + ((csr14<<5)&0x1000); + break; } + case 1: + data[3] = 0x7848 + ((csr12&0x7000) == 0x5000 ? 0x20 : 0) + + (csr12&0x06 ? 0x04 : 0); + break; + case 4: { + int csr14 = inl(ioaddr + CSR14); + data[3] = ((csr14>>9)&0x0380) + ((csr14>>1)&0x20) + 1; + break; + } + case 5: data[3] = inl(ioaddr + CSR12) >> 16; break; + default: data[3] = 0; break; + } + } else { + save_flags(flags); + cli(); + data[3] = mdio_read(dev, data[0] & 0x1f, data[1] & 0x1f); + restore_flags(flags); + } + return 0; + case SIOCDEVPRIVATE+2: /* Write the specified MII register */ + if (!suser()) + return -EPERM; + if (data[0] == 32) { /* 21142 pseudo-MII */ + } else { + save_flags(flags); + cli(); + mdio_write(dev, data[0] & 0x1f, data[1] & 0x1f, data[2]); + restore_flags(flags); + } + return 0; + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} +#endif /* HAVE_PRIVATE_IOCTL */ + +/* Set or clear the multicast filter for this adaptor. + Note that we only use exclusion around actually queueing the + new frame, not around filling tp->setup_frame. This is non-deterministic + when re-entered but still correct. */ + +/* The little-endian AUTODIN32 ethernet CRC calculation. + N.B. Do not use for bulk data, use a table-based routine instead. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial_le = 0xedb88320U; +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + +#ifdef NEW_MULTICAST +static void set_rx_mode(struct device *dev) +#else +static void set_rx_mode(struct device *dev, int num_addrs, void *addrs) +#endif +{ + long ioaddr = dev->base_addr; + int csr6 = inl(ioaddr + CSR6) & ~0x00D5; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + + tp->csr6 &= ~0x00D5; + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + outl(csr6 | 0x00C0, ioaddr + CSR6); + /* Unconditionally log net taps. */ + printk(KERN_INFO "%s: Promiscuous mode enabled.\n", dev->name); + tp->csr6 |= 0xC0; + } else if ((dev->mc_count > 1000) || (dev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + outl(csr6 | 0x0080, ioaddr + CSR6); + tp->csr6 |= 0x80; + } else { + u32 *setup_frm = tp->setup_frame; + struct dev_mc_list *mclist; + u16 *eaddrs; + u32 tx_flags; + int i; + + if (dev->mc_count > 14) { /* Must use a multicast hash table. */ + u16 hash_table[32]; + memset(hash_table, 0, sizeof(hash_table)); + /* This should work on big-endian machines as well. */ + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + set_bit(ether_crc_le(ETH_ALEN, mclist->dmi_addr) & 0x1ff, + hash_table); + /* Copy the hash table to the setup frame. + NOTE that only the LOW SHORTWORD of setup_frame[] is valid! */ + for (i = 0; i < 32; i++) + *setup_frm++ = hash_table[i]; + setup_frm += 7; + tx_flags = 0x08400000 | 192; + /* Too clever: i > 15 for fall-though. */ + } else { + /* We have <= 15 addresses so we can use the wonderful + 16 address perfect filtering of the Tulip. */ + for (i = 0, mclist = dev->mc_list; i < dev->mc_count; + i++, mclist = mclist->next) { + /* Note that only the low shortword of setup_frame[] is valid! + This code may require tweaking for non-x86 architectures! */ + eaddrs = (u16 *)mclist->dmi_addr; + *setup_frm++ = *eaddrs++; + *setup_frm++ = *eaddrs++; + *setup_frm++ = *eaddrs++; + } + /* Fill the rest of the table with our physical address. + Once again, only the low shortword or setup_frame[] is valid! */ + *setup_frm++ = 0xffff; + *setup_frm++ = 0xffff; + *setup_frm++ = 0xffff; + tx_flags = 0x08000000 | 192; + } + eaddrs = (u16 *)dev->dev_addr; + do { + *setup_frm++ = eaddrs[0]; + *setup_frm++ = eaddrs[1]; + *setup_frm++ = eaddrs[2]; + } while (++i < 15); + /* Now add this frame to the Tx list. */ + if (tp->cur_tx - tp->dirty_tx > TX_RING_SIZE - 2) { + /* Same setup recently queued, we need not add it. */ + } else { + unsigned long flags; + unsigned int entry; + + save_flags(flags); cli(); + entry = tp->cur_tx++ % TX_RING_SIZE; + + if (entry != 0) { + /* Avoid a chip errata by prefixing a dummy entry. */ + tp->tx_skbuff[entry] = 0; + tp->tx_ring[entry].length = + (entry == TX_RING_SIZE-1) ? 0x02000000 : 0; + tp->tx_ring[entry].buffer1 = 0; + tp->tx_ring[entry].status = 0x80000000; + entry = tp->cur_tx++ % TX_RING_SIZE; + } + + tp->tx_skbuff[entry] = 0; + /* Put the setup frame on the Tx list. */ + if (entry == TX_RING_SIZE-1) + tx_flags |= 0x02000000; /* Wrap ring. */ + tp->tx_ring[entry].length = tx_flags; + tp->tx_ring[entry].buffer1 = virt_to_bus(tp->setup_frame); + tp->tx_ring[entry].status = 0x80000000; + if (tp->cur_tx - tp->dirty_tx >= TX_RING_SIZE - 2) { + dev->tbusy = 1; + tp->tx_full = 1; + } + restore_flags(flags); + /* Trigger an immediate transmit demand. */ + outl(0, ioaddr + CSR1); + } + outl(csr6 | 0x0000, ioaddr + CSR6); + } +} + +#ifdef CARDBUS + +#include <pcmcia/driver_ops.h> + +static dev_node_t *tulip_attach(dev_locator_t *loc) +{ + u16 dev_id; + u32 io; + u8 bus, devfn; + struct device *dev; + + if (loc->bus != LOC_PCI) return NULL; + bus = loc->b.pci.bus; devfn = loc->b.pci.devfn; + printk(KERN_INFO "tulip_attach(bus %d, function %d)\n", bus, devfn); + pcibios_read_config_dword(bus, devfn, PCI_BASE_ADDRESS_0, &io); + pcibios_read_config_word(bus, devfn, PCI_DEVICE_ID, &dev_id); + io &= ~3; + dev = tulip_probe1(bus, devfn, NULL, DC21142, -1); + if (dev) { + dev_node_t *node = kmalloc(sizeof(dev_node_t), GFP_KERNEL); + strcpy(node->dev_name, dev->name); + node->major = node->minor = 0; + node->next = NULL; + MOD_INC_USE_COUNT; + return node; + } + return NULL; +} + +static void tulip_detach(dev_node_t *node) +{ + struct device **devp, **next; + printk(KERN_INFO "tulip_detach(%s)\n", node->dev_name); + for (devp = &root_tulip_dev; *devp; devp = next) { + next = &((struct tulip_private *)(*devp)->priv)->next_module; + if (strcmp((*devp)->name, node->dev_name) == 0) break; + } + if (*devp) { + unregister_netdev(*devp); + kfree(*devp); + *devp = *next; + kfree(node); + MOD_DEC_USE_COUNT; + } +} + +struct driver_operations tulip_ops = { + "tulip_cb", tulip_attach, NULL, NULL, tulip_detach +}; + +#endif /* Cardbus support */ + + +#ifdef MODULE +#if LINUX_VERSION_CODE > 0x20118 +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("Digital 21*4* Tulip ethernet driver"); +MODULE_PARM(debug, "i"); +MODULE_PARM(max_interrupt_work, "i"); +MODULE_PARM(reverse_probe, "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(MAX_UNITS) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(MAX_UNITS) "i"); +#endif + +/* An additional parameter that may be passed in... */ +static int debug = -1; + +int +init_module(void) +{ + if (debug >= 0) + tulip_debug = debug; + +#ifdef CARDBUS + register_driver(&tulip_ops); + return 0; +#else + return tulip_probe(NULL); +#endif +} + +void +cleanup_module(void) +{ + struct device *next_dev; + +#ifdef CARDBUS + unregister_driver(&tulip_ops); +#endif + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_tulip_dev) { + next_dev = ((struct tulip_private *)root_tulip_dev->priv)->next_module; + unregister_netdev(root_tulip_dev); + release_region(root_tulip_dev->base_addr, TULIP_TOTAL_SIZE); + kfree(root_tulip_dev); + root_tulip_dev = next_dev; + } +} + +#endif /* MODULE */ + +/* + * Local variables: + * SMP-compile-command: "gcc -D__SMP__ -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c tulip.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c tulip.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/via-rhine.c b/linux/src/drivers/net/via-rhine.c new file mode 100644 index 00000000..5dc9fe22 --- /dev/null +++ b/linux/src/drivers/net/via-rhine.c @@ -0,0 +1,1317 @@ +/* via-rhine.c: A Linux Ethernet device driver for VIA Rhine family chips. */ +/* + Written 1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License (GPL), incorporated herein by reference. + Drivers derived from this code also fall under the GPL and must retain + this authorship and copyright notice. + + This driver is designed for the VIA VT86c100A Rhine-II PCI Fast Ethernet + controller. It also works with the older 3043 Rhine-I chip. + + The author may be reached as becker@cesdis.edu, or + Donald Becker + 312 Severn Ave. #W302 + Annapolis MD 21403 + + Support and updates available at + http://cesdis.gsfc.nasa.gov/linux/drivers/via-rhine.html +*/ + +static const char *versionA = +"via-rhine.c:v1.00 9/5/98 Written by Donald Becker\n"; +static const char *versionB = +" http://cesdis.gsfc.nasa.gov/linux/drivers/via-rhine.html\n"; + +/* A few user-configurable values. These may be modified when a driver + module is loaded.*/ + +static int debug = 1; /* 1 normal messages, 0 quiet .. 7 verbose. */ +static int max_interrupt_work = 20; +static int min_pci_latency = 64; + +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + Setting to > 1518 effectively disables this feature. */ +static int rx_copybreak = 0; + +/* Used to pass the media type, etc. + Both 'options[]' and 'full_duplex[]' should exist for driver + interoperability. + The media type is usually passed in 'options[]'. +*/ +#define MAX_UNITS 8 /* More are supported, limit only on options */ +static int options[MAX_UNITS] = {-1, -1, -1, -1, -1, -1, -1, -1}; +static int full_duplex[MAX_UNITS] = {-1, -1, -1, -1, -1, -1, -1, -1}; + +/* Maximum number of multicast addresses to filter (vs. rx-all-multicast). + The Rhine has a 64 element 8390-like hash table. */ +static const int multicast_filter_limit = 32; + +/* Operational parameters that are set at compile time. */ + +/* Keep the ring sizes a power of two for compile efficiency. + The compiler will convert <unsigned>'%'<2^N> into a bit mask. + Making the Tx ring too large decreases the effectiveness of channel + bonding and packet priority. + There are no ill effects from too-large receive rings. */ +#define TX_RING_SIZE 8 +#define RX_RING_SIZE 16 + +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT (2*HZ) + +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +/* Include files, designed to support most kernel versions 2.0.0 and later. */ +#include <linux/config.h> +#include <linux/version.h> +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/bitops.h> +#include <asm/io.h> + +/* This driver was written to use PCI memory space, however some boards + only work with I/O space accesses. */ +#define VIA_USE_IO +#ifdef VIA_USE_IO +#undef readb +#undef readw +#undef readl +#undef writeb +#undef writew +#undef writel +#define readb inb +#define readw inw +#define readl inl +#define writeb outb +#define writew outw +#define writel outl +#endif + +/* Kernel compatibility defines, some common to David Hind's PCMCIA package. + This is only in the support-all-kernels source code. */ + +#define RUN_AT(x) (jiffies + (x)) + +#if (LINUX_VERSION_CODE >= 0x20100) +char kernel_version[] = UTS_RELEASE; +#else +#ifndef __alpha__ +#define ioremap vremap +#define iounmap vfree +#endif +#endif +#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115 +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("VIA Rhine PCI Fast Ethernet driver"); +MODULE_PARM(max_interrupt_work, "i"); +MODULE_PARM(min_pci_latency, "i"); +MODULE_PARM(debug, "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(MAX_UNITS) "i"); +MODULE_PARM(full_duplex, "1-" __MODULE_STRING(MAX_UNITS) "i"); +#endif +#if LINUX_VERSION_CODE < 0x20123 +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif +#if LINUX_VERSION_CODE <= 0x20139 +#define net_device_stats enet_statistics +#else +#define NETSTATS_VER2 +#endif +#if LINUX_VERSION_CODE < 0x20155 || defined(CARDBUS) +/* Grrrr, the PCI code changed, but did not consider CardBus... */ +#include <linux/bios32.h> +#define PCI_SUPPORT_VER1 +#else +#define PCI_SUPPORT_VER2 +#endif +#if LINUX_VERSION_CODE < 0x20159 +#define dev_free_skb(skb) dev_kfree_skb(skb, FREE_WRITE); +#else +#define dev_free_skb(skb) dev_kfree_skb(skb); +#endif + + +/* + Theory of Operation + +I. Board Compatibility + +This driver is designed for the VIA 86c100A Rhine-II PCI Fast Ethernet +controller. + +II. Board-specific settings + +Boards with this chip are functional only in a bus-master PCI slot. + +Many operational settings are loaded from the EEPROM to the Config word at +offset 0x78. This driver assumes that they are correct. +If this driver is compiled to use PCI memory space operations the EEPROM +must be configured to enable memory ops. + +III. Driver operation + +IIIa. Ring buffers + +This driver uses two statically allocated fixed-size descriptor lists +formed into rings by a branch from the final descriptor to the beginning of +the list. The ring sizes are set at compile time by RX/TX_RING_SIZE. + +IIIb/c. Transmit/Receive Structure + +This driver attempts to use a zero-copy receive and transmit scheme. + +Alas, all data buffers are required to start on a 32 bit boundary, so +the driver must often copy transmit packets into bounce buffers. + +The driver allocates full frame size skbuffs for the Rx ring buffers at +open() time and passes the skb->data field to the chip as receive data +buffers. When an incoming frame is less than RX_COPYBREAK bytes long, +a fresh skbuff is allocated and the frame is copied to the new skbuff. +When the incoming frame is larger, the skbuff is passed directly up the +protocol stack. Buffers consumed this way are replaced by newly allocated +skbuffs in the last phase of netdev_rx(). + +The RX_COPYBREAK value is chosen to trade-off the memory wasted by +using a full-sized skbuff for small frames vs. the copying costs of larger +frames. New boards are typically used in generously configured machines +and the underfilled buffers have negligible impact compared to the benefit of +a single allocation size, so the default value of zero results in never +copying packets. When copying is done, the cost is usually mitigated by using +a combined copy/checksum routine. Copying also preloads the cache, which is +most useful with small frames. + +Since the VIA chips are only able to transfer data to buffers on 32 bit +boundaries, the the IP header at offset 14 in an ethernet frame isn't +longword aligned for further processing. Copying these unaligned buffers +has the beneficial effect of 16-byte aligning the IP header. + +IIId. Synchronization + +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and interrupt handling software. + +The send packet thread has partial control over the Tx ring and 'dev->tbusy' +flag. It sets the tbusy flag whenever it's queuing a Tx packet. If the next +queue slot is empty, it clears the tbusy flag when finished otherwise it sets +the 'lp->tx_full' flag. + +The interrupt handler has exclusive control over the Rx ring and records stats +from the Tx ring. After reaping the stats, it marks the Tx queue entry as +empty by incrementing the dirty_tx mark. Iff the 'lp->tx_full' flag is set, it +clears both the tx_full and tbusy flags. + +IV. Notes + +IVb. References + +Preliminary VT86C100A manual from http://www.via.com.tw/ +http://cesdis.gsfc.nasa.gov/linux/misc/100mbps.html +http://cesdis.gsfc.nasa.gov/linux/misc/NWay.html + +IVc. Errata + +The VT86C100A manual is not reliable information. +The chip does not handle unaligned transmit or receive buffers, resulting +in significant performance degradation for bounce buffer copies on transmit +and unaligned IP headers on receive. +The chip does not pad to minimum transmit length. + +*/ + + + +/* This table drives the PCI probe routines. It's mostly boilerplate in all + of the drivers, and will likely be provided by some future kernel. + Note the matching code -- the first table entry matchs all 56** cards but + second only the 1234 card. +*/ +enum pci_flags_bit { + PCI_USES_IO=1, PCI_USES_MEM=2, PCI_USES_MASTER=4, + PCI_ADDR0=0x10<<0, PCI_ADDR1=0x10<<1, PCI_ADDR2=0x10<<2, PCI_ADDR3=0x10<<3, +}; +struct pci_id_info { + const char *name; + u16 vendor_id, device_id, device_id_mask, flags; + int io_size; + struct device *(*probe1)(int pci_bus, int pci_devfn, struct device *dev, + long ioaddr, int irq, int chip_idx, int fnd_cnt); +}; + +static struct device *via_probe1(int pci_bus, int pci_devfn, + struct device *dev, long ioaddr, int irq, + int chp_idx, int fnd_cnt); + +static struct pci_id_info pci_tbl[] = { + { "VIA VT86C100A Rhine-II", 0x1106, 0x6100, 0xffff, + PCI_USES_MEM|PCI_USES_IO|PCI_USES_MEM|PCI_USES_MASTER, 128, via_probe1}, + { "VIA VT3043 Rhine", 0x1106, 0x3043, 0xffff, + PCI_USES_IO|PCI_USES_MEM|PCI_USES_MASTER, 128, via_probe1}, + {0,}, /* 0 terminated list. */ +}; + + +/* A chip capabilities table, matching the entries in pci_tbl[] above. */ +enum chip_capability_flags {CanHaveMII=1, }; +struct chip_info { + int io_size; + int flags; +} static cap_tbl[] = { + {128, CanHaveMII, }, + {128, CanHaveMII, }, +}; + + +/* Offsets to the device registers. +*/ +enum register_offsets { + StationAddr=0x00, RxConfig=0x06, TxConfig=0x07, ChipCmd=0x08, + IntrStatus=0x0C, IntrEnable=0x0E, + MulticastFilter0=0x10, MulticastFilter1=0x14, + RxRingPtr=0x18, TxRingPtr=0x1C, + MIIPhyAddr=0x6C, MIIStatus=0x6D, PCIConfig=0x6E, + MIICmd=0x70, MIIRegAddr=0x71, MIIData=0x72, + Config=0x78, RxMissed=0x7C, RxCRCErrs=0x7E, +}; + +/* Bits in the interrupt status/mask registers. */ +enum intr_status_bits { + IntrRxDone=0x0001, IntrRxErr=0x0004, IntrRxEmpty=0x0020, + IntrTxDone=0x0002, IntrTxAbort=0x0008, IntrTxUnderrun=0x0010, + IntrPCIErr=0x0040, + IntrStatsMax=0x0080, IntrRxEarly=0x0100, IntrMIIChange=0x0200, + IntrRxOverflow=0x0400, IntrRxDropped=0x0800, IntrRxNoBuf=0x1000, + IntrTxAborted=0x2000, IntrLinkChange=0x4000, + IntrRxWakeUp=0x8000, + IntrNormalSummary=0x0003, IntrAbnormalSummary=0x8260, +}; + + +/* The Rx and Tx buffer descriptors. */ +struct rx_desc { + u16 rx_status; + u16 rx_length; + u32 desc_length; + u32 addr; + u32 next_desc; +}; +struct tx_desc { + u16 tx_status; + u16 tx_own; + u32 desc_length; + u32 addr; + u32 next_desc; +}; + +/* Bits in *_desc.status */ +enum rx_status_bits { + RxDescOwn=0x80000000, RxOK=0x8000, RxWholePkt=0x0300, RxErr=0x008F}; +enum desc_status_bits { + DescOwn=0x8000, DescEndPacket=0x4000, DescIntr=0x1000, +}; + +/* Bits in ChipCmd. */ +enum chip_cmd_bits { + CmdInit=0x0001, CmdStart=0x0002, CmdStop=0x0004, CmdRxOn=0x0008, + CmdTxOn=0x0010, CmdTxDemand=0x0020, CmdRxDemand=0x0040, + CmdEarlyRx=0x0100, CmdEarlyTx=0x0200, CmdFDuplex=0x0400, + CmdNoTxPoll=0x0800, CmdReset=0x8000, +}; + +struct netdev_private { + /* Descriptor rings first for alignment. */ + struct rx_desc rx_ring[RX_RING_SIZE]; + struct tx_desc tx_ring[TX_RING_SIZE]; + /* The addresses of receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + /* The saved address of a sent-in-place packet/buffer, for later free(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + unsigned char *tx_buf[TX_RING_SIZE]; /* Tx bounce buffers */ + unsigned char *tx_bufs; /* Tx bounce buffer region. */ + struct device *next_module; /* Link for devices of this type. */ + struct net_device_stats stats; + struct timer_list timer; /* Media monitoring timer. */ + unsigned char pci_bus, pci_devfn; + /* Frequently used values: keep some adjacent for cache effect. */ + int chip_id; + long in_interrupt; /* Word-long for SMP locks. */ + struct rx_desc *rx_head_desc; + unsigned int cur_rx, dirty_rx; /* Producer/consumer ring indices */ + unsigned int cur_tx, dirty_tx; + unsigned int rx_buf_sz; /* Based on MTU+slack. */ + u16 chip_cmd; /* Current setting for ChipCmd */ + unsigned int tx_full:1; /* The Tx queue is full. */ + /* These values are keep track of the transceiver/media in use. */ + unsigned int full_duplex:1; /* Full-duplex operation requested. */ + unsigned int duplex_lock:1; + unsigned int medialock:1; /* Do not sense media. */ + unsigned int default_port:4; /* Last dev->if_port value. */ + u8 tx_thresh, rx_thresh; + /* MII transceiver section. */ + int mii_cnt; /* MII device addresses. */ + u16 advertising; /* NWay media advertisement */ + unsigned char phys[2]; /* MII device addresses. */ +}; + +static int mdio_read(struct device *dev, int phy_id, int location); +static void mdio_write(struct device *dev, int phy_id, int location, int value); +static int netdev_open(struct device *dev); +static void check_duplex(struct device *dev); +static void netdev_timer(unsigned long data); +static void tx_timeout(struct device *dev); +static void init_ring(struct device *dev); +static int start_tx(struct sk_buff *skb, struct device *dev); +static void intr_handler(int irq, void *dev_instance, struct pt_regs *regs); +static int netdev_rx(struct device *dev); +static void netdev_error(struct device *dev, int intr_status); +static void set_rx_mode(struct device *dev); +static struct net_device_stats *get_stats(struct device *dev); +static int mii_ioctl(struct device *dev, struct ifreq *rq, int cmd); +static int netdev_close(struct device *dev); + + + +/* A list of our installed devices, for removing the driver module. */ +static struct device *root_net_dev = NULL; + +/* Ideally we would detect all network cards in slot order. That would + be best done a central PCI probe dispatch, which wouldn't work + well when dynamically adding drivers. So instead we detect just the + cards we know about in slot order. */ + +static int pci_etherdev_probe(struct device *dev, struct pci_id_info pci_tbl[]) +{ + int cards_found = 0; + int pci_index = 0; + unsigned char pci_bus, pci_device_fn; + + if ( ! pcibios_present()) + return -ENODEV; + + for (;pci_index < 0xff; pci_index++) { + u16 vendor, device, pci_command, new_command; + int chip_idx, irq; + long pciaddr; + long ioaddr; + + if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8, pci_index, + &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + break; + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + + for (chip_idx = 0; pci_tbl[chip_idx].vendor_id; chip_idx++) + if (vendor == pci_tbl[chip_idx].vendor_id + && (device & pci_tbl[chip_idx].device_id_mask) == + pci_tbl[chip_idx].device_id) + break; + if (pci_tbl[chip_idx].vendor_id == 0) /* Compiled out! */ + continue; + + { +#if defined(PCI_SUPPORT_VER2) + struct pci_dev *pdev = pci_find_slot(pci_bus, pci_device_fn); +#ifdef VIA_USE_IO + pciaddr = pdev->base_address[0]; +#else + pciaddr = pdev->base_address[1]; +#endif + irq = pdev->irq; +#else + u32 pci_memaddr; + u8 pci_irq_line; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); +#ifdef VIA_USE_IO + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_memaddr); + pciaddr = pci_memaddr; +#else + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_1, &pci_memaddr); + pciaddr = pci_memaddr; +#endif + irq = pci_irq_line; +#endif + } + + if (debug > 2) + printk(KERN_INFO "Found %s at PCI address %#lx, IRQ %d.\n", + pci_tbl[chip_idx].name, pciaddr, irq); + + if (pci_tbl[chip_idx].flags & PCI_USES_IO) { + if (check_region(pciaddr, pci_tbl[chip_idx].io_size)) + continue; + ioaddr = pciaddr & ~3; + } else if ((ioaddr = (long)ioremap(pciaddr & ~0xf, + pci_tbl[chip_idx].io_size)) == 0) { + printk(KERN_INFO "Failed to map PCI address %#lx.\n", + pciaddr); + continue; + } + + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + new_command = pci_command | (pci_tbl[chip_idx].flags & 7); + if (pci_command != new_command) { + printk(KERN_INFO " The PCI BIOS has not enabled the" + " device at %d/%d! Updating PCI command %4.4x->%4.4x.\n", + pci_bus, pci_device_fn, pci_command, new_command); + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, new_command); + } + + dev = pci_tbl[chip_idx].probe1(pci_bus, pci_device_fn, dev, ioaddr, + irq, chip_idx, cards_found); + + if (dev && (pci_tbl[chip_idx].flags & PCI_COMMAND_MASTER)) { + u8 pci_latency; + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < min_pci_latency) { + printk(KERN_INFO " PCI latency timer (CFLT) is " + "unreasonably low at %d. Setting to %d clocks.\n", + pci_latency, min_pci_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, min_pci_latency); + } + } + dev = 0; + cards_found++; + } + + return cards_found ? 0 : -ENODEV; +} + +#ifndef MODULE +int via_rhine_probe(struct device *dev) +{ + return pci_etherdev_probe(dev, pci_tbl); +} +#endif + +static struct device *via_probe1(int pci_bus, int pci_devfn, + struct device *dev, long ioaddr, int irq, + int chip_id, int card_idx) +{ + static int did_version = 0; /* Already printed version info */ + struct netdev_private *np; + int i, option = card_idx < MAX_UNITS ? options[card_idx] : 0; + + if (debug > 0 && did_version++ == 0) + printk(KERN_INFO "%s" KERN_INFO "%s", versionA, versionB); + + dev = init_etherdev(dev, 0); + + printk(KERN_INFO "%s: %s at 0x%lx, ", + dev->name, pci_tbl[chip_id].name, ioaddr); + + /* Ideally we would be read the EEPROM but access may be locked. */ + for (i = 0; i <6; i++) + dev->dev_addr[i] = readb(ioaddr + StationAddr + i); + for (i = 0; i < 5; i++) + printk("%2.2x:", dev->dev_addr[i]); + printk("%2.2x, IRQ %d.\n", dev->dev_addr[i], irq); + +#ifdef VIA_USE_IO + request_region(ioaddr, pci_tbl[chip_id].io_size, dev->name); +#endif + + /* Reset the chip to erase previous misconfiguration. */ + writew(CmdReset, ioaddr + ChipCmd); + + dev->base_addr = ioaddr; + dev->irq = irq; + + /* Make certain the descriptor lists are cache-aligned. */ + np = (void *)(((long)kmalloc(sizeof(*np), GFP_KERNEL) + 31) & ~31); + memset(np, 0, sizeof(*np)); + dev->priv = np; + + np->next_module = root_net_dev; + root_net_dev = dev; + + np->pci_bus = pci_bus; + np->pci_devfn = pci_devfn; + np->chip_id = chip_id; + + if (dev->mem_start) + option = dev->mem_start; + + /* The lower four bits are the media type. */ + if (option > 0) { + if (option & 0x200) + np->full_duplex = 1; + np->default_port = option & 15; + if (np->default_port) + np->medialock = 1; + } + if (card_idx < MAX_UNITS && full_duplex[card_idx] > 0) + np->full_duplex = 1; + + if (np->full_duplex) + np->duplex_lock = 1; + + /* The chip-specific entries in the device structure. */ + dev->open = &netdev_open; + dev->hard_start_xmit = &start_tx; + dev->stop = &netdev_close; + dev->get_stats = &get_stats; + dev->set_multicast_list = &set_rx_mode; + dev->do_ioctl = &mii_ioctl; + + if (cap_tbl[np->chip_id].flags & CanHaveMII) { + int phy, phy_idx = 0; + np->phys[0] = 1; /* Standard for this chip. */ + for (phy = 1; phy < 32 && phy_idx < 4; phy++) { + int mii_status = mdio_read(dev, phy, 1); + if (mii_status != 0xffff && mii_status != 0x0000) { + np->phys[phy_idx++] = phy; + np->advertising = mdio_read(dev, phy, 4); + printk(KERN_INFO "%s: MII PHY found at address %d, status " + "0x%4.4x advertising %4.4x Link %4.4x.\n", + dev->name, phy, mii_status, np->advertising, + mdio_read(dev, phy, 5)); + } + } + np->mii_cnt = phy_idx; + } + + return dev; +} + + +/* Read and write over the MII Management Data I/O (MDIO) interface. */ + +static int mdio_read(struct device *dev, int phy_id, int regnum) +{ + long ioaddr = dev->base_addr; + int boguscnt = 1024; + + /* Wait for a previous command to complete. */ + while ((readb(ioaddr + MIICmd) & 0x60) && --boguscnt > 0) + ; + writeb(0x00, ioaddr + MIICmd); + writeb(phy_id, ioaddr + MIIPhyAddr); + writeb(regnum, ioaddr + MIIRegAddr); + writeb(0x40, ioaddr + MIICmd); /* Trigger read */ + boguscnt = 1024; + while ((readb(ioaddr + MIICmd) & 0x40) && --boguscnt > 0) + ; + return readw(ioaddr + MIIData); +} + +static void mdio_write(struct device *dev, int phy_id, int regnum, int value) +{ + long ioaddr = dev->base_addr; + int boguscnt = 1024; + + /* Wait for a previous command to complete. */ + while ((readb(ioaddr + MIICmd) & 0x60) && --boguscnt > 0) + ; + writeb(0x00, ioaddr + MIICmd); + writeb(phy_id, ioaddr + MIIPhyAddr); + writeb(regnum, ioaddr + MIIRegAddr); + writew(value, ioaddr + MIIData); + writeb(0x20, ioaddr + MIICmd); /* Trigger write. */ + return; +} + + +static int netdev_open(struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + long ioaddr = dev->base_addr; + int i; + + /* Reset the chip. */ + writew(CmdReset, ioaddr + ChipCmd); + + if (request_irq(dev->irq, &intr_handler, SA_SHIRQ, dev->name, dev)) + return -EAGAIN; + + if (debug > 1) + printk(KERN_DEBUG "%s: netdev_open() irq %d.\n", + dev->name, dev->irq); + + MOD_INC_USE_COUNT; + + init_ring(dev); + + writel(virt_to_bus(np->rx_ring), ioaddr + RxRingPtr); + writel(virt_to_bus(np->tx_ring), ioaddr + TxRingPtr); + + for (i = 0; i < 6; i++) + writeb(dev->dev_addr[i], ioaddr + StationAddr + i); + + /* Initialize other registers. */ + writew(0x0006, ioaddr + PCIConfig); /* Tune configuration??? */ + /* Configure the FIFO thresholds. */ + writeb(0x20, ioaddr + TxConfig); /* Initial threshold 32 bytes */ + np->tx_thresh = 0x20; + np->rx_thresh = 0x60; /* Written in set_rx_mode(). */ + + if (dev->if_port == 0) + dev->if_port = np->default_port; + + dev->tbusy = 0; + dev->interrupt = 0; + np->in_interrupt = 0; + + set_rx_mode(dev); + + dev->start = 1; + + /* Enable interrupts by setting the interrupt mask. */ + writew(IntrRxDone | IntrRxErr | IntrRxEmpty| IntrRxOverflow| IntrRxDropped| + IntrTxDone | IntrTxAbort | IntrTxUnderrun | + IntrPCIErr | IntrStatsMax | IntrLinkChange | IntrMIIChange, + ioaddr + IntrEnable); + + np->chip_cmd = CmdStart|CmdTxOn|CmdRxOn|CmdNoTxPoll; + writew(np->chip_cmd, ioaddr + ChipCmd); + + check_duplex(dev); + + if (debug > 2) + printk(KERN_DEBUG "%s: Done netdev_open(), status %4.4x " + "MII status: %4.4x.\n", + dev->name, readw(ioaddr + ChipCmd), + mdio_read(dev, np->phys[0], 1)); + + /* Set the timer to check for link beat. */ + init_timer(&np->timer); + np->timer.expires = RUN_AT(1); + np->timer.data = (unsigned long)dev; + np->timer.function = &netdev_timer; /* timer handler */ + add_timer(&np->timer); + + return 0; +} + +static void check_duplex(struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + long ioaddr = dev->base_addr; + int mii_reg5 = mdio_read(dev, np->phys[0], 5); + int duplex; + + if (np->duplex_lock || mii_reg5 == 0xffff) + return; + duplex = (mii_reg5 & 0x0100) || (mii_reg5 & 0x01C0) == 0x0040; + if (np->full_duplex != duplex) { + np->full_duplex = duplex; + if (debug) + printk(KERN_INFO "%s: Setting %s-duplex based on MII #%d link" + " partner capability of %4.4x.\n", dev->name, + duplex ? "full" : "half", np->phys[0], mii_reg5); + if (duplex) + np->chip_cmd |= CmdFDuplex; + else + np->chip_cmd &= ~CmdFDuplex; + writew(np->chip_cmd, ioaddr + ChipCmd); + } +} + +static void netdev_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct netdev_private *np = (struct netdev_private *)dev->priv; + long ioaddr = dev->base_addr; + int next_tick = 10*HZ; + + if (debug > 3) { + printk(KERN_DEBUG "%s: VIA Rhine monitor tick, status %4.4x.\n", + dev->name, readw(ioaddr + IntrStatus)); + } + check_duplex(dev); + + np->timer.expires = RUN_AT(next_tick); + add_timer(&np->timer); +} + +static void tx_timeout(struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + long ioaddr = dev->base_addr; + + printk(KERN_WARNING "%s: Transmit timed out, status %4.4x, PHY status " + "%4.4x, resetting...\n", + dev->name, readw(ioaddr + IntrStatus), + mdio_read(dev, np->phys[0], 1)); + + /* Perhaps we should reinitialize the hardware here. */ + dev->if_port = 0; + /* Stop and restart the chip's Tx processes . */ + + /* Trigger an immediate transmit demand. */ + + dev->trans_start = jiffies; + np->stats.tx_errors++; + return; +} + + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void init_ring(struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + int i; + + np->tx_full = 0; + np->cur_rx = np->cur_tx = 0; + np->dirty_rx = np->dirty_tx = 0; + + np->rx_buf_sz = (dev->mtu <= 1500 ? PKT_BUF_SZ : dev->mtu + 32); + np->rx_head_desc = &np->rx_ring[0]; + + for (i = 0; i < RX_RING_SIZE; i++) { + np->rx_ring[i].rx_status = 0; + np->rx_ring[i].rx_length = 0; + np->rx_ring[i].desc_length = np->rx_buf_sz; + np->rx_ring[i].next_desc = virt_to_bus(&np->rx_ring[i+1]); + np->rx_skbuff[i] = 0; + } + /* Mark the last entry as wrapping the ring. */ + np->rx_ring[i-1].next_desc = virt_to_bus(&np->rx_ring[0]); + + /* Fill in the Rx buffers. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = dev_alloc_skb(np->rx_buf_sz); + np->rx_skbuff[i] = skb; + if (skb == NULL) + break; + skb->dev = dev; /* Mark as being used by this device. */ + np->rx_ring[i].addr = virt_to_bus(skb->tail); + np->rx_ring[i].rx_status = 0; + np->rx_ring[i].rx_length = DescOwn; + } + np->dirty_rx = (unsigned int)(i - RX_RING_SIZE); + + for (i = 0; i < TX_RING_SIZE; i++) { + np->tx_skbuff[i] = 0; + np->tx_ring[i].tx_own = 0; + np->tx_ring[i].desc_length = 0x00e08000; + np->tx_ring[i].next_desc = virt_to_bus(&np->tx_ring[i+1]); + np->tx_buf[i] = kmalloc(PKT_BUF_SZ, GFP_KERNEL); + } + np->tx_ring[i-1].next_desc = virt_to_bus(&np->tx_ring[0]); + + return; +} + +static int start_tx(struct sk_buff *skb, struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + unsigned entry; + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start < TX_TIMEOUT) + return 1; + tx_timeout(dev); + return 1; + } + + /* Caution: the write order is important here, set the field + with the "ownership" bits last. */ + + /* Calculate the next Tx descriptor entry. */ + entry = np->cur_tx % TX_RING_SIZE; + + np->tx_skbuff[entry] = skb; + + if ((long)skb->data & 3) { /* Must use alignment buffer. */ + if (np->tx_buf[entry] == NULL && + (np->tx_buf[entry] = kmalloc(PKT_BUF_SZ, GFP_KERNEL)) == NULL) + return 1; + memcpy(np->tx_buf[entry], skb->data, skb->len); + np->tx_ring[entry].addr = virt_to_bus(np->tx_buf[entry]); + } else + np->tx_ring[entry].addr = virt_to_bus(skb->data); + + np->tx_ring[entry].desc_length = 0x00E08000 | + (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN); + np->tx_ring[entry].tx_own = DescOwn; + + np->cur_tx++; + + /* Non-x86 Todo: explicitly flush cache lines here. */ + + /* Wake the potentially-idle transmit channel. */ + writew(CmdTxDemand | np->chip_cmd, dev->base_addr + ChipCmd); + + if (np->cur_tx - np->dirty_tx < TX_RING_SIZE - 1) + clear_bit(0, (void*)&dev->tbusy); /* Typical path */ + else + np->tx_full = 1; + dev->trans_start = jiffies; + + if (debug > 4) { + printk(KERN_DEBUG "%s: Transmit frame #%d queued in slot %d.\n", + dev->name, np->cur_tx, entry); + } + return 0; +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void intr_handler(int irq, void *dev_instance, struct pt_regs *rgs) +{ + struct device *dev = (struct device *)dev_instance; + struct netdev_private *np; + long ioaddr, boguscnt = max_interrupt_work; + + ioaddr = dev->base_addr; + np = (struct netdev_private *)dev->priv; +#if defined(__i386__) + /* A lock to prevent simultaneous entry bug on Intel SMP machines. */ + if (test_and_set_bit(0, (void*)&dev->interrupt)) { + printk(KERN_ERR"%s: SMP simultaneous entry of an interrupt handler.\n", + dev->name); + dev->interrupt = 0; /* Avoid halting machine. */ + return; + } +#else + if (dev->interrupt) { + printk(KERN_ERR "%s: Re-entering the interrupt handler.\n", dev->name); + return; + } + dev->interrupt = 1; +#endif + + do { + u32 intr_status = readw(ioaddr + IntrStatus); + + /* Acknowledge all of the current interrupt sources ASAP. */ + writew(intr_status & 0xffff, ioaddr + IntrStatus); + + if (debug > 4) + printk(KERN_DEBUG "%s: Interrupt, status %4.4x.\n", + dev->name, intr_status); + + if (intr_status == 0) + break; + + if (intr_status & (IntrRxDone | IntrRxErr | IntrRxDropped | + IntrRxWakeUp | IntrRxEmpty | IntrRxNoBuf)) + netdev_rx(dev); + + for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) { + int entry = np->dirty_tx % TX_RING_SIZE; + int txstatus; + if (np->tx_ring[entry].tx_own) + break; + txstatus = np->tx_ring[entry].tx_status; + if (debug > 6) + printk(KERN_DEBUG " Tx scavenge %d status %4.4x.\n", + entry, txstatus); + if (txstatus & 0x8000) { + if (debug > 1) + printk(KERN_DEBUG "%s: Transmit error, Tx status %4.4x.\n", + dev->name, txstatus); + np->stats.tx_errors++; + if (txstatus & 0x0400) np->stats.tx_carrier_errors++; + if (txstatus & 0x0200) np->stats.tx_window_errors++; + if (txstatus & 0x0100) np->stats.tx_aborted_errors++; + if (txstatus & 0x0080) np->stats.tx_heartbeat_errors++; + if (txstatus & 0x0002) np->stats.tx_fifo_errors++; +#ifdef ETHER_STATS + if (txstatus & 0x0100) np->stats.collisions16++; +#endif + /* Transmitter restarted in 'abnormal' handler. */ + } else { +#ifdef ETHER_STATS + if (txstatus & 0x0001) np->stats.tx_deferred++; +#endif + np->stats.collisions += (txstatus >> 3) & 15; +#if defined(NETSTATS_VER2) + np->stats.tx_bytes += np->tx_ring[entry].desc_length & 0x7ff; +#endif + np->stats.tx_packets++; + } + /* Free the original skb. */ + dev_free_skb(np->tx_skbuff[entry]); + np->tx_skbuff[entry] = 0; + } + if (np->tx_full && dev->tbusy + && np->cur_tx - np->dirty_tx < TX_RING_SIZE - 4) { + /* The ring is no longer full, clear tbusy. */ + np->tx_full = 0; + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + + /* Abnormal error summary/uncommon events handlers. */ + if (intr_status & (IntrPCIErr | IntrLinkChange | IntrMIIChange | + IntrStatsMax | IntrTxAbort | IntrTxUnderrun)) + netdev_error(dev, intr_status); + + if (--boguscnt < 0) { + printk(KERN_WARNING "%s: Too much work at interrupt, " + "status=0x%4.4x.\n", + dev->name, intr_status); + break; + } + } while (1); + + if (debug > 3) + printk(KERN_DEBUG "%s: exiting interrupt, status=%#4.4x.\n", + dev->name, readw(ioaddr + IntrStatus)); + +#if defined(__i386__) + clear_bit(0, (void*)&dev->interrupt); +#else + dev->interrupt = 0; +#endif + return; +} + +/* This routine is logically part of the interrupt handler, but isolated + for clarity and better register allocation. */ +static int netdev_rx(struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + int entry = np->cur_rx % RX_RING_SIZE; + int boguscnt = np->dirty_rx + RX_RING_SIZE - np->cur_rx; + + if (debug > 4) { + printk(KERN_DEBUG " In netdev_rx(), entry %d status %4.4x.\n", + entry, np->rx_head_desc->rx_length); + } + + /* If EOP is set on the next entry, it's a new packet. Send it up. */ + while ( ! (np->rx_head_desc->rx_length & DescOwn)) { + struct rx_desc *desc = np->rx_head_desc; + int data_size = desc->rx_length; + u16 desc_status = desc->rx_status; + + if (debug > 4) + printk(KERN_DEBUG " netdev_rx() status is %4.4x.\n", + desc_status); + if (--boguscnt < 0) + break; + if ( (desc_status & (RxWholePkt | RxErr)) != RxWholePkt) { + if ((desc_status & RxWholePkt) != RxWholePkt) { + printk(KERN_WARNING "%s: Oversized Ethernet frame spanned " + "multiple buffers, entry %#x length %d status %4.4x!\n", + dev->name, np->cur_rx, data_size, desc_status); + printk(KERN_WARNING "%s: Oversized Ethernet frame %p vs %p.\n", + dev->name, np->rx_head_desc, + &np->rx_ring[np->cur_rx % RX_RING_SIZE]); + np->stats.rx_length_errors++; + } else if (desc_status & RxErr) { + /* There was a error. */ + if (debug > 2) + printk(KERN_DEBUG " netdev_rx() Rx error was %8.8x.\n", + desc_status); + np->stats.rx_errors++; + if (desc_status & 0x0030) np->stats.rx_length_errors++; + if (desc_status & 0x0048) np->stats.rx_fifo_errors++; + if (desc_status & 0x0004) np->stats.rx_frame_errors++; + if (desc_status & 0x0002) np->stats.rx_crc_errors++; + } + } else { + struct sk_buff *skb; + /* Length should omit the CRC */ + u16 pkt_len = data_size - 4; + + /* Check if the packet is long enough to accept without copying + to a minimally-sized skbuff. */ + if (pkt_len < rx_copybreak + && (skb = dev_alloc_skb(pkt_len + 2)) != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); /* 16 byte align the IP header */ +#if ! defined(__alpha__) || USE_IP_COPYSUM /* Avoid misaligned on Alpha */ + eth_copy_and_sum(skb, bus_to_virt(desc->addr), + pkt_len, 0); + skb_put(skb, pkt_len); +#else + memcpy(skb_put(skb,pkt_len), bus_to_virt(desc->addr), pkt_len); +#endif + } else { + skb_put(skb = np->rx_skbuff[entry], pkt_len); + np->rx_skbuff[entry] = NULL; + } + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->last_rx = jiffies; + np->stats.rx_packets++; + } + entry = (++np->cur_rx) % RX_RING_SIZE; + np->rx_head_desc = &np->rx_ring[entry]; + } + + /* Refill the Rx ring buffers. */ + for (; np->cur_rx - np->dirty_rx > 0; np->dirty_rx++) { + struct sk_buff *skb; + entry = np->dirty_rx % RX_RING_SIZE; + if (np->rx_skbuff[entry] == NULL) { + skb = dev_alloc_skb(np->rx_buf_sz); + np->rx_skbuff[entry] = skb; + if (skb == NULL) + break; /* Better luck next round. */ + skb->dev = dev; /* Mark as being used by this device. */ + np->rx_ring[entry].addr = virt_to_bus(skb->tail); + } + np->rx_ring[entry].rx_status = 0; + np->rx_ring[entry].rx_length = DescOwn; + } + + /* Pre-emptively restart Rx engine. */ + writew(CmdRxDemand | np->chip_cmd, dev->base_addr + ChipCmd); + return 0; +} + +static void netdev_error(struct device *dev, int intr_status) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + long ioaddr = dev->base_addr; + + if (intr_status & (IntrMIIChange | IntrLinkChange)) { + if (readb(ioaddr + MIIStatus) & 0x02) + /* Link failed, restart autonegotiation. */ + mdio_write(dev, np->phys[0], 0, 0x3300); + else + check_duplex(dev); + if (debug) + printk(KERN_ERR "%s: MII status changed: Autonegotiation " + "advertising %4.4x partner %4.4x.\n", dev->name, + mdio_read(dev, np->phys[0], 4), + mdio_read(dev, np->phys[0], 5)); + } + if (intr_status & IntrStatsMax) { + np->stats.rx_crc_errors += readw(ioaddr + RxCRCErrs); + np->stats.rx_missed_errors += readw(ioaddr + RxMissed); + writel(0, RxMissed); + } + if (intr_status & IntrTxAbort) { + /* Stats counted in Tx-done handler, just restart Tx. */ + writew(CmdTxDemand | np->chip_cmd, dev->base_addr + ChipCmd); + } + if (intr_status & IntrTxUnderrun) { + if (np->tx_thresh < 0xE0) + writeb(np->tx_thresh += 0x20, ioaddr + TxConfig); + if (debug > 1) + printk(KERN_INFO "%s: Transmitter underrun, increasing Tx " + "threshold setting to %2.2x.\n", dev->name, np->tx_thresh); + } + if ((intr_status & ~(IntrLinkChange|IntrStatsMax|IntrTxAbort)) && debug) { + printk(KERN_ERR "%s: Something Wicked happened! %4.4x.\n", + dev->name, intr_status); + /* Recovery for other fault sources not known. */ + writew(CmdTxDemand | np->chip_cmd, dev->base_addr + ChipCmd); + } +} + +static struct enet_statistics *get_stats(struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + long ioaddr = dev->base_addr; + + /* Nominally we should lock this segment of code for SMP, although + the vulnerability window is very small and statistics are + non-critical. */ + np->stats.rx_crc_errors += readw(ioaddr + RxCRCErrs); + np->stats.rx_missed_errors += readw(ioaddr + RxMissed); + writel(0, RxMissed); + + return &np->stats; +} + +/* The big-endian AUTODIN II ethernet CRC calculation. + N.B. Do not use for bulk data, use a table-based routine instead. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial = 0x04c11db7U; +static inline u32 ether_crc(int length, unsigned char *data) +{ + int crc = -1; + + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 0; bit < 8; bit++, current_octet >>= 1) { + crc = (crc << 1) ^ + ((crc < 0) ^ (current_octet & 1) ? ethernet_polynomial : 0); + } + } + return crc; +} + +static void set_rx_mode(struct device *dev) +{ + struct netdev_private *np = (struct netdev_private *)dev->priv; + long ioaddr = dev->base_addr; + u32 mc_filter[2]; /* Multicast hash filter */ + u8 rx_mode; /* Note: 0x02=accept runt, 0x01=accept errs */ + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + /* Unconditionally log net taps. */ + printk(KERN_NOTICE "%s: Promiscuous mode enabled.\n", dev->name); + rx_mode = 0x1C; + } else if ((dev->mc_count > multicast_filter_limit) + || (dev->flags & IFF_ALLMULTI)) { + /* Too many to match, or accept all multicasts. */ + rx_mode = 0x0C; + } else { + struct dev_mc_list *mclist; + int i; + memset(mc_filter, 0, sizeof(mc_filter)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) { + set_bit(ether_crc(ETH_ALEN, mclist->dmi_addr) >> 26, + mc_filter); + } + writel(mc_filter[0], ioaddr + MulticastFilter0); + writel(mc_filter[1], ioaddr + MulticastFilter1); + rx_mode = 0x0C; + } + writeb(np->rx_thresh | rx_mode, ioaddr + RxConfig); +} + +static int mii_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + u16 *data = (u16 *)&rq->ifr_data; + + switch(cmd) { + case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */ + data[0] = ((struct netdev_private *)dev->priv)->phys[0] & 0x1f; + /* Fall Through */ + case SIOCDEVPRIVATE+1: /* Read the specified MII register. */ + data[3] = mdio_read(dev, data[0] & 0x1f, data[1] & 0x1f); + return 0; + case SIOCDEVPRIVATE+2: /* Write the specified MII register */ + if (!suser()) + return -EPERM; + mdio_write(dev, data[0] & 0x1f, data[1] & 0x1f, data[2]); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int netdev_close(struct device *dev) +{ + long ioaddr = dev->base_addr; + struct netdev_private *np = (struct netdev_private *)dev->priv; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (debug > 1) + printk(KERN_DEBUG "%s: Shutting down ethercard, status was %4.4x.\n", + dev->name, readw(ioaddr + ChipCmd)); + + /* Disable interrupts by clearing the interrupt mask. */ + writew(0x0000, ioaddr + IntrEnable); + + /* Stop the chip's Tx and Rx processes. */ + writew(CmdStop, ioaddr + ChipCmd); + + del_timer(&np->timer); + + free_irq(dev->irq, dev); + + /* Free all the skbuffs in the Rx queue. */ + for (i = 0; i < RX_RING_SIZE; i++) { + np->rx_ring[i].rx_length = 0; + np->rx_ring[i].addr = 0xBADF00D0; /* An invalid address. */ + if (np->rx_skbuff[i]) { +#if LINUX_VERSION_CODE < 0x20100 + np->rx_skbuff[i]->free = 1; +#endif + dev_free_skb(np->rx_skbuff[i]); + } + np->rx_skbuff[i] = 0; + } + for (i = 0; i < TX_RING_SIZE; i++) { + if (np->tx_skbuff[i]) + dev_free_skb(np->tx_skbuff[i]); + np->tx_skbuff[i] = 0; + } + + MOD_DEC_USE_COUNT; + + return 0; +} + + +#ifdef MODULE +int init_module(void) +{ + if (debug) /* Emit version even if no cards detected. */ + printk(KERN_INFO "%s" KERN_INFO "%s", versionA, versionB); +#ifdef CARDBUS + register_driver(ðerdev_ops); + return 0; +#else + return pci_etherdev_probe(NULL, pci_tbl); +#endif +} + +void cleanup_module(void) +{ + +#ifdef CARDBUS + unregister_driver(ðerdev_ops); +#endif + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_net_dev) { + struct netdev_private *np = + (struct netdev_private *)(root_net_dev->priv); + unregister_netdev(root_net_dev); +#ifdef VIA_USE_IO + release_region(root_net_dev->base_addr, pci_tbl[np->chip_id].io_size); +#else + iounmap((char *)(root_net_dev->base_addr)); +#endif + kfree(root_net_dev); + root_net_dev = np->next_module; +#if 0 + kfree(np); /* Assumption: no struct realignment. */ +#endif + } +} + +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c via-rhine.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * SMP-compile-command: "gcc -D__SMP__ -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c via-rhine.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/wavelan.c b/linux/src/drivers/net/wavelan.c new file mode 100644 index 00000000..dbe88153 --- /dev/null +++ b/linux/src/drivers/net/wavelan.c @@ -0,0 +1,4373 @@ +/* + * WaveLAN ISA driver + * + * Jean II - HPLB '96 + * + * Reorganisation and extension of the driver. + * Original copyright follows (also see the end of this file). + * See wavelan.p.h for details. + */ + +/* + * AT&T GIS (nee NCR) WaveLAN card: + * An Ethernet-like radio transceiver + * controlled by an Intel 82586 coprocessor. + */ + +#include "wavelan.p.h" /* Private header */ + +/************************* MISC SUBROUTINES **************************/ +/* + * Subroutines which won't fit in one of the following category + * (WaveLAN modem or i82586) + */ + +/*------------------------------------------------------------------*/ +/* + * Wrapper for disabling interrupts. + */ +static inline unsigned long +wv_splhi(void) +{ + unsigned long flags; + + save_flags(flags); + cli(); + + return(flags); +} + +/*------------------------------------------------------------------*/ +/* + * Wrapper for re-enabling interrupts. + */ +static inline void +wv_splx(unsigned long flags) +{ + restore_flags(flags); +} + +/*------------------------------------------------------------------*/ +/* + * Translate irq number to PSA irq parameter + */ +static u_char +wv_irq_to_psa(int irq) +{ + if(irq < 0 || irq >= NELS(irqvals)) + return 0; + + return irqvals[irq]; +} + +/*------------------------------------------------------------------*/ +/* + * Translate PSA irq parameter to irq number + */ +static int +wv_psa_to_irq(u_char irqval) +{ + int irq; + + for(irq = 0; irq < NELS(irqvals); irq++) + if(irqvals[irq] == irqval) + return irq; + + return -1; +} + +#ifdef STRUCT_CHECK +/*------------------------------------------------------------------*/ +/* + * Sanity routine to verify the sizes of the various WaveLAN interface + * structures. + */ +static char * +wv_struct_check(void) +{ +#define SC(t,s,n) if (sizeof(t) != s) return(n); + + SC(psa_t, PSA_SIZE, "psa_t"); + SC(mmw_t, MMW_SIZE, "mmw_t"); + SC(mmr_t, MMR_SIZE, "mmr_t"); + SC(ha_t, HA_SIZE, "ha_t"); + +#undef SC + + return((char *) NULL); +} /* wv_struct_check */ +#endif /* STRUCT_CHECK */ + +/********************* HOST ADAPTER SUBROUTINES *********************/ +/* + * Useful subroutines to manage the WaveLAN ISA interface + * + * One major difference with the PCMCIA hardware (except the port mapping) + * is that we have to keep the state of the Host Control Register + * because of the interrupt enable & bus size flags. + */ + +/*------------------------------------------------------------------*/ +/* + * Read from card's Host Adaptor Status Register. + */ +static inline u_short +hasr_read(u_long ioaddr) +{ + return(inw(HASR(ioaddr))); +} /* hasr_read */ + +/*------------------------------------------------------------------*/ +/* + * Write to card's Host Adapter Command Register. + */ +static inline void +hacr_write(u_long ioaddr, + u_short hacr) +{ + outw(hacr, HACR(ioaddr)); +} /* hacr_write */ + +/*------------------------------------------------------------------*/ +/* + * Write to card's Host Adapter Command Register. Include a delay for + * those times when it is needed. + */ +static inline void +hacr_write_slow(u_long ioaddr, + u_short hacr) +{ + hacr_write(ioaddr, hacr); + /* delay might only be needed sometimes */ + udelay(1000L); +} /* hacr_write_slow */ + +/*------------------------------------------------------------------*/ +/* + * Set the channel attention bit. + */ +static inline void +set_chan_attn(u_long ioaddr, + u_short hacr) +{ + hacr_write(ioaddr, hacr | HACR_CA); +} /* set_chan_attn */ + +/*------------------------------------------------------------------*/ +/* + * Reset, and then set host adaptor into default mode. + */ +static inline void +wv_hacr_reset(u_long ioaddr) +{ + hacr_write_slow(ioaddr, HACR_RESET); + hacr_write(ioaddr, HACR_DEFAULT); +} /* wv_hacr_reset */ + +/*------------------------------------------------------------------*/ +/* + * Set the i/o transfer over the ISA bus to 8 bits mode + */ +static inline void +wv_16_off(u_long ioaddr, + u_short hacr) +{ + hacr &= ~HACR_16BITS; + hacr_write(ioaddr, hacr); +} /* wv_16_off */ + +/*------------------------------------------------------------------*/ +/* + * Set the i/o transfer over the ISA bus to 8 bits mode + */ +static inline void +wv_16_on(u_long ioaddr, + u_short hacr) +{ + hacr |= HACR_16BITS; + hacr_write(ioaddr, hacr); +} /* wv_16_on */ + +/*------------------------------------------------------------------*/ +/* + * Disable interrupts on the WaveLAN hardware + */ +static inline void +wv_ints_off(device * dev) +{ + net_local * lp = (net_local *)dev->priv; + u_long ioaddr = dev->base_addr; + u_long x; + + x = wv_splhi(); + + lp->hacr &= ~HACR_INTRON; + hacr_write(ioaddr, lp->hacr); + + wv_splx(x); +} /* wv_ints_off */ + +/*------------------------------------------------------------------*/ +/* + * Enable interrupts on the WaveLAN hardware + */ +static inline void +wv_ints_on(device * dev) +{ + net_local * lp = (net_local *)dev->priv; + u_long ioaddr = dev->base_addr; + u_long x; + + x = wv_splhi(); + + lp->hacr |= HACR_INTRON; + hacr_write(ioaddr, lp->hacr); + + wv_splx(x); +} /* wv_ints_on */ + +/******************* MODEM MANAGEMENT SUBROUTINES *******************/ +/* + * Useful subroutines to manage the modem of the WaveLAN + */ + +/*------------------------------------------------------------------*/ +/* + * Read the Parameter Storage Area from the WaveLAN card's memory + */ +/* + * Read bytes from the PSA. + */ +static void +psa_read(u_long ioaddr, + u_short hacr, + int o, /* offset in PSA */ + u_char * b, /* buffer to fill */ + int n) /* size to read */ +{ + wv_16_off(ioaddr, hacr); + + while(n-- > 0) + { + outw(o, PIOR2(ioaddr)); + o++; + *b++ = inb(PIOP2(ioaddr)); + } + + wv_16_on(ioaddr, hacr); +} /* psa_read */ + +/*------------------------------------------------------------------*/ +/* + * Write the Paramter Storage Area to the WaveLAN card's memory + */ +static void +psa_write(u_long ioaddr, + u_short hacr, + int o, /* Offset in psa */ + u_char * b, /* Buffer in memory */ + int n) /* Length of buffer */ +{ + int count = 0; + + wv_16_off(ioaddr, hacr); + + while(n-- > 0) + { + outw(o, PIOR2(ioaddr)); + o++; + + outb(*b, PIOP2(ioaddr)); + b++; + + /* Wait for the memory to finish its write cycle */ + count = 0; + while((count++ < 100) && + (hasr_read(ioaddr) & HASR_PSA_BUSY)) + udelay(1000); + } + + wv_16_on(ioaddr, hacr); +} /* psa_write */ + +#ifdef PSA_CRC +/*------------------------------------------------------------------*/ +/* + * Calculate the PSA CRC (not tested yet) + * As the WaveLAN drivers don't use the CRC, I won't use it either. + * Thanks to Valster, Nico <NVALSTER@wcnd.nl.lucent.com> for the code + * NOTE: By specifying a length including the CRC position the + * returned value should be zero. (i.e. a correct checksum in the PSA) + */ +static u_short +psa_crc(u_short * psa, /* The PSA */ + int size) /* Number of short for CRC */ +{ + int byte_cnt; /* Loop on the PSA */ + u_short crc_bytes = 0; /* Data in the PSA */ + int bit_cnt; /* Loop on the bits of the short */ + + for(byte_cnt = 0; byte_cnt <= size; byte_cnt++ ) + { + crc_bytes ^= psa[byte_cnt]; /* Its an xor */ + + for(bit_cnt = 1; bit_cnt < 9; bit_cnt++ ) + { + if(crc_bytes & 0x0001) + crc_bytes = (crc_bytes >> 1) ^ 0xA001; + else + crc_bytes >>= 1 ; + } + } + + return crc_bytes; +} /* psa_crc */ +#endif /* PSA_CRC */ + +/*------------------------------------------------------------------*/ +/* + * Write 1 byte to the MMC. + */ +static inline void +mmc_out(u_long ioaddr, + u_short o, + u_char d) +{ + /* Wait for MMC to go idle */ + while(inw(HASR(ioaddr)) & HASR_MMC_BUSY) + ; + + outw((u_short) (((u_short) d << 8) | (o << 1) | 1), + MMCR(ioaddr)); +} + +/*------------------------------------------------------------------*/ +/* + * Routine to write bytes to the Modem Management Controller. + * We start by the end because it is the way it should be ! + */ +static inline void +mmc_write(u_long ioaddr, + u_char o, + u_char * b, + int n) +{ + o += n; + b += n; + + while(n-- > 0 ) + mmc_out(ioaddr, --o, *(--b)); +} /* mmc_write */ + +/*------------------------------------------------------------------*/ +/* + * Read 1 byte from the MMC. + * Optimised version for 1 byte, avoid using memory... + */ +static inline u_char +mmc_in(u_long ioaddr, + u_short o) +{ + while(inw(HASR(ioaddr)) & HASR_MMC_BUSY) + ; + outw(o << 1, MMCR(ioaddr)); + + while(inw(HASR(ioaddr)) & HASR_MMC_BUSY) + ; + return (u_char) (inw(MMCR(ioaddr)) >> 8); +} + +/*------------------------------------------------------------------*/ +/* + * Routine to read bytes from the Modem Management Controller. + * The implementation is complicated by a lack of address lines, + * which prevents decoding of the low-order bit. + * (code has just been moved in the above function) + * We start by the end because it is the way it should be ! + */ +static inline void +mmc_read(u_long ioaddr, + u_char o, + u_char * b, + int n) +{ + o += n; + b += n; + + while(n-- > 0) + *(--b) = mmc_in(ioaddr, --o); +} /* mmc_read */ + +/*------------------------------------------------------------------*/ +/* + * Get the type of encryption available... + */ +static inline int +mmc_encr(u_long ioaddr) /* i/o port of the card */ +{ + int temp; + + temp = mmc_in(ioaddr, mmroff(0, mmr_des_avail)); + if((temp != MMR_DES_AVAIL_DES) && (temp != MMR_DES_AVAIL_AES)) + return 0; + else + return temp; +} + +/*------------------------------------------------------------------*/ +/* + * Wait for the frequency EEPROM to complete a command... + * I hope this one will be optimally inlined... + */ +static inline void +fee_wait(u_long ioaddr, /* i/o port of the card */ + int delay, /* Base delay to wait for */ + int number) /* Number of time to wait */ +{ + int count = 0; /* Wait only a limited time */ + + while((count++ < number) && + (mmc_in(ioaddr, mmroff(0, mmr_fee_status)) & MMR_FEE_STATUS_BUSY)) + udelay(delay); +} + +/*------------------------------------------------------------------*/ +/* + * Read bytes from the frequency EEPROM (frequency select cards). + */ +static void +fee_read(u_long ioaddr, /* i/o port of the card */ + u_short o, /* destination offset */ + u_short * b, /* data buffer */ + int n) /* number of registers */ +{ + b += n; /* Position at the end of the area */ + + /* Write the address */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), o + n - 1); + + /* Loop on all buffer */ + while(n-- > 0) + { + /* Write the read command */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_READ); + + /* Wait until EEPROM is ready (should be quick!) */ + fee_wait(ioaddr, 10, 100); + + /* Read the value */ + *--b = ((mmc_in(ioaddr, mmroff(0, mmr_fee_data_h)) << 8) | + mmc_in(ioaddr, mmroff(0, mmr_fee_data_l))); + } +} + +#ifdef WIRELESS_EXT /* If wireless extension exist in the kernel */ + +/*------------------------------------------------------------------*/ +/* + * Write bytes from the Frequency EEPROM (frequency select cards). + * This is a bit complicated, because the frequency EEPROM has to + * be unprotected and the write enabled. + * Jean II + */ +static void +fee_write(u_long ioaddr, /* i/o port of the card */ + u_short o, /* destination offset */ + u_short * b, /* data buffer */ + int n) /* number of registers */ +{ + b += n; /* Position at the end of the area */ + +#ifdef EEPROM_IS_PROTECTED /* disabled */ +#ifdef DOESNT_SEEM_TO_WORK /* disabled */ + /* Ask to read the protected register */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_PRREAD); + + fee_wait(ioaddr, 10, 100); + + /* Read the protected register */ + printk("Protected 2 : %02X-%02X\n", + mmc_in(ioaddr, mmroff(0, mmr_fee_data_h)), + mmc_in(ioaddr, mmroff(0, mmr_fee_data_l))); +#endif /* DOESNT_SEEM_TO_WORK */ + + /* Enable protected register */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), MMW_FEE_ADDR_EN); + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_PREN); + + fee_wait(ioaddr, 10, 100); + + /* Unprotect area */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), o + n); + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_PRWRITE); +#ifdef DOESNT_SEEM_TO_WORK /* disabled */ + /* Or use : */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_PRCLEAR); +#endif /* DOESNT_SEEM_TO_WORK */ + + fee_wait(ioaddr, 10, 100); +#endif /* EEPROM_IS_PROTECTED */ + + /* Write enable */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), MMW_FEE_ADDR_EN); + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_WREN); + + fee_wait(ioaddr, 10, 100); + + /* Write the EEPROM address */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), o + n - 1); + + /* Loop on all buffer */ + while(n-- > 0) + { + /* Write the value */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_data_h), (*--b) >> 8); + mmc_out(ioaddr, mmwoff(0, mmw_fee_data_l), *b & 0xFF); + + /* Write the write command */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_WRITE); + + /* Wavelan doc says : wait at least 10 ms for EEBUSY = 0 */ + udelay(10000); + fee_wait(ioaddr, 10, 100); + } + + /* Write disable */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), MMW_FEE_ADDR_DS); + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_WDS); + + fee_wait(ioaddr, 10, 100); + +#ifdef EEPROM_IS_PROTECTED /* disabled */ + /* Reprotect EEPROM */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), 0x00); + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), MMW_FEE_CTRL_PRWRITE); + + fee_wait(ioaddr, 10, 100); +#endif /* EEPROM_IS_PROTECTED */ +} +#endif /* WIRELESS_EXT */ + +/************************ I82586 SUBROUTINES *************************/ +/* + * Usefull subroutines to manage the Ethernet controler + */ + +/*------------------------------------------------------------------*/ +/* + * Read bytes from the on-board RAM. + * Why inlining this function make it fail ??? + */ +static /*inline*/ void +obram_read(u_long ioaddr, + u_short o, + u_char * b, + int n) +{ + outw(o, PIOR1(ioaddr)); + insw(PIOP1(ioaddr), (unsigned short *) b, (n + 1) >> 1); +} + +/*------------------------------------------------------------------*/ +/* + * Write bytes to the on-board RAM. + */ +static inline void +obram_write(u_long ioaddr, + u_short o, + u_char * b, + int n) +{ + outw(o, PIOR1(ioaddr)); + outsw(PIOP1(ioaddr), (unsigned short *) b, (n + 1) >> 1); +} + +/*------------------------------------------------------------------*/ +/* + * Acknowledge the reading of the status issued by the i82586 + */ +static void +wv_ack(device * dev) +{ + net_local * lp = (net_local *)dev->priv; + u_long ioaddr = dev->base_addr; + u_short scb_cs; + int i; + + obram_read(ioaddr, scboff(OFFSET_SCB, scb_status), + (unsigned char *) &scb_cs, sizeof(scb_cs)); + scb_cs &= SCB_ST_INT; + + if(scb_cs == 0) + return; + + obram_write(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *) &scb_cs, sizeof(scb_cs)); + + set_chan_attn(ioaddr, lp->hacr); + + for(i = 1000; i > 0; i--) + { + obram_read(ioaddr, scboff(OFFSET_SCB, scb_command), (unsigned char *)&scb_cs, sizeof(scb_cs)); + if(scb_cs == 0) + break; + + udelay(10); + } + udelay(100); + +#ifdef DEBUG_CONFIG_ERROR + if(i <= 0) + printk(KERN_INFO "%s: wv_ack(): board not accepting command.\n", + dev->name); +#endif +} + +/*------------------------------------------------------------------*/ +/* + * Set channel attention bit and busy wait until command has + * completed, then acknowledge the command completion. + */ +static inline int +wv_synchronous_cmd(device * dev, + const char * str) +{ + net_local * lp = (net_local *)dev->priv; + u_long ioaddr = dev->base_addr; + u_short scb_cmd; + ach_t cb; + int i; + + scb_cmd = SCB_CMD_CUC & SCB_CMD_CUC_GO; + obram_write(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *) &scb_cmd, sizeof(scb_cmd)); + + set_chan_attn(ioaddr, lp->hacr); + + for (i = 1000; i > 0; i--) + { + obram_read(ioaddr, OFFSET_CU, (unsigned char *)&cb, sizeof(cb)); + if (cb.ac_status & AC_SFLD_C) + break; + + udelay(10); + } + udelay(100); + + if(i <= 0 || !(cb.ac_status & AC_SFLD_OK)) + { +#ifdef DEBUG_CONFIG_ERROR + printk(KERN_INFO "%s: %s failed; status = 0x%x\n", + dev->name, str, cb.ac_status); +#endif +#ifdef DEBUG_I82586_SHOW + wv_scb_show(ioaddr); +#endif + return -1; + } + + /* Ack the status */ + wv_ack(dev); + + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Configuration commands completion interrupt. + * Check if done, and if ok... + */ +static inline int +wv_config_complete(device * dev, + u_long ioaddr, + net_local * lp) +{ + unsigned short mcs_addr; + unsigned short status; + int ret; + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: ->wv_config_complete()\n", dev->name); +#endif + + mcs_addr = lp->tx_first_in_use + sizeof(ac_tx_t) + sizeof(ac_nop_t) + + sizeof(tbd_t) + sizeof(ac_cfg_t) + sizeof(ac_ias_t); + + /* Read the status of the last command (set mc list) */ + obram_read(ioaddr, acoff(mcs_addr, ac_status), (unsigned char *)&status, sizeof(status)); + + /* If not completed -> exit */ + if((status & AC_SFLD_C) == 0) + ret = 0; /* Not ready to be scrapped */ + else + { +#ifdef DEBUG_CONFIG_ERROR + unsigned short cfg_addr; + unsigned short ias_addr; + + /* Check mc_config command */ + if(status & AC_SFLD_OK != 0) + printk(KERN_INFO "wv_config_complete(): set_multicast_address failed; status = 0x%x\n", + dev->name, str, status); + + /* check ia-config command */ + ias_addr = mcs_addr - sizeof(ac_ias_t); + obram_read(ioaddr, acoff(ias_addr, ac_status), (unsigned char *)&status, sizeof(status)); + if(status & AC_SFLD_OK != 0) + printk(KERN_INFO "wv_config_complete(): set_MAC_address; status = 0x%x\n", + dev->name, str, status); + + /* Check config command */ + cfg_addr = ias_addr - sizeof(ac_cfg_t); + obram_read(ioaddr, acoff(cfg_addr, ac_status), (unsigned char *)&status, sizeof(status)); + if(status & AC_SFLD_OK != 0) + printk(KERN_INFO "wv_config_complete(): configure; status = 0x%x\n", + dev->name, str, status); +#endif /* DEBUG_CONFIG_ERROR */ + + ret = 1; /* Ready to be scrapped */ + } + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: <-wv_config_complete() - %d\n", dev->name, ret); +#endif + return ret; +} + +/*------------------------------------------------------------------*/ +/* + * Command completion interrupt. + * Reclaim as many freed tx buffers as we can. + */ +static int +wv_complete(device * dev, + u_long ioaddr, + net_local * lp) +{ + int nreaped = 0; + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: ->wv_complete()\n", dev->name); +#endif + + /* Loop on all the transmit buffers */ + while(lp->tx_first_in_use != I82586NULL) + { + unsigned short tx_status; + + /* Read the first transmit buffer */ + obram_read(ioaddr, acoff(lp->tx_first_in_use, ac_status), (unsigned char *)&tx_status, sizeof(tx_status)); + + /* Hack for reconfiguration... */ + if(tx_status == 0xFFFF) + if(!wv_config_complete(dev, ioaddr, lp)) + break; /* Not completed */ + + /* If not completed -> exit */ + if((tx_status & AC_SFLD_C) == 0) + break; + + /* We now remove this buffer */ + nreaped++; + --lp->tx_n_in_use; + +/* +if (lp->tx_n_in_use > 0) + printk("%c", "0123456789abcdefghijk"[lp->tx_n_in_use]); +*/ + + /* Was it the last one ? */ + if(lp->tx_n_in_use <= 0) + lp->tx_first_in_use = I82586NULL; + else + { + /* Next one in the chain */ + lp->tx_first_in_use += TXBLOCKZ; + if(lp->tx_first_in_use >= OFFSET_CU + NTXBLOCKS * TXBLOCKZ) + lp->tx_first_in_use -= NTXBLOCKS * TXBLOCKZ; + } + + /* Hack for reconfiguration... */ + if(tx_status == 0xFFFF) + continue; + + /* Now, check status of the finished command */ + if(tx_status & AC_SFLD_OK) + { + int ncollisions; + + lp->stats.tx_packets++; + ncollisions = tx_status & AC_SFLD_MAXCOL; + lp->stats.collisions += ncollisions; +#ifdef DEBUG_INTERRUPT_INFO + if(ncollisions > 0) + printk(KERN_DEBUG "%s: wv_complete(): tx completed after %d collisions.\n", + dev->name, ncollisions); +#endif + } + else + { + lp->stats.tx_errors++; +#ifndef IGNORE_NORMAL_XMIT_ERRS + if(tx_status & AC_SFLD_S10) + { + lp->stats.tx_carrier_errors++; +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wv_complete(): tx error: no CS.\n", + dev->name); +#endif + } +#endif /* IGNORE_NORMAL_XMIT_ERRS */ + if(tx_status & AC_SFLD_S9) + { + lp->stats.tx_carrier_errors++; +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wv_complete(): tx error: lost CTS.\n", + dev->name); +#endif + } + if(tx_status & AC_SFLD_S8) + { + lp->stats.tx_fifo_errors++; +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wv_complete(): tx error: slow DMA.\n", + dev->name); +#endif + } +#ifndef IGNORE_NORMAL_XMIT_ERRS + if(tx_status & AC_SFLD_S6) + { + lp->stats.tx_heartbeat_errors++; +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wv_complete(): tx error: heart beat.\n", + dev->name); +#endif + } + if(tx_status & AC_SFLD_S5) + { + lp->stats.tx_aborted_errors++; +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wv_complete(): tx error: too many collisions.\n", + dev->name); +#endif + } +#endif /* IGNORE_NORMAL_XMIT_ERRS */ + } + +#ifdef DEBUG_INTERRUPT_INFO + printk(KERN_DEBUG "%s: wv_complete(): tx completed, tx_status 0x%04x\n", + dev->name, tx_status); +#endif + } + +#ifdef DEBUG_INTERRUPT_INFO + if(nreaped > 1) + printk(KERN_DEBUG "%s: wv_complete(): reaped %d\n", dev->name, nreaped); +#endif + + /* + * Inform upper layers. + */ + if(lp->tx_n_in_use < NTXBLOCKS - 1) + { + dev->tbusy = 0; + mark_bh(NET_BH); + } + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: <-wv_complete()\n", dev->name); +#endif + return nreaped; +} + +/*------------------------------------------------------------------*/ +/* + * Reconfigure the i82586, or at least ask for it... + * Because wv_82586_config use a transmission buffer, we must do it + * when we are sure that there is one left, so we do it now + * or in wavelan_packet_xmit() (I can't find any better place, + * wavelan_interrupt is not an option...), so you may experience + * some delay sometime... + */ +static inline void +wv_82586_reconfig(device * dev) +{ + net_local * lp = (net_local *)dev->priv; + + /* Check if we can do it now ! */ + if(!(dev->start) || (set_bit(0, (void *)&dev->tbusy) != 0)) + { + lp->reconfig_82586 = 1; +#ifdef DEBUG_CONFIG_INFO + printk(KERN_DEBUG "%s: wv_82586_reconfig(): delayed (busy = %ld, start = %d)\n", + dev->name, dev->tbusy, dev->start); +#endif + } + else + wv_82586_config(dev); +} + +/********************* DEBUG & INFO SUBROUTINES *********************/ +/* + * This routines are used in the code to show debug informations. + * Most of the time, it dump the content of hardware structures... + */ + +#ifdef DEBUG_PSA_SHOW +/*------------------------------------------------------------------*/ +/* + * Print the formatted contents of the Parameter Storage Area. + */ +static void +wv_psa_show(psa_t * p) +{ + printk(KERN_DEBUG "##### WaveLAN psa contents: #####\n"); + printk(KERN_DEBUG "psa_io_base_addr_1: 0x%02X %02X %02X %02X\n", + p->psa_io_base_addr_1, + p->psa_io_base_addr_2, + p->psa_io_base_addr_3, + p->psa_io_base_addr_4); + printk(KERN_DEBUG "psa_rem_boot_addr_1: 0x%02X %02X %02X\n", + p->psa_rem_boot_addr_1, + p->psa_rem_boot_addr_2, + p->psa_rem_boot_addr_3); + printk(KERN_DEBUG "psa_holi_params: 0x%02x, ", p->psa_holi_params); + printk("psa_int_req_no: %d\n", p->psa_int_req_no); +#ifdef DEBUG_SHOW_UNUSED + printk(KERN_DEBUG "psa_unused0[]: %02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + p->psa_unused0[0], + p->psa_unused0[1], + p->psa_unused0[2], + p->psa_unused0[3], + p->psa_unused0[4], + p->psa_unused0[5], + p->psa_unused0[6]); +#endif /* DEBUG_SHOW_UNUSED */ + printk(KERN_DEBUG "psa_univ_mac_addr[]: %02x:%02x:%02x:%02x:%02x:%02x\n", + p->psa_univ_mac_addr[0], + p->psa_univ_mac_addr[1], + p->psa_univ_mac_addr[2], + p->psa_univ_mac_addr[3], + p->psa_univ_mac_addr[4], + p->psa_univ_mac_addr[5]); + printk(KERN_DEBUG "psa_local_mac_addr[]: %02x:%02x:%02x:%02x:%02x:%02x\n", + p->psa_local_mac_addr[0], + p->psa_local_mac_addr[1], + p->psa_local_mac_addr[2], + p->psa_local_mac_addr[3], + p->psa_local_mac_addr[4], + p->psa_local_mac_addr[5]); + printk(KERN_DEBUG "psa_univ_local_sel: %d, ", p->psa_univ_local_sel); + printk("psa_comp_number: %d, ", p->psa_comp_number); + printk("psa_thr_pre_set: 0x%02x\n", p->psa_thr_pre_set); + printk(KERN_DEBUG "psa_feature_select/decay_prm: 0x%02x, ", + p->psa_feature_select); + printk("psa_subband/decay_update_prm: %d\n", p->psa_subband); + printk(KERN_DEBUG "psa_quality_thr: 0x%02x, ", p->psa_quality_thr); + printk("psa_mod_delay: 0x%02x\n", p->psa_mod_delay); + printk(KERN_DEBUG "psa_nwid: 0x%02x%02x, ", p->psa_nwid[0], p->psa_nwid[1]); + printk("psa_nwid_select: %d\n", p->psa_nwid_select); + printk(KERN_DEBUG "psa_encryption_select: %d, ", p->psa_encryption_select); + printk("psa_encryption_key[]: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + p->psa_encryption_key[0], + p->psa_encryption_key[1], + p->psa_encryption_key[2], + p->psa_encryption_key[3], + p->psa_encryption_key[4], + p->psa_encryption_key[5], + p->psa_encryption_key[6], + p->psa_encryption_key[7]); + printk(KERN_DEBUG "psa_databus_width: %d\n", p->psa_databus_width); + printk(KERN_DEBUG "psa_call_code/auto_squelch: 0x%02x, ", + p->psa_call_code[0]); + printk("psa_call_code[]: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + p->psa_call_code[0], + p->psa_call_code[1], + p->psa_call_code[2], + p->psa_call_code[3], + p->psa_call_code[4], + p->psa_call_code[5], + p->psa_call_code[6], + p->psa_call_code[7]); +#ifdef DEBUG_SHOW_UNUSED + printk(KERN_DEBUG "psa_reserved[]: %02X:%02X:%02X:%02X\n", + p->psa_reserved[0], + p->psa_reserved[1], + p->psa_reserved[2], + p->psa_reserved[3]); +#endif /* DEBUG_SHOW_UNUSED */ + printk(KERN_DEBUG "psa_conf_status: %d, ", p->psa_conf_status); + printk("psa_crc: 0x%02x%02x, ", p->psa_crc[0], p->psa_crc[1]); + printk("psa_crc_status: 0x%02x\n", p->psa_crc_status); +} /* wv_psa_show */ +#endif /* DEBUG_PSA_SHOW */ + +#ifdef DEBUG_MMC_SHOW +/*------------------------------------------------------------------*/ +/* + * Print the formatted status of the Modem Management Controller. + * This function need to be completed... + */ +static void +wv_mmc_show(device * dev) +{ + u_long ioaddr = dev->base_addr; + net_local * lp = (net_local *)dev->priv; + mmr_t m; + + /* Basic check */ + if(hasr_read(ioaddr) & HASR_NO_CLK) + { + printk(KERN_WARNING "%s: wv_mmc_show: modem not connected\n", + dev->name); + return; + } + + /* Read the mmc */ + mmc_out(ioaddr, mmwoff(0, mmw_freeze), 1); + mmc_read(ioaddr, 0, (u_char *)&m, sizeof(m)); + mmc_out(ioaddr, mmwoff(0, mmw_freeze), 0); + +#ifdef WIRELESS_EXT /* If wireless extension exist in the kernel */ + /* Don't forget to update statistics */ + lp->wstats.discard.nwid += (m.mmr_wrong_nwid_h << 8) | m.mmr_wrong_nwid_l; +#endif /* WIRELESS_EXT */ + + printk(KERN_DEBUG "##### WaveLAN modem status registers: #####\n"); +#ifdef DEBUG_SHOW_UNUSED + printk(KERN_DEBUG "mmc_unused0[]: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + m.mmr_unused0[0], + m.mmr_unused0[1], + m.mmr_unused0[2], + m.mmr_unused0[3], + m.mmr_unused0[4], + m.mmr_unused0[5], + m.mmr_unused0[6], + m.mmr_unused0[7]); +#endif /* DEBUG_SHOW_UNUSED */ + printk(KERN_DEBUG "Encryption algorythm: %02X - Status: %02X\n", + m.mmr_des_avail, m.mmr_des_status); +#ifdef DEBUG_SHOW_UNUSED + printk(KERN_DEBUG "mmc_unused1[]: %02X:%02X:%02X:%02X:%02X\n", + m.mmr_unused1[0], + m.mmr_unused1[1], + m.mmr_unused1[2], + m.mmr_unused1[3], + m.mmr_unused1[4]); +#endif /* DEBUG_SHOW_UNUSED */ + printk(KERN_DEBUG "dce_status: 0x%x [%s%s%s%s]\n", + m.mmr_dce_status, + (m.mmr_dce_status & MMR_DCE_STATUS_RX_BUSY) ? "energy detected,":"", + (m.mmr_dce_status & MMR_DCE_STATUS_LOOPT_IND) ? + "loop test indicated," : "", + (m.mmr_dce_status & MMR_DCE_STATUS_TX_BUSY) ? "transmitter on," : "", + (m.mmr_dce_status & MMR_DCE_STATUS_JBR_EXPIRED) ? + "jabber timer expired," : ""); + printk(KERN_DEBUG "Dsp ID: %02X\n", + m.mmr_dsp_id); +#ifdef DEBUG_SHOW_UNUSED + printk(KERN_DEBUG "mmc_unused2[]: %02X:%02X\n", + m.mmr_unused2[0], + m.mmr_unused2[1]); +#endif /* DEBUG_SHOW_UNUSED */ + printk(KERN_DEBUG "# correct_nwid: %d, # wrong_nwid: %d\n", + (m.mmr_correct_nwid_h << 8) | m.mmr_correct_nwid_l, + (m.mmr_wrong_nwid_h << 8) | m.mmr_wrong_nwid_l); + printk(KERN_DEBUG "thr_pre_set: 0x%x [current signal %s]\n", + m.mmr_thr_pre_set & MMR_THR_PRE_SET, + (m.mmr_thr_pre_set & MMR_THR_PRE_SET_CUR) ? "above" : "below"); + printk(KERN_DEBUG "signal_lvl: %d [%s], ", + m.mmr_signal_lvl & MMR_SIGNAL_LVL, + (m.mmr_signal_lvl & MMR_SIGNAL_LVL_VALID) ? "new msg" : "no new msg"); + printk("silence_lvl: %d [%s], ", m.mmr_silence_lvl & MMR_SILENCE_LVL, + (m.mmr_silence_lvl & MMR_SILENCE_LVL_VALID) ? "update done" : "no new update"); + printk("sgnl_qual: 0x%x [%s]\n", + m.mmr_sgnl_qual & MMR_SGNL_QUAL, + (m.mmr_sgnl_qual & MMR_SGNL_QUAL_ANT) ? "Antenna 1" : "Antenna 0"); +#ifdef DEBUG_SHOW_UNUSED + printk(KERN_DEBUG "netw_id_l: %x\n", m.mmr_netw_id_l); +#endif /* DEBUG_SHOW_UNUSED */ +} /* wv_mmc_show */ +#endif /* DEBUG_MMC_SHOW */ + +#ifdef DEBUG_I82586_SHOW +/*------------------------------------------------------------------*/ +/* + * Print the last block of the i82586 memory + */ +static void +wv_scb_show(u_long ioaddr) +{ + scb_t scb; + + obram_read(ioaddr, OFFSET_SCB, (unsigned char *)&scb, sizeof(scb)); + + printk(KERN_DEBUG "##### WaveLAN system control block: #####\n"); + + printk(KERN_DEBUG "status: "); + printk("stat 0x%x[%s%s%s%s] ", + (scb.scb_status & (SCB_ST_CX | SCB_ST_FR | SCB_ST_CNA | SCB_ST_RNR)) >> 12, + (scb.scb_status & SCB_ST_CX) ? "cmd completion interrupt," : "", + (scb.scb_status & SCB_ST_FR) ? "frame received," : "", + (scb.scb_status & SCB_ST_CNA) ? "cmd unit not active," : "", + (scb.scb_status & SCB_ST_RNR) ? "rcv unit not ready," : ""); + printk("cus 0x%x[%s%s%s] ", + (scb.scb_status & SCB_ST_CUS) >> 8, + ((scb.scb_status & SCB_ST_CUS) == SCB_ST_CUS_IDLE) ? "idle" : "", + ((scb.scb_status & SCB_ST_CUS) == SCB_ST_CUS_SUSP) ? "suspended" : "", + ((scb.scb_status & SCB_ST_CUS) == SCB_ST_CUS_ACTV) ? "active" : ""); + printk("rus 0x%x[%s%s%s%s]\n", + (scb.scb_status & SCB_ST_RUS) >> 4, + ((scb.scb_status & SCB_ST_RUS) == SCB_ST_RUS_IDLE) ? "idle" : "", + ((scb.scb_status & SCB_ST_RUS) == SCB_ST_RUS_SUSP) ? "suspended" : "", + ((scb.scb_status & SCB_ST_RUS) == SCB_ST_RUS_NRES) ? "no resources" : "", + ((scb.scb_status & SCB_ST_RUS) == SCB_ST_RUS_RDY) ? "ready" : ""); + + printk(KERN_DEBUG "command: "); + printk("ack 0x%x[%s%s%s%s] ", + (scb.scb_command & (SCB_CMD_ACK_CX | SCB_CMD_ACK_FR | SCB_CMD_ACK_CNA | SCB_CMD_ACK_RNR)) >> 12, + (scb.scb_command & SCB_CMD_ACK_CX) ? "ack cmd completion," : "", + (scb.scb_command & SCB_CMD_ACK_FR) ? "ack frame received," : "", + (scb.scb_command & SCB_CMD_ACK_CNA) ? "ack CU not active," : "", + (scb.scb_command & SCB_CMD_ACK_RNR) ? "ack RU not ready," : ""); + printk("cuc 0x%x[%s%s%s%s%s] ", + (scb.scb_command & SCB_CMD_CUC) >> 8, + ((scb.scb_command & SCB_CMD_CUC) == SCB_CMD_CUC_NOP) ? "nop" : "", + ((scb.scb_command & SCB_CMD_CUC) == SCB_CMD_CUC_GO) ? "start cbl_offset" : "", + ((scb.scb_command & SCB_CMD_CUC) == SCB_CMD_CUC_RES) ? "resume execution" : "", + ((scb.scb_command & SCB_CMD_CUC) == SCB_CMD_CUC_SUS) ? "suspend execution" : "", + ((scb.scb_command & SCB_CMD_CUC) == SCB_CMD_CUC_ABT) ? "abort execution" : ""); + printk("ruc 0x%x[%s%s%s%s%s]\n", + (scb.scb_command & SCB_CMD_RUC) >> 4, + ((scb.scb_command & SCB_CMD_RUC) == SCB_CMD_RUC_NOP) ? "nop" : "", + ((scb.scb_command & SCB_CMD_RUC) == SCB_CMD_RUC_GO) ? "start rfa_offset" : "", + ((scb.scb_command & SCB_CMD_RUC) == SCB_CMD_RUC_RES) ? "resume reception" : "", + ((scb.scb_command & SCB_CMD_RUC) == SCB_CMD_RUC_SUS) ? "suspend reception" : "", + ((scb.scb_command & SCB_CMD_RUC) == SCB_CMD_RUC_ABT) ? "abort reception" : ""); + + printk(KERN_DEBUG "cbl_offset 0x%x ", scb.scb_cbl_offset); + printk("rfa_offset 0x%x\n", scb.scb_rfa_offset); + + printk(KERN_DEBUG "crcerrs %d ", scb.scb_crcerrs); + printk("alnerrs %d ", scb.scb_alnerrs); + printk("rscerrs %d ", scb.scb_rscerrs); + printk("ovrnerrs %d\n", scb.scb_ovrnerrs); +} + +/*------------------------------------------------------------------*/ +/* + * Print the formatted status of the i82586's receive unit. + */ +static void +wv_ru_show(device * dev) +{ + /* net_local *lp = (net_local *) dev->priv; */ + + printk(KERN_DEBUG "##### WaveLAN i82586 receiver unit status: #####\n"); + printk(KERN_DEBUG "ru:"); + /* + * Not implemented yet... + */ + printk("\n"); +} /* wv_ru_show */ + +/*------------------------------------------------------------------*/ +/* + * Display info about one control block of the i82586 memory + */ +static void +wv_cu_show_one(device * dev, + net_local * lp, + int i, + u_short p) +{ + u_long ioaddr; + ac_tx_t actx; + + ioaddr = dev->base_addr; + + printk("%d: 0x%x:", i, p); + + obram_read(ioaddr, p, (unsigned char *)&actx, sizeof(actx)); + printk(" status=0x%x,", actx.tx_h.ac_status); + printk(" command=0x%x,", actx.tx_h.ac_command); + + /* + { + tbd_t tbd; + + obram_read(ioaddr, actx.tx_tbd_offset, (unsigned char *)&tbd, sizeof(tbd)); + printk(" tbd_status=0x%x,", tbd.tbd_status); + } + */ + + printk("|"); +} + +/*------------------------------------------------------------------*/ +/* + * Print status of the command unit of the i82586 + */ +static void +wv_cu_show(device * dev) +{ + net_local * lp = (net_local *)dev->priv; + unsigned int i; + u_short p; + + printk(KERN_DEBUG "##### WaveLAN i82586 command unit status: #####\n"); + + printk(KERN_DEBUG); + for(i = 0, p = lp->tx_first_in_use; i < NTXBLOCKS; i++) + { + wv_cu_show_one(dev, lp, i, p); + + p += TXBLOCKZ; + if(p >= OFFSET_CU + NTXBLOCKS * TXBLOCKZ) + p -= NTXBLOCKS * TXBLOCKZ; + } + printk("\n"); +} +#endif /* DEBUG_I82586_SHOW */ + +#ifdef DEBUG_DEVICE_SHOW +/*------------------------------------------------------------------*/ +/* + * Print the formatted status of the WaveLAN PCMCIA device driver. + */ +static void +wv_dev_show(device * dev) +{ + printk(KERN_DEBUG "dev:"); + printk(" start=%d,", dev->start); + printk(" tbusy=%ld,", dev->tbusy); + printk(" interrupt=%d,", dev->interrupt); + printk(" trans_start=%ld,", dev->trans_start); + printk(" flags=0x%x,", dev->flags); + printk("\n"); +} /* wv_dev_show */ + +/*------------------------------------------------------------------*/ +/* + * Print the formatted status of the WaveLAN PCMCIA device driver's + * private information. + */ +static void +wv_local_show(device * dev) +{ + net_local *lp; + + lp = (net_local *)dev->priv; + + printk(KERN_DEBUG "local:"); + printk(" tx_n_in_use=%d,", lp->tx_n_in_use); + printk(" hacr=0x%x,", lp->hacr); + printk(" rx_head=0x%x,", lp->rx_head); + printk(" rx_last=0x%x,", lp->rx_last); + printk(" tx_first_free=0x%x,", lp->tx_first_free); + printk(" tx_first_in_use=0x%x,", lp->tx_first_in_use); + printk("\n"); +} /* wv_local_show */ +#endif /* DEBUG_DEVICE_SHOW */ + +#if defined(DEBUG_RX_INFO) || defined(DEBUG_TX_INFO) +/*------------------------------------------------------------------*/ +/* + * Dump packet header (and content if necessary) on the screen + */ +static inline void +wv_packet_info(u_char * p, /* Packet to dump */ + int length, /* Length of the packet */ + char * msg1, /* Name of the device */ + char * msg2) /* Name of the function */ +{ +#ifndef DEBUG_PACKET_DUMP + printk(KERN_DEBUG "%s: %s(): dest %02X:%02X:%02X:%02X:%02X:%02X, length %d\n", + msg1, msg2, p[0], p[1], p[2], p[3], p[4], p[5], length); + printk(KERN_DEBUG "%s: %s(): src %02X:%02X:%02X:%02X:%02X:%02X, type 0x%02X%02X\n", + msg1, msg2, p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13]); + +#else /* DEBUG_PACKET_DUMP */ + int i; + int maxi; + + printk(KERN_DEBUG "%s: %s(): len=%d, data=\"", msg1, msg2, length); + + if((maxi = length) > DEBUG_PACKET_DUMP) + maxi = DEBUG_PACKET_DUMP; + for(i = 0; i < maxi; i++) + if(p[i] >= ' ' && p[i] <= '~') + printk(" %c", p[i]); + else + printk("%02X", p[i]); + if(maxi < length) + printk(".."); + printk("\"\n"); + printk(KERN_DEBUG "\n"); +#endif /* DEBUG_PACKET_DUMP */ +} +#endif /* defined(DEBUG_RX_INFO) || defined(DEBUG_TX_INFO) */ + +/*------------------------------------------------------------------*/ +/* + * This is the information which is displayed by the driver at startup + * There is a lot of flag to configure it at your will... + */ +static inline void +wv_init_info(device * dev) +{ + short ioaddr = dev->base_addr; + net_local * lp = (net_local *)dev->priv; + psa_t psa; + int i; + + /* Read the parameter storage area */ + psa_read(ioaddr, lp->hacr, 0, (unsigned char *) &psa, sizeof(psa)); + +#ifdef DEBUG_PSA_SHOW + wv_psa_show(&psa); +#endif +#ifdef DEBUG_MMC_SHOW + wv_mmc_show(dev); +#endif +#ifdef DEBUG_I82586_SHOW + wv_cu_show(dev); +#endif + +#ifdef DEBUG_BASIC_SHOW + /* Now, let's go for the basic stuff */ + printk(KERN_NOTICE "%s: WaveLAN at %#x,", dev->name, ioaddr); + for(i = 0; i < WAVELAN_ADDR_SIZE; i++) + printk("%s%02X", (i == 0) ? " " : ":", dev->dev_addr[i]); + printk(", IRQ %d", dev->irq); + + /* Print current network id */ + if(psa.psa_nwid_select) + printk(", nwid 0x%02X-%02X", psa.psa_nwid[0], psa.psa_nwid[1]); + else + printk(", nwid off"); + + /* If 2.00 card */ + if(!(mmc_in(ioaddr, mmroff(0, mmr_fee_status)) & + (MMR_FEE_STATUS_DWLD | MMR_FEE_STATUS_BUSY))) + { + unsigned short freq; + + /* Ask the EEPROM to read the frequency from the first area */ + fee_read(ioaddr, 0x00 /* 1st area - frequency... */, + &freq, 1); + + /* Print frequency */ + printk(", 2.00, %ld", (freq >> 6) + 2400L); + + /* Hack !!! */ + if(freq & 0x20) + printk(".5"); + } + else + { + printk(", PC"); + switch(psa.psa_comp_number) + { + case PSA_COMP_PC_AT_915: + case PSA_COMP_PC_AT_2400: + printk("-AT"); + break; + case PSA_COMP_PC_MC_915: + case PSA_COMP_PC_MC_2400: + printk("-MC"); + break; + case PSA_COMP_PCMCIA_915: + printk("MCIA"); + break; + default: + printk("???"); + } + printk(", "); + switch (psa.psa_subband) + { + case PSA_SUBBAND_915: + printk("915"); + break; + case PSA_SUBBAND_2425: + printk("2425"); + break; + case PSA_SUBBAND_2460: + printk("2460"); + break; + case PSA_SUBBAND_2484: + printk("2484"); + break; + case PSA_SUBBAND_2430_5: + printk("2430.5"); + break; + default: + printk("???"); + } + } + + printk(" MHz\n"); +#endif /* DEBUG_BASIC_SHOW */ + +#ifdef DEBUG_VERSION_SHOW + /* Print version information */ + printk(KERN_NOTICE "%s", version); +#endif +} /* wv_init_info */ + +/********************* IOCTL, STATS & RECONFIG *********************/ +/* + * We found here routines that are called by Linux on differents + * occasions after the configuration and not for transmitting data + * These may be called when the user use ifconfig, /proc/net/dev + * or wireless extensions + */ + +/*------------------------------------------------------------------*/ +/* + * Get the current ethernet statistics. This may be called with the + * card open or closed. + * Used when the user read /proc/net/dev + */ +static en_stats * +wavelan_get_stats(device * dev) +{ +#ifdef DEBUG_IOCTL_TRACE + printk(KERN_DEBUG "%s: <>wavelan_get_stats()\n", dev->name); +#endif + + return(&((net_local *) dev->priv)->stats); +} + +/*------------------------------------------------------------------*/ +/* + * Set or clear the multicast filter for this adaptor. + * num_addrs == -1 Promiscuous mode, receive all packets + * num_addrs == 0 Normal mode, clear multicast list + * num_addrs > 0 Multicast mode, receive normal and MC packets, + * and do best-effort filtering. + */ +static void +wavelan_set_multicast_list(device * dev) +{ + net_local * lp = (net_local *) dev->priv; + +#ifdef DEBUG_IOCTL_TRACE + printk(KERN_DEBUG "%s: ->wavelan_set_multicast_list()\n", dev->name); +#endif + +#ifdef DEBUG_IOCTL_INFO + printk(KERN_DEBUG "%s: wavelan_set_multicast_list(): setting Rx mode %02X to %d addresses.\n", + dev->name, dev->flags, dev->mc_count); +#endif + + /* If we ask for promiscuous mode, + * or all multicast addresses (we don't have that !) + * or too much multicast addresses for the hardware filter */ + if((dev->flags & IFF_PROMISC) || + (dev->flags & IFF_ALLMULTI) || + (dev->mc_count > I82586_MAX_MULTICAST_ADDRESSES)) + { + /* + * Enable promiscuous mode: receive all packets. + */ + if(!lp->promiscuous) + { + lp->promiscuous = 1; + lp->mc_count = 0; + + wv_82586_reconfig(dev); + + /* Tell the kernel that we are doing a really bad job... */ + dev->flags |= IFF_PROMISC; + } + } + else + /* If there is some multicast addresses to send */ + if(dev->mc_list != (struct dev_mc_list *) NULL) + { + /* + * Disable promiscuous mode, but receive all packets + * in multicast list + */ +#ifdef MULTICAST_AVOID + if(lp->promiscuous || + (dev->mc_count != lp->mc_count)) +#endif + { + lp->promiscuous = 0; + lp->mc_count = dev->mc_count; + + wv_82586_reconfig(dev); + } + } + else + { + /* + * Switch to normal mode: disable promiscuous mode and + * clear the multicast list. + */ + if(lp->promiscuous || lp->mc_count == 0) + { + lp->promiscuous = 0; + lp->mc_count = 0; + + wv_82586_reconfig(dev); + } + } +#ifdef DEBUG_IOCTL_TRACE + printk(KERN_DEBUG "%s: <-wavelan_set_multicast_list()\n", dev->name); +#endif +} + +/*------------------------------------------------------------------*/ +/* + * This function doesn't exist... + */ +static int +wavelan_set_mac_address(device * dev, + void * addr) +{ + struct sockaddr * mac = addr; + + /* Copy the address */ + memcpy(dev->dev_addr, mac->sa_data, WAVELAN_ADDR_SIZE); + + /* Reconfig the beast */ + wv_82586_reconfig(dev); + + return 0; +} + +#ifdef WIRELESS_EXT /* If wireless extension exist in the kernel */ + +/*------------------------------------------------------------------*/ +/* + * Frequency setting (for hardware able of it) + * It's a bit complicated and you don't really want to look into it... + * (called in wavelan_ioctl) + */ +static inline int +wv_set_frequency(u_long ioaddr, /* i/o port of the card */ + iw_freq * frequency) +{ + const int BAND_NUM = 10; /* Number of bands */ + long freq = 0L; /* offset to 2.4 GHz in .5 MHz */ +#ifdef DEBUG_IOCTL_INFO + int i; +#endif + + /* Setting by frequency */ + /* Theoretically, you may set any frequency between + * the two limits with a 0.5 MHz precision. In practice, + * I don't want you to have trouble with local + * regulations... */ + if((frequency->e == 1) && + (frequency->m >= (int) 2.412e8) && (frequency->m <= (int) 2.487e8)) + { + freq = ((frequency->m / 10000) - 24000L) / 5; + } + + /* Setting by channel (same as wfreqsel) */ + /* Warning : each channel is 22MHz wide, so some of the channels + * will interfere... */ + if((frequency->e == 0) && + (frequency->m >= 0) && (frequency->m < BAND_NUM)) + { + /* frequency in 1/4 of MHz (as read in the offset register) */ + short bands[] = { 0x30, 0x58, 0x64, 0x7A, 0x80, 0xA8, 0xD0, 0xF0, 0xF8, 0x150 }; + + /* Get frequency offset */ + freq = bands[frequency->m] >> 1; + } + + /* Verify if the frequency is allowed */ + if(freq != 0L) + { + u_short table[10]; /* Authorized frequency table */ + + /* Read the frequency table */ + fee_read(ioaddr, 0x71 /* frequency table */, + table, 10); + +#ifdef DEBUG_IOCTL_INFO + printk(KERN_DEBUG "Frequency table :"); + for(i = 0; i < 10; i++) + { + printk(" %04X", + table[i]); + } + printk("\n"); +#endif + + /* Look in the table if the frequency is allowed */ + if(!(table[9 - ((freq - 24) / 16)] & + (1 << ((freq - 24) % 16)))) + return -EINVAL; /* not allowed */ + } + else + return -EINVAL; + + /* If we get a usable frequency */ + if(freq != 0L) + { + unsigned short area[16]; + unsigned short dac[2]; + unsigned short area_verify[16]; + unsigned short dac_verify[2]; + /* Corresponding gain (in the power adjust value table) + * see AT&T WaveLAN Data Manual, REF 407-024689/E, page 3-8 + * & WCIN062D.DOC, page 6.2.9 */ + unsigned short power_limit[] = { 40, 80, 120, 160, 0 }; + int power_band = 0; /* Selected band */ + unsigned short power_adjust; /* Correct value */ + + /* Search for the gain */ + power_band = 0; + while((freq > power_limit[power_band]) && + (power_limit[++power_band] != 0)) + ; + + /* Read the first area */ + fee_read(ioaddr, 0x00, + area, 16); + + /* Read the DAC */ + fee_read(ioaddr, 0x60, + dac, 2); + + /* Read the new power adjust value */ + fee_read(ioaddr, 0x6B - (power_band >> 1), + &power_adjust, 1); + if(power_band & 0x1) + power_adjust >>= 8; + else + power_adjust &= 0xFF; + +#ifdef DEBUG_IOCTL_INFO + printk(KERN_DEBUG "WaveLAN EEPROM Area 1:"); + for(i = 0; i < 16; i++) + { + printk(" %04X", + area[i]); + } + printk("\n"); + + printk(KERN_DEBUG "WaveLAN EEPROM DAC: %04X %04X\n", + dac[0], dac[1]); +#endif + + /* Frequency offset (for info only) */ + area[0] = ((freq << 5) & 0xFFE0) | (area[0] & 0x1F); + + /* Receiver Principle main divider coefficient */ + area[3] = (freq >> 1) + 2400L - 352L; + area[2] = ((freq & 0x1) << 4) | (area[2] & 0xFFEF); + + /* Transmitter Main divider coefficient */ + area[13] = (freq >> 1) + 2400L; + area[12] = ((freq & 0x1) << 4) | (area[2] & 0xFFEF); + + /* Others part of the area are flags, bit streams or unused... */ + + /* Set the value in the DAC. */ + dac[1] = ((power_adjust >> 1) & 0x7F) | (dac[1] & 0xFF80); + dac[0] = ((power_adjust & 0x1) << 4) | (dac[0] & 0xFFEF); + + /* Write the first area. */ + fee_write(ioaddr, 0x00, + area, 16); + + /* Write the DAC. */ + fee_write(ioaddr, 0x60, + dac, 2); + + /* We now should verify here that the EEPROM writing was OK. */ + + /* Reread the first area. */ + fee_read(ioaddr, 0x00, + area_verify, 16); + + /* ReRead the DAC */ + fee_read(ioaddr, 0x60, + dac_verify, 2); + + /* Compare */ + if(memcmp(area, area_verify, 16 * 2) || + memcmp(dac, dac_verify, 2 * 2)) + { +#ifdef DEBUG_IOCTL_ERROR + printk(KERN_INFO "WaveLAN: wv_set_frequency: unable to write new frequency to EEPROM(?).\n"); +#endif + return -EOPNOTSUPP; + } + + /* We must download the frequency parameters to the + * synthesizers (from the EEPROM - area 1) + * Note: as the EEPROM is automatically decremented, we set the end + * if the area... */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), 0x0F); + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), + MMW_FEE_CTRL_READ | MMW_FEE_CTRL_DWLD); + + /* Wait until the download is finished */ + fee_wait(ioaddr, 100, 100); + + /* We must now download the power adjust value (gain) to + * the synthesizers (from the EEPROM - area 7 - DAC) */ + mmc_out(ioaddr, mmwoff(0, mmw_fee_addr), 0x61); + mmc_out(ioaddr, mmwoff(0, mmw_fee_ctrl), + MMW_FEE_CTRL_READ | MMW_FEE_CTRL_DWLD); + + /* Wait until the download is finished */ + fee_wait(ioaddr, 100, 100); + +#ifdef DEBUG_IOCTL_INFO + /* Verification of what we have done... */ + + printk(KERN_DEBUG "WaveLAN EEPROM Area 1:"); + for(i = 0; i < 16; i++) + { + printk(" %04X", + area_verify[i]); + } + printk("\n"); + + printk(KERN_DEBUG "WaveLAN EEPROM DAC: %04X %04X\n", + dac_verify[0], dac_verify[1]); +#endif + + return 0; + } + else + return -EINVAL; /* Bah, never get there... */ +} + +/*------------------------------------------------------------------*/ +/* + * Give the list of available frequencies + */ +static inline int +wv_frequency_list(u_long ioaddr, /* i/o port of the card */ + iw_freq * list, /* List of frequency to fill */ + int max) /* Maximum number of frequencies */ +{ + u_short table[10]; /* Authorized frequency table */ + long freq = 0L; /* offset to 2.4 GHz in .5 MHz + 12 MHz */ + int i; /* index in the table */ + + /* Read the frequency table */ + fee_read(ioaddr, 0x71 /* frequency table */, + table, 10); + + /* Look all frequencies */ + i = 0; + for(freq = 0; freq < 150; freq++) + /* Look in the table if the frequency is allowed */ + if(table[9 - (freq / 16)] & (1 << (freq % 16))) + { + /* put in the list */ + list[i].m = (((freq + 24) * 5) + 24000L) * 10000; + list[i++].e = 1; + + /* Check number */ + if(i >= max) + return(i); + } + + return(i); +} + +#ifdef WIRELESS_SPY +/*------------------------------------------------------------------*/ +/* + * Gather wireless spy statistics : for each packet, compare the source + * address with out list, and if match, get the stats... + * Sorry, but this function really need wireless extensions... + */ +static inline void +wl_spy_gather(device * dev, + u_char * mac, /* MAC address */ + u_char * stats) /* Statistics to gather */ +{ + net_local * lp = (net_local *) dev->priv; + int i; + + /* Look all addresses */ + for(i = 0; i < lp->spy_number; i++) + /* If match */ + if(!memcmp(mac, lp->spy_address[i], WAVELAN_ADDR_SIZE)) + { + /* Update statistics */ + lp->spy_stat[i].qual = stats[2] & MMR_SGNL_QUAL; + lp->spy_stat[i].level = stats[0] & MMR_SIGNAL_LVL; + lp->spy_stat[i].noise = stats[1] & MMR_SILENCE_LVL; + lp->spy_stat[i].updated = 0x7; + } +} +#endif /* WIRELESS_SPY */ + +#ifdef HISTOGRAM +/*------------------------------------------------------------------*/ +/* + * This function calculates an histogram on the signal level. + * As the noise is quite constant, it's like doing it on the SNR. + * We have defined a set of interval (lp->his_range), and each time + * the level goes in that interval, we increment the count (lp->his_sum). + * With this histogram you may detect if one WaveLAN is really weak, + * or you may also calculate the mean and standard deviation of the level. + */ +static inline void +wl_his_gather(device * dev, + u_char * stats) /* Statistics to gather */ +{ + net_local * lp = (net_local *) dev->priv; + u_char level = stats[0] & MMR_SIGNAL_LVL; + int i; + + /* Find the correct interval */ + i = 0; + while((i < (lp->his_number - 1)) && (level >= lp->his_range[i++])) + ; + + /* Increment interval counter */ + (lp->his_sum[i])++; +} +#endif /* HISTOGRAM */ + +/*------------------------------------------------------------------*/ +/* + * Perform ioctl : config & info stuff + * This is here that are treated the wireless extensions (iwconfig) + */ +static int +wavelan_ioctl(struct device * dev, /* device on which the ioctl is applied */ + struct ifreq * rq, /* data passed */ + int cmd) /* ioctl number */ +{ + u_long ioaddr = dev->base_addr; + net_local * lp = (net_local *)dev->priv; /* lp is not unused */ + struct iwreq * wrq = (struct iwreq *) rq; + psa_t psa; + mm_t m; + unsigned long x; + int ret = 0; + +#ifdef DEBUG_IOCTL_TRACE + printk(KERN_DEBUG "%s: ->wavelan_ioctl(cmd=0x%X)\n", dev->name, cmd); +#endif + + /* Disable interrupts & save flags */ + x = wv_splhi(); + + /* Look what is the request */ + switch(cmd) + { + /* --------------- WIRELESS EXTENSIONS --------------- */ + + case SIOCGIWNAME: + strcpy(wrq->u.name, "Wavelan"); + break; + + case SIOCSIWNWID: + /* Set NWID in WaveLAN */ + if(wrq->u.nwid.on) + { + /* Set NWID in psa */ + psa.psa_nwid[0] = (wrq->u.nwid.nwid & 0xFF00) >> 8; + psa.psa_nwid[1] = wrq->u.nwid.nwid & 0xFF; + psa.psa_nwid_select = 0x01; + psa_write(ioaddr, lp->hacr, (char *)psa.psa_nwid - (char *)&psa, + (unsigned char *)psa.psa_nwid, 3); + + /* Set NWID in mmc */ + m.w.mmw_netw_id_l = wrq->u.nwid.nwid & 0xFF; + m.w.mmw_netw_id_h = (wrq->u.nwid.nwid & 0xFF00) >> 8; + mmc_write(ioaddr, (char *)&m.w.mmw_netw_id_l - (char *)&m, + (unsigned char *)&m.w.mmw_netw_id_l, 2); + mmc_out(ioaddr, mmwoff(0, mmw_loopt_sel), 0x00); + } + else + { + /* Disable nwid in the psa */ + psa.psa_nwid_select = 0x00; + psa_write(ioaddr, lp->hacr, + (char *)&psa.psa_nwid_select - (char *)&psa, + (unsigned char *)&psa.psa_nwid_select, 1); + + /* Disable nwid in the mmc (no filtering) */ + mmc_out(ioaddr, mmwoff(0, mmw_loopt_sel), MMW_LOOPT_SEL_DIS_NWID); + } + break; + + case SIOCGIWNWID: + /* Read the NWID */ + psa_read(ioaddr, lp->hacr, (char *)psa.psa_nwid - (char *)&psa, + (unsigned char *)psa.psa_nwid, 3); + wrq->u.nwid.nwid = (psa.psa_nwid[0] << 8) + psa.psa_nwid[1]; + wrq->u.nwid.on = psa.psa_nwid_select; + break; + + case SIOCSIWFREQ: + /* Attempt to recognise 2.00 cards (2.4 GHz frequency selectable) */ + if(!(mmc_in(ioaddr, mmroff(0, mmr_fee_status)) & + (MMR_FEE_STATUS_DWLD | MMR_FEE_STATUS_BUSY))) + ret = wv_set_frequency(ioaddr, &(wrq->u.freq)); + else + ret = -EOPNOTSUPP; + break; + + case SIOCGIWFREQ: + /* Attempt to recognise 2.00 cards (2.4 GHz frequency selectable) + * (does it work for everybody ??? - especially old cards...) */ + if(!(mmc_in(ioaddr, mmroff(0, mmr_fee_status)) & + (MMR_FEE_STATUS_DWLD | MMR_FEE_STATUS_BUSY))) + { + unsigned short freq; + + /* Ask the EEPROM to read the frequency from the first area */ + fee_read(ioaddr, 0x00 /* 1st area - frequency... */, + &freq, 1); + wrq->u.freq.m = ((freq >> 5) * 5 + 24000L) * 10000; + wrq->u.freq.e = 1; + } + else + { + int bands[] = { 915e6, 2.425e8, 2.46e8, 2.484e8, 2.4305e8 }; + + psa_read(ioaddr, lp->hacr, (char *)&psa.psa_subband - (char *)&psa, + (unsigned char *)&psa.psa_subband, 1); + + if(psa.psa_subband <= 4) + { + wrq->u.freq.m = bands[psa.psa_subband]; + wrq->u.freq.e = (psa.psa_subband != 0); + } + else + ret = -EOPNOTSUPP; + } + break; + + case SIOCSIWSENS: + /* Set the level threshold */ + if(!suser()) + return -EPERM; + psa.psa_thr_pre_set = wrq->u.sensitivity & 0x3F; + psa_write(ioaddr, lp->hacr, (char *)&psa.psa_thr_pre_set - (char *)&psa, + (unsigned char *) &psa.psa_thr_pre_set, 1); + mmc_out(ioaddr, mmwoff(0, mmw_thr_pre_set), psa.psa_thr_pre_set); + break; + + case SIOCGIWSENS: + /* Read the level threshold */ + psa_read(ioaddr, lp->hacr, (char *)&psa.psa_thr_pre_set - (char *)&psa, + (unsigned char *) &psa.psa_thr_pre_set, 1); + wrq->u.sensitivity = psa.psa_thr_pre_set & 0x3F; + break; + + case SIOCSIWENCODE: + /* Set encryption key */ + if(!mmc_encr(ioaddr)) + { + ret = -EOPNOTSUPP; + break; + } + + if(wrq->u.encoding.method) + { /* enable encryption */ + int i; + long long key = wrq->u.encoding.code; + + for(i = 7; i >= 0; i--) + { + psa.psa_encryption_key[i] = key & 0xFF; + key >>= 8; + } + psa.psa_encryption_select = 1; + psa_write(ioaddr, lp->hacr, + (char *) &psa.psa_encryption_select - (char *) &psa, + (unsigned char *) &psa.psa_encryption_select, 8+1); + + mmc_out(ioaddr, mmwoff(0, mmw_encr_enable), + MMW_ENCR_ENABLE_EN | MMW_ENCR_ENABLE_MODE); + mmc_write(ioaddr, mmwoff(0, mmw_encr_key), + (unsigned char *) &psa.psa_encryption_key, 8); + } + else + { /* disable encryption */ + psa.psa_encryption_select = 0; + psa_write(ioaddr, lp->hacr, + (char *) &psa.psa_encryption_select - (char *) &psa, + (unsigned char *) &psa.psa_encryption_select, 1); + + mmc_out(ioaddr, mmwoff(0, mmw_encr_enable), 0); + } + break; + + case SIOCGIWENCODE: + /* Read the encryption key */ + if(!mmc_encr(ioaddr)) + { + ret = -EOPNOTSUPP; + break; + } + + /* only super-user can see encryption key */ + if(!suser()) + { + ret = -EPERM; + break; + } + else + { + int i; + long long key = 0; + + psa_read(ioaddr, lp->hacr, + (char *) &psa.psa_encryption_select - (char *) &psa, + (unsigned char *) &psa.psa_encryption_select, 1+8); + for(i = 0; i < 8; i++) + { + key <<= 8; + key += psa.psa_encryption_key[i]; + } + wrq->u.encoding.code = key; + + /* encryption is enabled */ + if(psa.psa_encryption_select) + wrq->u.encoding.method = mmc_encr(ioaddr); + else + wrq->u.encoding.method = 0; + } + break; + + case SIOCGIWRANGE: + /* basic checking */ + if(wrq->u.data.pointer != (caddr_t) 0) + { + struct iw_range range; + + /* Verify the user buffer */ + ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer, + sizeof(struct iw_range)); + if(ret) + break; + + /* Set the length (useless : its constant...) */ + wrq->u.data.length = sizeof(struct iw_range); + + /* Set information in the range struct */ + range.throughput = 1.6 * 1024 * 1024; /* don't argue on this ! */ + range.min_nwid = 0x0000; + range.max_nwid = 0xFFFF; + + /* Attempt to recognise 2.00 cards (2.4 GHz frequency selectable). */ + if(!(mmc_in(ioaddr, mmroff(0, mmr_fee_status)) & + (MMR_FEE_STATUS_DWLD | MMR_FEE_STATUS_BUSY))) + { + range.num_channels = 10; + range.num_frequency = wv_frequency_list(ioaddr, range.freq, + IW_MAX_FREQUENCIES); + } + else + range.num_channels = range.num_frequency = 0; + + range.sensitivity = 0x3F; + range.max_qual.qual = MMR_SGNL_QUAL; + range.max_qual.level = MMR_SIGNAL_LVL; + range.max_qual.noise = MMR_SILENCE_LVL; + + /* Copy structure to the user buffer */ + copy_to_user(wrq->u.data.pointer, &range, + sizeof(struct iw_range)); + } + break; + + case SIOCGIWPRIV: + /* Basic checking... */ + if(wrq->u.data.pointer != (caddr_t) 0) + { + struct iw_priv_args priv[] = + { /* cmd, set_args, get_args, name */ + { SIOCSIPQTHR, IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1, 0, "setqualthr" }, + { SIOCGIPQTHR, 0, IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1, "getqualthr" }, + + { SIOCSIPHISTO, IW_PRIV_TYPE_BYTE | 16, 0, "sethisto" }, + { SIOCGIPHISTO, 0, IW_PRIV_TYPE_INT | 16, "gethisto" }, + }; + + /* Verify the user buffer */ + ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer, + sizeof(priv)); + if(ret) + break; + + /* Set the number of ioctl available */ + wrq->u.data.length = 4; + + /* Copy structure to the user buffer */ + copy_to_user(wrq->u.data.pointer, (u_char *) priv, + sizeof(priv)); + } + break; + +#ifdef WIRELESS_SPY + case SIOCSIWSPY: + /* Set the spy list */ + + /* Check the number of addresses */ + if(wrq->u.data.length > IW_MAX_SPY) + { + ret = -E2BIG; + break; + } + lp->spy_number = wrq->u.data.length; + + /* If there is some addresses to copy */ + if(lp->spy_number > 0) + { + struct sockaddr address[IW_MAX_SPY]; + int i; + + /* Verify where the user has set his addresses */ + ret = verify_area(VERIFY_READ, wrq->u.data.pointer, + sizeof(struct sockaddr) * lp->spy_number); + if(ret) + break; + /* Copy addresses to the driver */ + copy_from_user(address, wrq->u.data.pointer, + sizeof(struct sockaddr) * lp->spy_number); + + /* Copy addresses to the lp structure */ + for(i = 0; i < lp->spy_number; i++) + { + memcpy(lp->spy_address[i], address[i].sa_data, + WAVELAN_ADDR_SIZE); + } + + /* Reset structure... */ + memset(lp->spy_stat, 0x00, sizeof(iw_qual) * IW_MAX_SPY); + +#ifdef DEBUG_IOCTL_INFO + printk(KERN_DEBUG "SetSpy - Set of new addresses is :\n"); + for(i = 0; i < wrq->u.data.length; i++) + printk(KERN_DEBUG "%02X:%02X:%02X:%02X:%02X:%02X \n", + lp->spy_address[i][0], + lp->spy_address[i][1], + lp->spy_address[i][2], + lp->spy_address[i][3], + lp->spy_address[i][4], + lp->spy_address[i][5]); +#endif /* DEBUG_IOCTL_INFO */ + } + + break; + + case SIOCGIWSPY: + /* Get the spy list and spy stats */ + + /* Set the number of addresses */ + wrq->u.data.length = lp->spy_number; + + /* If the user want to have the addresses back... */ + if((lp->spy_number > 0) && (wrq->u.data.pointer != (caddr_t) 0)) + { + struct sockaddr address[IW_MAX_SPY]; + int i; + + /* Verify the user buffer */ + ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer, + (sizeof(iw_qual) + sizeof(struct sockaddr)) + * IW_MAX_SPY); + if(ret) + break; + + /* Copy addresses from the lp structure */ + for(i = 0; i < lp->spy_number; i++) + { + memcpy(address[i].sa_data, lp->spy_address[i], + WAVELAN_ADDR_SIZE); + address[i].sa_family = AF_UNIX; + } + + /* Copy addresses to the user buffer */ + copy_to_user(wrq->u.data.pointer, address, + sizeof(struct sockaddr) * lp->spy_number); + + /* Copy stats to the user buffer (just after) */ + copy_to_user(wrq->u.data.pointer + + (sizeof(struct sockaddr) * lp->spy_number), + lp->spy_stat, sizeof(iw_qual) * lp->spy_number); + + /* Reset updated flags */ + for(i = 0; i < lp->spy_number; i++) + lp->spy_stat[i].updated = 0x0; + } /* if(pointer != NULL) */ + + break; +#endif /* WIRELESS_SPY */ + + /* ------------------ PRIVATE IOCTL ------------------ */ + + case SIOCSIPQTHR: + if(!suser()) + return -EPERM; + psa.psa_quality_thr = *(wrq->u.name) & 0x0F; + psa_write(ioaddr, lp->hacr, (char *)&psa.psa_quality_thr - (char *)&psa, + (unsigned char *)&psa.psa_quality_thr, 1); + mmc_out(ioaddr, mmwoff(0, mmw_quality_thr), psa.psa_quality_thr); + break; + + case SIOCGIPQTHR: + psa_read(ioaddr, lp->hacr, (char *)&psa.psa_quality_thr - (char *)&psa, + (unsigned char *)&psa.psa_quality_thr, 1); + *(wrq->u.name) = psa.psa_quality_thr & 0x0F; + break; + +#ifdef HISTOGRAM + case SIOCSIPHISTO: + /* Verif if the user is root */ + if(!suser()) + return -EPERM; + + /* Check the number of intervals */ + if(wrq->u.data.length > 16) + { + ret = -E2BIG; + break; + } + lp->his_number = wrq->u.data.length; + + /* If there is some addresses to copy */ + if(lp->his_number > 0) + { + /* Verify where the user has set his addresses */ + ret = verify_area(VERIFY_READ, wrq->u.data.pointer, + sizeof(char) * lp->his_number); + if(ret) + break; + /* Copy interval ranges to the driver */ + copy_from_user(lp->his_range, wrq->u.data.pointer, + sizeof(char) * lp->his_number); + + /* Reset structure... */ + memset(lp->his_sum, 0x00, sizeof(long) * 16); + } + break; + + case SIOCGIPHISTO: + /* Set the number of intervals */ + wrq->u.data.length = lp->his_number; + + /* Give back the distribution statistics */ + if((lp->his_number > 0) && (wrq->u.data.pointer != (caddr_t) 0)) + { + /* Verify the user buffer */ + ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer, + sizeof(long) * 16); + if(ret) + break; + + /* Copy data to the user buffer */ + copy_to_user(wrq->u.data.pointer, lp->his_sum, + sizeof(long) * lp->his_number); + } /* if(pointer != NULL) */ + break; +#endif /* HISTOGRAM */ + + /* ------------------- OTHER IOCTL ------------------- */ + + default: + ret = -EOPNOTSUPP; + } + + /* Enable interrupts, restore flags */ + wv_splx(x); + +#ifdef DEBUG_IOCTL_TRACE + printk(KERN_DEBUG "%s: <-wavelan_ioctl()\n", dev->name); +#endif + return ret; +} + +/*------------------------------------------------------------------*/ +/* + * Get wireless statistics + * Called by /proc/net/wireless + */ +static iw_stats * +wavelan_get_wireless_stats(device * dev) +{ + u_long ioaddr = dev->base_addr; + net_local * lp = (net_local *) dev->priv; + mmr_t m; + iw_stats * wstats; + unsigned long x; + +#ifdef DEBUG_IOCTL_TRACE + printk(KERN_DEBUG "%s: ->wavelan_get_wireless_stats()\n", dev->name); +#endif + + /* Disable interrupts & save flags */ + x = wv_splhi(); + + if(lp == (net_local *) NULL) + return (iw_stats *) NULL; + wstats = &lp->wstats; + + /* Get data from the mmc */ + mmc_out(ioaddr, mmwoff(0, mmw_freeze), 1); + + mmc_read(ioaddr, mmroff(0, mmr_dce_status), &m.mmr_dce_status, 1); + mmc_read(ioaddr, mmroff(0, mmr_wrong_nwid_l), &m.mmr_wrong_nwid_l, 2); + mmc_read(ioaddr, mmroff(0, mmr_thr_pre_set), &m.mmr_thr_pre_set, 4); + + mmc_out(ioaddr, mmwoff(0, mmw_freeze), 0); + + /* Copy data to wireless stuff */ + wstats->status = m.mmr_dce_status; + wstats->qual.qual = m.mmr_sgnl_qual & MMR_SGNL_QUAL; + wstats->qual.level = m.mmr_signal_lvl & MMR_SIGNAL_LVL; + wstats->qual.noise = m.mmr_silence_lvl & MMR_SILENCE_LVL; + wstats->qual.updated = (((m.mmr_signal_lvl & MMR_SIGNAL_LVL_VALID) >> 7) | + ((m.mmr_signal_lvl & MMR_SIGNAL_LVL_VALID) >> 6) | + ((m.mmr_silence_lvl & MMR_SILENCE_LVL_VALID) >> 5)); + wstats->discard.nwid += (m.mmr_wrong_nwid_h << 8) | m.mmr_wrong_nwid_l; + wstats->discard.code = 0L; + wstats->discard.misc = 0L; + + /* Enable interrupts & restore flags */ + wv_splx(x); + +#ifdef DEBUG_IOCTL_TRACE + printk(KERN_DEBUG "%s: <-wavelan_get_wireless_stats()\n", dev->name); +#endif + return &lp->wstats; +} +#endif /* WIRELESS_EXT */ + +/************************* PACKET RECEPTION *************************/ +/* + * This part deals with receiving the packets. + * The interrupt handler gets an interrupt when a packet has been + * successfully received and calls this part. + */ + +/*------------------------------------------------------------------*/ +/* + * This routine does the actual copying of data (including the Ethernet + * header structure) from the WaveLAN card to an sk_buff chain that + * will be passed up to the network interface layer. NOTE: we + * currently don't handle trailer protocols (neither does the rest of + * the network interface), so if that is needed, it will (at least in + * part) be added here. The contents of the receive ring buffer are + * copied to a message chain that is then passed to the kernel. + * + * Note: if any errors occur, the packet is "dropped on the floor" + * (called by wv_packet_rcv()) + */ +static inline void +wv_packet_read(device * dev, + u_short buf_off, + int sksize) +{ + net_local * lp = (net_local *) dev->priv; + u_long ioaddr = dev->base_addr; + struct sk_buff * skb; + +#ifdef DEBUG_RX_TRACE + printk(KERN_DEBUG "%s: ->wv_packet_read(0x%X, %d)\n", + dev->name, fd_p, sksize); +#endif + + /* Allocate buffer for the data */ + if((skb = dev_alloc_skb(sksize)) == (struct sk_buff *) NULL) + { +#ifdef DEBUG_RX_ERROR + printk(KERN_INFO "%s: wv_packet_read(): could not alloc_skb(%d, GFP_ATOMIC).\n", + dev->name, sksize); +#endif + lp->stats.rx_dropped++; + return; + } + + skb->dev = dev; + + /* Copy the packet to the buffer */ + obram_read(ioaddr, buf_off, skb_put(skb, sksize), sksize); + skb->protocol=eth_type_trans(skb, dev); + +#ifdef DEBUG_RX_INFO + wv_packet_info(skb->mac.raw, sksize, dev->name, "wv_packet_read"); +#endif /* DEBUG_RX_INFO */ + + /* Statistics gathering & stuff associated. + * It seem a bit messy with all the define, but it's really simple... */ +#if defined(WIRELESS_SPY) || defined(HISTOGRAM) + if( +#ifdef WIRELESS_SPY + (lp->spy_number > 0) || +#endif /* WIRELESS_SPY */ +#ifdef HISTOGRAM + (lp->his_number > 0) || +#endif /* HISTOGRAM */ + 0) + { + u_char stats[3]; /* signal level, noise level, signal quality */ + + /* read signal level, silence level and signal quality bytes */ + /* Note: in the PCMCIA hardware, these are part of the frame. It seems + * that for the ISA hardware, it's nowhere to be found in the frame, + * so I'm obliged to do this (it has a side effect on /proc/net/wireless). + * Any ideas? */ + mmc_out(ioaddr, mmwoff(0, mmw_freeze), 1); + mmc_read(ioaddr, mmroff(0, mmr_signal_lvl), stats, 3); + mmc_out(ioaddr, mmwoff(0, mmw_freeze), 0); + +#ifdef DEBUG_RX_INFO + printk(KERN_DEBUG "%s: wv_packet_read(): Signal level %d/63, Silence level %d/63, signal quality %d/16\n", + dev->name, stats[0] & 0x3F, stats[1] & 0x3F, stats[2] & 0x0F); +#endif + + /* Spying stuff */ +#ifdef WIRELESS_SPY + wl_spy_gather(dev, skb->mac.raw + WAVELAN_ADDR_SIZE, stats); +#endif /* WIRELESS_SPY */ +#ifdef HISTOGRAM + wl_his_gather(dev, stats); +#endif /* HISTOGRAM */ + } +#endif /* defined(WIRELESS_SPY) || defined(HISTOGRAM) */ + + /* + * Hand the packet to the Network Module + */ + netif_rx(skb); + + lp->stats.rx_packets++; + +#ifdef DEBUG_RX_TRACE + printk(KERN_DEBUG "%s: <-wv_packet_read()\n", dev->name); +#endif +} + +/*------------------------------------------------------------------*/ +/* + * Transfer as many packets as we can + * from the device RAM. + * Called by the interrupt handler. + */ +static inline void +wv_receive(device * dev) +{ + u_long ioaddr = dev->base_addr; + net_local * lp = (net_local *)dev->priv; + int nreaped = 0; + +#ifdef DEBUG_RX_TRACE + printk(KERN_DEBUG "%s: ->wv_receive()\n", dev->name); +#endif + + /* Loop on each received packet */ + for(;;) + { + fd_t fd; + rbd_t rbd; + ushort pkt_len; + + obram_read(ioaddr, lp->rx_head, (unsigned char *) &fd, sizeof(fd)); + + /* If the current frame is not complete, we have reach the end... */ + if((fd.fd_status & FD_STATUS_C) != FD_STATUS_C) + break; /* This is how we exit the loop */ + + nreaped++; + + /* Check if frame correctly received */ + if((fd.fd_status & (FD_STATUS_B | FD_STATUS_OK)) != + (FD_STATUS_B | FD_STATUS_OK)) + { + /* + * Not sure about this one -- it does not seem + * to be an error so we will keep quiet about it. + */ +#ifndef IGNORE_NORMAL_XMIT_ERRS +#ifdef DEBUG_RX_ERROR + if((fd.fd_status & FD_STATUS_B) != FD_STATUS_B) + printk(KERN_INFO "%s: wv_receive(): frame not consumed by RU.\n", + dev->name); +#endif +#endif /* IGNORE_NORMAL_XMIT_ERRS */ + +#ifdef DEBUG_RX_ERROR + if((fd.fd_status & FD_STATUS_OK) != FD_STATUS_OK) + printk(KERN_INFO "%s: wv_receive(): frame not received successfully.\n", + dev->name); +#endif + } + + /* Were there problems in processing the frame? Let's check. */ + if((fd.fd_status & (FD_STATUS_S6 | FD_STATUS_S7 | FD_STATUS_S8 | + FD_STATUS_S9 | FD_STATUS_S10 | FD_STATUS_S11)) + != 0) + { + lp->stats.rx_errors++; + +#ifdef DEBUG_RX_ERROR + if((fd.fd_status & FD_STATUS_S6) != 0) + printk(KERN_INFO "%s: wv_receive(): no EOF flag.\n", dev->name); +#endif + + if((fd.fd_status & FD_STATUS_S7) != 0) + { + lp->stats.rx_length_errors++; +#ifdef DEBUG_RX_ERROR + printk(KERN_INFO "%s: wv_receive(): frame too short.\n", + dev->name); +#endif + } + + if((fd.fd_status & FD_STATUS_S8) != 0) + { + lp->stats.rx_over_errors++; +#ifdef DEBUG_RX_ERROR + printk(KERN_INFO "%s: wv_receive(): rx DMA overrun.\n", + dev->name); +#endif + } + + if((fd.fd_status & FD_STATUS_S9) != 0) + { + lp->stats.rx_fifo_errors++; +#ifdef DEBUG_RX_ERROR + printk(KERN_INFO "%s: wv_receive(): ran out of resources.\n", + dev->name); +#endif + } + + if((fd.fd_status & FD_STATUS_S10) != 0) + { + lp->stats.rx_frame_errors++; +#ifdef DEBUG_RX_ERROR + printk(KERN_INFO "%s: wv_receive(): alignment error.\n", + dev->name); +#endif + } + + if((fd.fd_status & FD_STATUS_S11) != 0) + { + lp->stats.rx_crc_errors++; +#ifdef DEBUG_RX_ERROR + printk(KERN_INFO "%s: wv_receive(): CRC error.\n", dev->name); +#endif + } + } + + /* Does the frame contain a pointer to the data? Let's check. */ + if(fd.fd_rbd_offset == I82586NULL) +#ifdef DEBUG_RX_ERROR + printk(KERN_INFO "%s: wv_receive(): frame has no data.\n", dev->name); +#endif + else + { + obram_read(ioaddr, fd.fd_rbd_offset, + (unsigned char *) &rbd, sizeof(rbd)); + +#ifdef DEBUG_RX_ERROR + if((rbd.rbd_status & RBD_STATUS_EOF) != RBD_STATUS_EOF) + printk(KERN_INFO "%s: wv_receive(): missing EOF flag.\n", + dev->name); + + if((rbd.rbd_status & RBD_STATUS_F) != RBD_STATUS_F) + printk(KERN_INFO "%s: wv_receive(): missing F flag.\n", + dev->name); +#endif + + pkt_len = rbd.rbd_status & RBD_STATUS_ACNT; + + /* Read the packet and transmit to Linux */ + wv_packet_read(dev, rbd.rbd_bufl, pkt_len); + } /* if frame has data */ + + fd.fd_status = 0; + obram_write(ioaddr, fdoff(lp->rx_head, fd_status), + (unsigned char *) &fd.fd_status, sizeof(fd.fd_status)); + + fd.fd_command = FD_COMMAND_EL; + obram_write(ioaddr, fdoff(lp->rx_head, fd_command), + (unsigned char *) &fd.fd_command, sizeof(fd.fd_command)); + + fd.fd_command = 0; + obram_write(ioaddr, fdoff(lp->rx_last, fd_command), + (unsigned char *) &fd.fd_command, sizeof(fd.fd_command)); + + lp->rx_last = lp->rx_head; + lp->rx_head = fd.fd_link_offset; + } /* for(;;) -> loop on all frames */ + +#ifdef DEBUG_RX_INFO + if(nreaped > 1) + printk(KERN_DEBUG "%s: wv_receive(): reaped %d\n", dev->name, nreaped); +#endif +#ifdef DEBUG_RX_TRACE + printk(KERN_DEBUG "%s: <-wv_receive()\n", dev->name); +#endif +} + +/*********************** PACKET TRANSMISSION ***********************/ +/* + * This part deals with sending packet through the WaveLAN + * + */ + +/*------------------------------------------------------------------*/ +/* + * This routine fills in the appropriate registers and memory + * locations on the WaveLAN card and starts the card off on + * the transmit. + * + * The principle : + * Each block contain a transmit command, a nop command, + * a transmit block descriptor and a buffer. + * The CU read the transmit block which point to the tbd, + * read the tbd and the content of the buffer. + * When it has finished with it, it goes to the next command + * which in our case is the nop. The nop point on itself, + * so the CU stop here. + * When we add the next block, we modify the previous nop + * to make it point on the new tx command. + * Simple, isn't it ? + * + * (called in wavelan_packet_xmit()) + */ +static inline void +wv_packet_write(device * dev, + void * buf, + short length) +{ + net_local * lp = (net_local *) dev->priv; + u_long ioaddr = dev->base_addr; + unsigned short txblock; + unsigned short txpred; + unsigned short tx_addr; + unsigned short nop_addr; + unsigned short tbd_addr; + unsigned short buf_addr; + ac_tx_t tx; + ac_nop_t nop; + tbd_t tbd; + int clen = length; + unsigned long x; + +#ifdef DEBUG_TX_TRACE + printk(KERN_DEBUG "%s: ->wv_packet_write(%d)\n", dev->name, length); +#endif + + /* Check if we need some padding */ + if(clen < ETH_ZLEN) + clen = ETH_ZLEN; + + x = wv_splhi(); + + /* Calculate addresses of next block and previous block */ + txblock = lp->tx_first_free; + txpred = txblock - TXBLOCKZ; + if(txpred < OFFSET_CU) + txpred += NTXBLOCKS * TXBLOCKZ; + lp->tx_first_free += TXBLOCKZ; + if(lp->tx_first_free >= OFFSET_CU + NTXBLOCKS * TXBLOCKZ) + lp->tx_first_free -= NTXBLOCKS * TXBLOCKZ; + +/* +if (lp->tx_n_in_use > 0) + printk("%c", "0123456789abcdefghijk"[lp->tx_n_in_use]); +*/ + + lp->tx_n_in_use++; + + /* Calculate addresses of the differents part of the block */ + tx_addr = txblock; + nop_addr = tx_addr + sizeof(tx); + tbd_addr = nop_addr + sizeof(nop); + buf_addr = tbd_addr + sizeof(tbd); + + /* + * Transmit command. + */ + tx.tx_h.ac_status = 0; + obram_write(ioaddr, toff(ac_tx_t, tx_addr, tx_h.ac_status), + (unsigned char *) &tx.tx_h.ac_status, + sizeof(tx.tx_h.ac_status)); + + /* + * NOP command. + */ + nop.nop_h.ac_status = 0; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_status), + (unsigned char *) &nop.nop_h.ac_status, + sizeof(nop.nop_h.ac_status)); + nop.nop_h.ac_link = nop_addr; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_link), + (unsigned char *) &nop.nop_h.ac_link, + sizeof(nop.nop_h.ac_link)); + + /* + * Transmit buffer descriptor + */ + tbd.tbd_status = TBD_STATUS_EOF | (TBD_STATUS_ACNT & clen); + tbd.tbd_next_bd_offset = I82586NULL; + tbd.tbd_bufl = buf_addr; + tbd.tbd_bufh = 0; + obram_write(ioaddr, tbd_addr, (unsigned char *)&tbd, sizeof(tbd)); + + /* + * Data + */ + obram_write(ioaddr, buf_addr, buf, clen); + + /* + * Overwrite the predecessor NOP link + * so that it points to this txblock. + */ + nop_addr = txpred + sizeof(tx); + nop.nop_h.ac_status = 0; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_status), + (unsigned char *)&nop.nop_h.ac_status, + sizeof(nop.nop_h.ac_status)); + nop.nop_h.ac_link = txblock; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_link), + (unsigned char *) &nop.nop_h.ac_link, + sizeof(nop.nop_h.ac_link)); + + /* If watchdog not already active, activate it... */ + if(lp->watchdog.prev == (timer_list *) NULL) + { + /* set timer to expire in WATCHDOG_JIFFIES */ + lp->watchdog.expires = jiffies + WATCHDOG_JIFFIES; + add_timer(&lp->watchdog); + } + + if(lp->tx_first_in_use == I82586NULL) + lp->tx_first_in_use = txblock; + + if(lp->tx_n_in_use < NTXBLOCKS - 1) + dev->tbusy = 0; + + wv_splx(x); + +#ifdef DEBUG_TX_INFO + wv_packet_info((u_char *) buf, length, dev->name, "wv_packet_write"); +#endif /* DEBUG_TX_INFO */ + +#ifdef DEBUG_TX_TRACE + printk(KERN_DEBUG "%s: <-wv_packet_write()\n", dev->name); +#endif +} + +/*------------------------------------------------------------------*/ +/* + * This routine is called when we want to send a packet (NET3 callback) + * In this routine, we check if the hardware is ready to accept + * the packet. We also prevent reentrance. Then, we call the function + * to send the packet... + */ +static int +wavelan_packet_xmit(struct sk_buff * skb, + device * dev) +{ + net_local * lp = (net_local *)dev->priv; + +#ifdef DEBUG_TX_TRACE + printk(KERN_DEBUG "%s: ->wavelan_packet_xmit(0x%X)\n", dev->name, + (unsigned) skb); +#endif + + /* This flag indicate that the hardware can't perform a transmission. + * Theoretically, NET3 checks it before sending a packet to the driver, + * but in fact it never does that and pool continuously. + * As the watchdog will abort overly long transmissions, we are quite safe. + */ + if(dev->tbusy) + return 1; + + /* + * If some higher layer thinks we've missed + * a tx-done interrupt we are passed NULL. + * Caution: dev_tint() handles the cli()/sti() itself. + */ + if(skb == (struct sk_buff *)0) + { +#ifdef DEBUG_TX_ERROR + printk(KERN_INFO "%s: wavelan_packet_xmit(): skb == NULL\n", dev->name); +#endif + dev_tint(dev); + return 0; + } + + /* + * Block a timer-based transmit from overlapping. + * In other words, prevent reentering this routine. + */ + if(set_bit(0, (void *)&dev->tbusy) != 0) +#ifdef DEBUG_TX_ERROR + printk(KERN_INFO "%s: Transmitter access conflict.\n", dev->name); +#endif + else + { + /* If somebody has asked to reconfigure the controller, + * we can do it now. + */ + if(lp->reconfig_82586) + { + wv_82586_config(dev); + if(dev->tbusy) + return 1; + } + +#ifdef DEBUG_TX_ERROR + if(skb->next) + printk(KERN_INFO "skb has next\n"); +#endif + + wv_packet_write(dev, skb->data, skb->len); + } + + dev_kfree_skb(skb, FREE_WRITE); + +#ifdef DEBUG_TX_TRACE + printk(KERN_DEBUG "%s: <-wavelan_packet_xmit()\n", dev->name); +#endif + return 0; +} + +/*********************** HARDWARE CONFIGURATION ***********************/ +/* + * This part does the real job of starting and configuring the hardware. + */ + +/*--------------------------------------------------------------------*/ +/* + * Routine to initialize the Modem Management Controller. + * (called by wv_hw_reset()) + */ +static inline int +wv_mmc_init(device * dev) +{ + u_long ioaddr = dev->base_addr; + net_local * lp = (net_local *)dev->priv; + psa_t psa; + mmw_t m; + int configured; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: ->wv_mmc_init()\n", dev->name); +#endif + + /* Read the parameter storage area */ + psa_read(ioaddr, lp->hacr, 0, (unsigned char *) &psa, sizeof(psa)); + +#ifdef USE_PSA_CONFIG + configured = psa.psa_conf_status & 1; +#else + configured = 0; +#endif + + /* Is the PSA is not configured */ + if(!configured) + { + /* User will be able to configure NWID after (with iwconfig) */ + psa.psa_nwid[0] = 0; + psa.psa_nwid[1] = 0; + + /* no NWID checking since NWID is not set */ + psa.psa_nwid_select = 0; + + /* Disable encryption */ + psa.psa_encryption_select = 0; + + /* Set to standard values + * 0x04 for AT, + * 0x01 for MCA, + * 0x04 for PCMCIA and 2.00 card (AT&T 407-024689/E document) + */ + if (psa.psa_comp_number & 1) + psa.psa_thr_pre_set = 0x01; + else + psa.psa_thr_pre_set = 0x04; + psa.psa_quality_thr = 0x03; + + /* It is configured */ + psa.psa_conf_status |= 1; + +#ifdef USE_PSA_CONFIG + /* Write the psa */ + psa_write(ioaddr, lp->hacr, (char *)psa.psa_nwid - (char *)&psa, + (unsigned char *)psa.psa_nwid, 4); + psa_write(ioaddr, lp->hacr, (char *)&psa.psa_thr_pre_set - (char *)&psa, + (unsigned char *)&psa.psa_thr_pre_set, 1); + psa_write(ioaddr, lp->hacr, (char *)&psa.psa_quality_thr - (char *)&psa, + (unsigned char *)&psa.psa_quality_thr, 1); + psa_write(ioaddr, lp->hacr, (char *)&psa.psa_conf_status - (char *)&psa, + (unsigned char *)&psa.psa_conf_status, 1); +#endif + } + + /* Zero the mmc structure */ + memset(&m, 0x00, sizeof(m)); + + /* Copy PSA info to the mmc */ + m.mmw_netw_id_l = psa.psa_nwid[1]; + m.mmw_netw_id_h = psa.psa_nwid[0]; + + if(psa.psa_nwid_select & 1) + m.mmw_loopt_sel = 0x00; + else + m.mmw_loopt_sel = MMW_LOOPT_SEL_DIS_NWID; + + memcpy(&m.mmw_encr_key, &psa.psa_encryption_key, + sizeof(m.mmw_encr_key)); + + if(psa.psa_encryption_select) + m.mmw_encr_enable = MMW_ENCR_ENABLE_EN | MMW_ENCR_ENABLE_MODE; + else + m.mmw_encr_enable = 0; + + m.mmw_thr_pre_set = psa.psa_thr_pre_set & 0x3F; + m.mmw_quality_thr = psa.psa_quality_thr & 0x0F; + + /* Missing: encryption stuff... */ + + /* + * Set default modem control parameters. + * See NCR document 407-0024326 Rev. A. + */ + m.mmw_jabber_enable = 0x01; + m.mmw_anten_sel = MMW_ANTEN_SEL_ALG_EN; + m.mmw_ifs = 0x20; + m.mmw_mod_delay = 0x04; + m.mmw_jam_time = 0x38; + + m.mmw_encr_enable = 0; + m.mmw_des_io_invert = 0; + m.mmw_freeze = 0; + m.mmw_decay_prm = 0; + m.mmw_decay_updat_prm = 0; + + /* Write all info to MMC */ + mmc_write(ioaddr, 0, (u_char *)&m, sizeof(m)); + + /* The following code starts the modem of the 2.00 frequency + * selectable cards at power on. It's not strictly needed for the + * following boots. + * The original patch was by Joe Finney for the PCMCIA driver, but + * I've cleaned it up a bit and added documentation. + * Thanks to Loeke Brederveld from Lucent for the info. + */ + + /* Attempt to recognise 2.00 cards (2.4 GHz frequency selectable) + * (does it work for everybody? -- especially old cards?) */ + /* Note: WFREQSEL verifies that it is able to read a sensible + * frequency from EEPROM (address 0x00) and that MMR_FEE_STATUS_ID + * is 0xA (Xilinx version) or 0xB (Ariadne version). + * My test is more crude but does work. */ + if(!(mmc_in(ioaddr, mmroff(0, mmr_fee_status)) & + (MMR_FEE_STATUS_DWLD | MMR_FEE_STATUS_BUSY))) + { + /* We must download the frequency parameters to the + * synthesizers (from the EEPROM - area 1) + * Note : as the EEPROM is auto decremented, we set the end + * if the area... */ + m.mmw_fee_addr = 0x0F; + m.mmw_fee_ctrl = MMW_FEE_CTRL_READ | MMW_FEE_CTRL_DWLD; + mmc_write(ioaddr, (char *)&m.mmw_fee_ctrl - (char *)&m, + (unsigned char *)&m.mmw_fee_ctrl, 2); + + /* Wait until the download is finished */ + fee_wait(ioaddr, 100, 100); + +#ifdef DEBUG_CONFIG_INFO + /* The frequency was in the last word downloaded. */ + mmc_read(ioaddr, (char *)&m.mmw_fee_data_l - (char *)&m, + (unsigned char *)&m.mmw_fee_data_l, 2); + + /* Print some info for the user. */ + printk(KERN_DEBUG "%s: WaveLAN 2.00 recognised (frequency select) : Current frequency = %ld\n", + dev->name, + ((m.mmw_fee_data_h << 4) | + (m.mmw_fee_data_l >> 4)) * 5 / 2 + 24000L); +#endif + + /* We must now download the power adjust value (gain) to + * the synthesizers (from the EEPROM - area 7 - DAC) */ + m.mmw_fee_addr = 0x61; + m.mmw_fee_ctrl = MMW_FEE_CTRL_READ | MMW_FEE_CTRL_DWLD; + mmc_write(ioaddr, (char *)&m.mmw_fee_ctrl - (char *)&m, + (unsigned char *)&m.mmw_fee_ctrl, 2); + + /* Wait until the download is finished */ + } /* if 2.00 card */ + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: <-wv_mmc_init()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Construct the fd and rbd structures. + * Start the receive unit. + * (called by wv_hw_reset()) + */ +static inline int +wv_ru_start(device * dev) +{ + net_local * lp = (net_local *) dev->priv; + u_long ioaddr = dev->base_addr; + u_short scb_cs; + fd_t fd; + rbd_t rbd; + u_short rx; + u_short rx_next; + int i; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: ->wv_ru_start()\n", dev->name); +#endif + + obram_read(ioaddr, scboff(OFFSET_SCB, scb_status), (unsigned char *)&scb_cs, sizeof(scb_cs)); + if((scb_cs & SCB_ST_RUS) == SCB_ST_RUS_RDY) + return 0; + + lp->rx_head = OFFSET_RU; + + for(i = 0, rx = lp->rx_head; i < NRXBLOCKS; i++, rx = rx_next) + { + rx_next = (i == NRXBLOCKS - 1) ? lp->rx_head : rx + RXBLOCKZ; + + fd.fd_status = 0; + fd.fd_command = (i == NRXBLOCKS - 1) ? FD_COMMAND_EL : 0; + fd.fd_link_offset = rx_next; + fd.fd_rbd_offset = rx + sizeof(fd); + obram_write(ioaddr, rx, (unsigned char *)&fd, sizeof(fd)); + + rbd.rbd_status = 0; + rbd.rbd_next_rbd_offset = I82586NULL; + rbd.rbd_bufl = rx + sizeof(fd) + sizeof(rbd); + rbd.rbd_bufh = 0; + rbd.rbd_el_size = RBD_EL | (RBD_SIZE & MAXDATAZ); + obram_write(ioaddr, rx + sizeof(fd), + (unsigned char *) &rbd, sizeof(rbd)); + + lp->rx_last = rx; + } + + obram_write(ioaddr, scboff(OFFSET_SCB, scb_rfa_offset), + (unsigned char *) &lp->rx_head, sizeof(lp->rx_head)); + + scb_cs = SCB_CMD_RUC_GO; + obram_write(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *) &scb_cs, sizeof(scb_cs)); + + set_chan_attn(ioaddr, lp->hacr); + + for(i = 1000; i > 0; i--) + { + obram_read(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *) &scb_cs, sizeof(scb_cs)); + if (scb_cs == 0) + break; + + udelay(10); + } + + if(i <= 0) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_INFO "%s: wavelan_ru_start(): board not accepting command.\n", + dev->name); +#endif + return -1; + } + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: <-wv_ru_start()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Initialise the transmit blocks. + * Start the command unit executing the NOP + * self-loop of the first transmit block. + * + * Here, we create the list of send buffers used to transmit packets + * between the PC and the command unit. For each buffer, we create a + * buffer descriptor (pointing on the buffer), a transmit command + * (pointing to the buffer descriptor) and a NOP command. + * The transmit command is linked to the NOP, and the NOP to itself. + * When we will have finished executing the transmit command, we will + * then loop on the NOP. By releasing the NOP link to a new command, + * we may send another buffer. + * + * (called by wv_hw_reset()) + */ +static inline int +wv_cu_start(device * dev) +{ + net_local * lp = (net_local *) dev->priv; + u_long ioaddr = dev->base_addr; + int i; + u_short txblock; + u_short first_nop; + u_short scb_cs; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: ->wv_cu_start()\n", dev->name); +#endif + + lp->tx_first_free = OFFSET_CU; + lp->tx_first_in_use = I82586NULL; + + for(i = 0, txblock = OFFSET_CU; + i < NTXBLOCKS; + i++, txblock += TXBLOCKZ) + { + ac_tx_t tx; + ac_nop_t nop; + tbd_t tbd; + unsigned short tx_addr; + unsigned short nop_addr; + unsigned short tbd_addr; + unsigned short buf_addr; + + tx_addr = txblock; + nop_addr = tx_addr + sizeof(tx); + tbd_addr = nop_addr + sizeof(nop); + buf_addr = tbd_addr + sizeof(tbd); + + tx.tx_h.ac_status = 0; + tx.tx_h.ac_command = acmd_transmit | AC_CFLD_I; + tx.tx_h.ac_link = nop_addr; + tx.tx_tbd_offset = tbd_addr; + obram_write(ioaddr, tx_addr, (unsigned char *) &tx, sizeof(tx)); + + nop.nop_h.ac_status = 0; + nop.nop_h.ac_command = acmd_nop; + nop.nop_h.ac_link = nop_addr; + obram_write(ioaddr, nop_addr, (unsigned char *) &nop, sizeof(nop)); + + tbd.tbd_status = TBD_STATUS_EOF; + tbd.tbd_next_bd_offset = I82586NULL; + tbd.tbd_bufl = buf_addr; + tbd.tbd_bufh = 0; + obram_write(ioaddr, tbd_addr, (unsigned char *) &tbd, sizeof(tbd)); + } + + first_nop = OFFSET_CU + (NTXBLOCKS - 1) * TXBLOCKZ + sizeof(ac_tx_t); + obram_write(ioaddr, scboff(OFFSET_SCB, scb_cbl_offset), + (unsigned char *) &first_nop, sizeof(first_nop)); + + scb_cs = SCB_CMD_CUC_GO; + obram_write(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *) &scb_cs, sizeof(scb_cs)); + + set_chan_attn(ioaddr, lp->hacr); + + for(i = 1000; i > 0; i--) + { + obram_read(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *) &scb_cs, sizeof(scb_cs)); + if (scb_cs == 0) + break; + + udelay(10); + } + + if(i <= 0) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_INFO "%s: wavelan_cu_start(): board not accepting command.\n", + dev->name); +#endif + return -1; + } + + lp->tx_n_in_use = 0; + dev->tbusy = 0; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: <-wv_cu_start()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * This routine does a standard config of the WaveLAN controler (i82586). + * + * It initialises the scp, iscp and scb structure + * The first two are just pointers to the next. + * The last one is used for basic configuration and for basic + * communication (interrupt status). + * + * (called by wv_hw_reset()) + */ +static inline int +wv_82586_start(device * dev) +{ + net_local * lp = (net_local *) dev->priv; + u_long ioaddr = dev->base_addr; + scp_t scp; /* system configuration pointer */ + iscp_t iscp; /* intermediate scp */ + scb_t scb; /* system control block */ + ach_t cb; /* Action command header */ + u_char zeroes[512]; + int i; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: ->wv_82586_start()\n", dev->name); +#endif + + /* + * Clear the onboard RAM. + */ + memset(&zeroes[0], 0x00, sizeof(zeroes)); + for(i = 0; i < I82586_MEMZ; i += sizeof(zeroes)) + obram_write(ioaddr, i, &zeroes[0], sizeof(zeroes)); + + /* + * Construct the command unit structures: + * scp, iscp, scb, cb. + */ + memset(&scp, 0x00, sizeof(scp)); + scp.scp_sysbus = SCP_SY_16BBUS; + scp.scp_iscpl = OFFSET_ISCP; + obram_write(ioaddr, OFFSET_SCP, (unsigned char *)&scp, sizeof(scp)); + + memset(&iscp, 0x00, sizeof(iscp)); + iscp.iscp_busy = 1; + iscp.iscp_offset = OFFSET_SCB; + obram_write(ioaddr, OFFSET_ISCP, (unsigned char *)&iscp, sizeof(iscp)); + + /* Our first command is to reset the i82586. */ + memset(&scb, 0x00, sizeof(scb)); + scb.scb_command = SCB_CMD_RESET; + scb.scb_cbl_offset = OFFSET_CU; + scb.scb_rfa_offset = OFFSET_RU; + obram_write(ioaddr, OFFSET_SCB, (unsigned char *)&scb, sizeof(scb)); + + set_chan_attn(ioaddr, lp->hacr); + + /* Wait for command to finish. */ + for(i = 1000; i > 0; i--) + { + obram_read(ioaddr, OFFSET_ISCP, (unsigned char *) &iscp, sizeof(iscp)); + + if(iscp.iscp_busy == (unsigned short) 0) + break; + + udelay(10); + } + + if(i <= 0) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_INFO "%s: wv_82586_start(): iscp_busy timeout.\n", + dev->name); +#endif + return -1; + } + + /* Check command completion */ + for(i = 15; i > 0; i--) + { + obram_read(ioaddr, OFFSET_SCB, (unsigned char *) &scb, sizeof(scb)); + + if (scb.scb_status == (SCB_ST_CX | SCB_ST_CNA)) + break; + + udelay(10); + } + + if (i <= 0) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_INFO "%s: wv_82586_start(): status: expected 0x%02x, got 0x%02x.\n", + dev->name, SCB_ST_CX | SCB_ST_CNA, scb.scb_status); +#endif + return -1; + } + + wv_ack(dev); + + /* Set the action command header. */ + memset(&cb, 0x00, sizeof(cb)); + cb.ac_command = AC_CFLD_EL | (AC_CFLD_CMD & acmd_diagnose); + cb.ac_link = OFFSET_CU; + obram_write(ioaddr, OFFSET_CU, (unsigned char *)&cb, sizeof(cb)); + + if(wv_synchronous_cmd(dev, "diag()") == -1) + return -1; + + obram_read(ioaddr, OFFSET_CU, (unsigned char *)&cb, sizeof(cb)); + if(cb.ac_status & AC_SFLD_FAIL) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_INFO "%s: wv_82586_start(): i82586 Self Test failed.\n", + dev->name); +#endif + return -1; + } + +#ifdef DEBUG_I82586_SHOW + wv_scb_show(ioaddr); +#endif + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: <-wv_82586_start()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * This routine does a standard configuration of the WaveLAN controller + * (i82586). + * + * This routine is a violent hack. We use the first free transmit block + * to make our configuration. In the buffer area, we create the three + * configuration commands (linked). We make the previous NOP point to + * the beginning of the buffer instead of the tx command. After, we go + * as usual to the NOP command. + * Note that only the last command (mc_set) will generate an interrupt. + * + * (called by wv_hw_reset(), wv_82586_reconfig()) + */ +static void +wv_82586_config(device * dev) +{ + net_local * lp = (net_local *) dev->priv; + u_long ioaddr = dev->base_addr; + unsigned short txblock; + unsigned short txpred; + unsigned short tx_addr; + unsigned short nop_addr; + unsigned short tbd_addr; + unsigned short cfg_addr; + unsigned short ias_addr; + unsigned short mcs_addr; + ac_tx_t tx; + ac_nop_t nop; + ac_cfg_t cfg; /* Configure action */ + ac_ias_t ias; /* IA-setup action */ + ac_mcs_t mcs; /* Multicast setup */ + struct dev_mc_list * dmi; + unsigned long x; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: ->wv_82586_config()\n", dev->name); +#endif + + x = wv_splhi(); + + /* Calculate addresses of next block and previous block */ + txblock = lp->tx_first_free; + txpred = txblock - TXBLOCKZ; + if(txpred < OFFSET_CU) + txpred += NTXBLOCKS * TXBLOCKZ; + lp->tx_first_free += TXBLOCKZ; + if(lp->tx_first_free >= OFFSET_CU + NTXBLOCKS * TXBLOCKZ) + lp->tx_first_free -= NTXBLOCKS * TXBLOCKZ; + + lp->tx_n_in_use++; + + /* Calculate addresses of the different parts of the block. */ + tx_addr = txblock; + nop_addr = tx_addr + sizeof(tx); + tbd_addr = nop_addr + sizeof(nop); + cfg_addr = tbd_addr + sizeof(tbd_t); /* beginning of the buffer */ + ias_addr = cfg_addr + sizeof(cfg); + mcs_addr = ias_addr + sizeof(ias); + + /* + * Transmit command + */ + tx.tx_h.ac_status = 0xFFFF; /* Fake completion value */ + obram_write(ioaddr, toff(ac_tx_t, tx_addr, tx_h.ac_status), + (unsigned char *) &tx.tx_h.ac_status, + sizeof(tx.tx_h.ac_status)); + + /* + * NOP command + */ + nop.nop_h.ac_status = 0; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_status), + (unsigned char *) &nop.nop_h.ac_status, + sizeof(nop.nop_h.ac_status)); + nop.nop_h.ac_link = nop_addr; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_link), + (unsigned char *) &nop.nop_h.ac_link, + sizeof(nop.nop_h.ac_link)); + + /* Create a configure action */ + memset(&cfg, 0x00, sizeof(cfg)); + +#if 0 + /* + * The default board configuration + */ + cfg.fifolim_bytecnt = 0x080c; + cfg.addrlen_mode = 0x2600; + cfg.linprio_interframe = 0x7820; /* IFS=120, ACS=2 */ + cfg.slot_time = 0xf00c; /* slottime=12 */ + cfg.hardware = 0x0008; /* tx even without CD */ + cfg.min_frame_len = 0x0040; +#endif /* 0 */ + + /* + * For Linux we invert AC_CFG_ALOC(..) so as to conform + * to the way that net packets reach us from above. + * (See also ac_tx_t.) + */ + cfg.cfg_byte_cnt = AC_CFG_BYTE_CNT(sizeof(ac_cfg_t) - sizeof(ach_t)); + cfg.cfg_fifolim = AC_CFG_FIFOLIM(8); + cfg.cfg_byte8 = AC_CFG_SAV_BF(0) | + AC_CFG_SRDY(0); + cfg.cfg_byte9 = AC_CFG_ELPBCK(0) | + AC_CFG_ILPBCK(0) | + AC_CFG_PRELEN(AC_CFG_PLEN_2) | + AC_CFG_ALOC(1) | + AC_CFG_ADDRLEN(WAVELAN_ADDR_SIZE); + cfg.cfg_byte10 = AC_CFG_BOFMET(0) | + AC_CFG_ACR(0) | + AC_CFG_LINPRIO(0); + cfg.cfg_ifs = 32; + cfg.cfg_slotl = 0; + cfg.cfg_byte13 = AC_CFG_RETRYNUM(15) | + AC_CFG_SLTTMHI(2); + cfg.cfg_byte14 = AC_CFG_FLGPAD(0) | + AC_CFG_BTSTF(0) | + AC_CFG_CRC16(0) | + AC_CFG_NCRC(0) | + AC_CFG_TNCRS(1) | + AC_CFG_MANCH(0) | + AC_CFG_BCDIS(0) | + AC_CFG_PRM(lp->promiscuous); + cfg.cfg_byte15 = AC_CFG_ICDS(0) | + AC_CFG_CDTF(0) | + AC_CFG_ICSS(0) | + AC_CFG_CSTF(0); +/* + cfg.cfg_min_frm_len = AC_CFG_MNFRM(64); +*/ + cfg.cfg_min_frm_len = AC_CFG_MNFRM(8); + + cfg.cfg_h.ac_command = (AC_CFLD_CMD & acmd_configure); + cfg.cfg_h.ac_link = ias_addr; + obram_write(ioaddr, cfg_addr, (unsigned char *)&cfg, sizeof(cfg)); + + /* Setup the MAC address */ + memset(&ias, 0x00, sizeof(ias)); + ias.ias_h.ac_command = (AC_CFLD_CMD & acmd_ia_setup); + ias.ias_h.ac_link = mcs_addr; + memcpy(&ias.ias_addr[0], (unsigned char *)&dev->dev_addr[0], sizeof(ias.ias_addr)); + obram_write(ioaddr, ias_addr, (unsigned char *)&ias, sizeof(ias)); + + /* Initialize adapter's ethernet multicast addresses */ + memset(&mcs, 0x00, sizeof(mcs)); + mcs.mcs_h.ac_command = AC_CFLD_I | (AC_CFLD_CMD & acmd_mc_setup); + mcs.mcs_h.ac_link = nop_addr; + mcs.mcs_cnt = WAVELAN_ADDR_SIZE * lp->mc_count; + obram_write(ioaddr, mcs_addr, (unsigned char *)&mcs, sizeof(mcs)); + + /* If any address to set */ + if(lp->mc_count) + { + for(dmi=dev->mc_list; dmi; dmi=dmi->next) + outsw(PIOP1(ioaddr), (u_short *) dmi->dmi_addr, + WAVELAN_ADDR_SIZE >> 1); + +#ifdef DEBUG_CONFIG_INFO + printk(KERN_DEBUG "%s: wv_82586_config(): set %d multicast addresses:\n", + dev->name, lp->mc_count); + for(dmi=dev->mc_list; dmi; dmi=dmi->next) + printk(KERN_DEBUG " %02x:%02x:%02x:%02x:%02x:%02x\n", + dmi->dmi_addr[0], dmi->dmi_addr[1], dmi->dmi_addr[2], + dmi->dmi_addr[3], dmi->dmi_addr[4], dmi->dmi_addr[5] ); +#endif + } + + /* + * Overwrite the predecessor NOP link + * so that it points to the configure action. + */ + nop_addr = txpred + sizeof(tx); + nop.nop_h.ac_status = 0; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_status), + (unsigned char *)&nop.nop_h.ac_status, + sizeof(nop.nop_h.ac_status)); + nop.nop_h.ac_link = cfg_addr; + obram_write(ioaddr, toff(ac_nop_t, nop_addr, nop_h.ac_link), + (unsigned char *) &nop.nop_h.ac_link, + sizeof(nop.nop_h.ac_link)); + + /* If watchdog not already active, activate it... */ + if(lp->watchdog.prev == (timer_list *) NULL) + { + /* set timer to expire in WATCHDOG_JIFFIES */ + lp->watchdog.expires = jiffies + WATCHDOG_JIFFIES; + add_timer(&lp->watchdog); + } + + lp->reconfig_82586 = 0; + + if(lp->tx_first_in_use == I82586NULL) + lp->tx_first_in_use = txblock; + + if(lp->tx_n_in_use < NTXBLOCKS - 1) + dev->tbusy = 0; + + wv_splx(x); + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: <-wv_82586_config()\n", dev->name); +#endif +} + +/*------------------------------------------------------------------*/ +/* + * This routine, called by wavelan_close(), gracefully stops the + * WaveLAN controller (i82586). + */ +static inline void +wv_82586_stop(device * dev) +{ + net_local * lp = (net_local *) dev->priv; + u_long ioaddr = dev->base_addr; + u_short scb_cmd; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: ->wv_82586_stop()\n", dev->name); +#endif + + /* Suspend both command unit and receive unit. */ + scb_cmd = (SCB_CMD_CUC & SCB_CMD_CUC_SUS) | (SCB_CMD_RUC & SCB_CMD_RUC_SUS); + obram_write(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *)&scb_cmd, sizeof(scb_cmd)); + set_chan_attn(ioaddr, lp->hacr); + + /* No more interrupts */ + wv_ints_off(dev); + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: <-wv_82586_stop()\n", dev->name); +#endif +} + +/*------------------------------------------------------------------*/ +/* + * Totally reset the WaveLAN and restart it. + * Performs the following actions: + * 1. A power reset (reset DMA) + * 2. Initialize the radio modem (using wv_mmc_init) + * 3. Reset & Configure LAN controller (using wv_82586_start) + * 4. Start the LAN controller's command unit + * 5. Start the LAN controller's receive unit + */ +static int +wv_hw_reset(device * dev) +{ + net_local * lp = (net_local *)dev->priv; + u_long ioaddr = dev->base_addr; + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: ->wv_hw_reset(dev=0x%x)\n", dev->name, + (unsigned int)dev); +#endif + + /* If watchdog was activated, kill it! */ + if(lp->watchdog.prev != (timer_list *) NULL) + del_timer(&lp->watchdog); + + /* Increase the number of resets done */ + lp->nresets++; + + wv_hacr_reset(ioaddr); + lp->hacr = HACR_DEFAULT; + + if((wv_mmc_init(dev) < 0) || + (wv_82586_start(dev) < 0)) + return -1; + + /* Enable the card to send interrupts */ + wv_ints_on(dev); + + /* Start card functions */ + if((wv_ru_start(dev) < 0) || + (wv_cu_start(dev) < 0)) + return -1; + + /* Finish configuration */ + wv_82586_config(dev); + +#ifdef DEBUG_CONFIG_TRACE + printk(KERN_DEBUG "%s: <-wv_hw_reset()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Check if there is a WaveLAN at the specific base address. + * As a side effect, this reads the MAC address. + * (called in wavelan_probe() and init_module()) + */ +static int +wv_check_ioaddr(u_long ioaddr, + u_char * mac) +{ + int i; /* Loop counter */ + + /* Check if the base address if available */ + if(check_region(ioaddr, sizeof(ha_t))) + return EADDRINUSE; /* ioaddr already used... */ + + /* Reset host interface */ + wv_hacr_reset(ioaddr); + + /* Read the MAC address from the parameter storage area */ + psa_read(ioaddr, HACR_DEFAULT, psaoff(0, psa_univ_mac_addr), + mac, 6); + + /* + * Check the first three octets of the address for the manufacturer's code. + * Note: If this can't find your WaveLAN card, you've got a + * non-NCR/AT&T/Lucent ISA card. See wavelan.p.h for details on + * how to configure your card. + */ + for(i = 0; i < (sizeof(MAC_ADDRESSES) / sizeof(char) / 3); i++) + if((mac[0] == MAC_ADDRESSES[i][0]) && + (mac[1] == MAC_ADDRESSES[i][1]) && + (mac[2] == MAC_ADDRESSES[i][2])) + return 0; + +#ifdef DEBUG_CONFIG_INFO + printk(KERN_WARNING "WaveLAN (0x%3X): your MAC address might be: %02X:%02X:%02X.\n", + ioaddr, mac[0], mac[1], mac[2]); +#endif + return ENODEV; +} + +/************************ INTERRUPT HANDLING ************************/ + +/* + * This function is the interrupt handler for the WaveLAN card. This + * routine will be called whenever: + */ +static void +wavelan_interrupt(int irq, + void * dev_id, + struct pt_regs * regs) +{ + device * dev; + u_long ioaddr; + net_local * lp; + u_short hasr; + u_short status; + u_short ack_cmd; + + if((dev = (device *) (irq2dev_map[irq])) == (device *) NULL) + { +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_WARNING "wavelan_interrupt(): irq %d for unknown device.\n", + irq); +#endif + return; + } + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: ->wavelan_interrupt()\n", dev->name); +#endif + + lp = (net_local *) dev->priv; + ioaddr = dev->base_addr; + + /* Prevent reentrance. What should we do here? */ +#ifdef DEBUG_INTERRUPT_ERROR + if(dev->interrupt) + printk(KERN_INFO "%s: wavelan_interrupt(): Re-entering the interrupt handler.\n", + dev->name); +#endif + dev->interrupt = 1; + + if((hasr = hasr_read(ioaddr)) & HASR_MMC_INTR) + { + u_char dce_status; + + /* + * Interrupt from the modem management controller. + * This will clear it -- ignored for now. + */ + mmc_read(ioaddr, mmroff(0, mmr_dce_status), &dce_status, sizeof(dce_status)); +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wavelan_interrupt(): unexpected mmc interrupt: status 0x%04x.\n", + dev->name, dce_status); +#endif + } + + if((hasr & HASR_82586_INTR) == 0) + { + dev->interrupt = 0; +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wavelan_interrupt(): interrupt not coming from i82586\n", + dev->name); +#endif + return; + } + + /* Read interrupt data. */ + obram_read(ioaddr, scboff(OFFSET_SCB, scb_status), + (unsigned char *) &status, sizeof(status)); + + /* + * Acknowledge the interrupt(s). + */ + ack_cmd = status & SCB_ST_INT; + obram_write(ioaddr, scboff(OFFSET_SCB, scb_command), + (unsigned char *) &ack_cmd, sizeof(ack_cmd)); + set_chan_attn(ioaddr, lp->hacr); + +#ifdef DEBUG_INTERRUPT_INFO + printk(KERN_DEBUG "%s: wavelan_interrupt(): status 0x%04x.\n", + dev->name, status); +#endif + + /* Command completed. */ + if((status & SCB_ST_CX) == SCB_ST_CX) + { +#ifdef DEBUG_INTERRUPT_INFO + printk(KERN_DEBUG "%s: wavelan_interrupt(): command completed.\n", + dev->name); +#endif + wv_complete(dev, ioaddr, lp); + + /* If watchdog was activated, kill it ! */ + if(lp->watchdog.prev != (timer_list *) NULL) + del_timer(&lp->watchdog); + if(lp->tx_n_in_use > 0) + { + /* set timer to expire in WATCHDOG_JIFFIES */ + lp->watchdog.expires = jiffies + WATCHDOG_JIFFIES; + add_timer(&lp->watchdog); + } + } + + /* Frame received. */ + if((status & SCB_ST_FR) == SCB_ST_FR) + { +#ifdef DEBUG_INTERRUPT_INFO + printk(KERN_DEBUG "%s: wavelan_interrupt(): received packet.\n", + dev->name); +#endif + wv_receive(dev); + } + + /* Check the state of the command unit. */ + if(((status & SCB_ST_CNA) == SCB_ST_CNA) || + (((status & SCB_ST_CUS) != SCB_ST_CUS_ACTV) && dev->start)) + { +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wavelan_interrupt(): CU inactive -- restarting\n", + dev->name); +#endif + wv_hw_reset(dev); + } + + /* Check the state of the command unit. */ + if(((status & SCB_ST_RNR) == SCB_ST_RNR) || + (((status & SCB_ST_RUS) != SCB_ST_RUS_RDY) && dev->start)) + { +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wavelan_interrupt(): RU not ready -- restarting\n", + dev->name); +#endif + wv_hw_reset(dev); + } + + dev->interrupt = 0; + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: <-wavelan_interrupt()\n", dev->name); +#endif +} + +/*------------------------------------------------------------------*/ +/* + * Watchdog: when we start a transmission, we set a timer in the + * kernel. If the transmission completes, this timer is disabled. If + * the timer expires, we try to unlock the hardware. + * + * Note: this watchdog doesn't work on the same principle as the + * watchdog in the previous version of the ISA driver. I made it this + * way because the overhead of add_timer() and del_timer() is nothing + * and because it avoids calling the watchdog, saving some CPU time. + */ +static void +wavelan_watchdog(u_long a) +{ + device * dev; + net_local * lp; + u_long ioaddr; + unsigned long x; + unsigned int nreaped; + + dev = (device *) a; + ioaddr = dev->base_addr; + lp = (net_local *) dev->priv; + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: ->wavelan_watchdog()\n", dev->name); +#endif + +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wavelan_watchdog: watchdog timer expired\n", + dev->name); +#endif + + x = wv_splhi(); + + dev = (device *) a; + ioaddr = dev->base_addr; + lp = (net_local *) dev->priv; + + if(lp->tx_n_in_use <= 0) + { + wv_splx(x); + return; + } + + nreaped = wv_complete(dev, ioaddr, lp); + +#ifdef DEBUG_INTERRUPT_INFO + printk(KERN_DEBUG "%s: wavelan_watchdog(): %d reaped, %d remain.\n", + dev->name, nreaped, lp->tx_n_in_use); +#endif + +#ifdef DEBUG_PSA_SHOW + { + psa_t psa; + psa_read(dev, 0, (unsigned char *) &psa, sizeof(psa)); + wv_psa_show(&psa); + } +#endif +#ifdef DEBUG_MMC_SHOW + wv_mmc_show(dev); +#endif +#ifdef DEBUG_I82586_SHOW + wv_cu_show(dev); +#endif + + /* If no buffer has been freed */ + if(nreaped == 0) + { +#ifdef DEBUG_INTERRUPT_ERROR + printk(KERN_INFO "%s: wavelan_watchdog(): cleanup failed, trying reset\n", + dev->name); +#endif + wv_hw_reset(dev); + } + else + /* Reset watchdog for next transmission. */ + if(lp->tx_n_in_use > 0) + { + /* set timer to expire in WATCHDOG_JIFFIES */ + lp->watchdog.expires = jiffies + WATCHDOG_JIFFIES; + add_timer(&lp->watchdog); + } + + wv_splx(x); + +#ifdef DEBUG_INTERRUPT_TRACE + printk(KERN_DEBUG "%s: <-wavelan_watchdog()\n", dev->name); +#endif +} + +/********************* CONFIGURATION CALLBACKS *********************/ +/* + * Here are the functions called by the Linux networking code (NET3) + * for initialization, configuration and deinstallations of the + * WaveLAN ISA hardware. + */ + +/*------------------------------------------------------------------*/ +/* + * Configure and start up the WaveLAN PCMCIA adaptor. + * Called by NET3 when it "open" the device. + */ +static int +wavelan_open(device * dev) +{ + u_long x; + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: ->wavelan_open(dev=0x%x)\n", dev->name, + (unsigned int) dev); +#endif + + /* Check irq */ + if(dev->irq == 0) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_WARNING "%s: wavelan_open(): no IRQ\n", dev->name); +#endif + return -ENXIO; + } + + if((irq2dev_map[dev->irq] != (device *) NULL) || + /* This is always true, but avoid the false IRQ. */ + ((irq2dev_map[dev->irq] = dev) == (device *) NULL) || + (request_irq(dev->irq, &wavelan_interrupt, 0, "WaveLAN", NULL) != 0)) + { + irq2dev_map[dev->irq] = (device *) NULL; +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_WARNING "%s: wavelan_open(): invalid IRQ\n", dev->name); +#endif + return -EAGAIN; + } + + x = wv_splhi(); + if(wv_hw_reset(dev) != -1) + { + dev->interrupt = 0; + dev->start = 1; + } + else + { + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = (device *) NULL; +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_INFO "%s: wavelan_open(): impossible to start the card\n", + dev->name); +#endif + return -EAGAIN; + } + wv_splx(x); + + MOD_INC_USE_COUNT; + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: <-wavelan_open()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Shut down the WaveLAN ISA card. + * Called by NET3 when it "closes" the device. + */ +static int +wavelan_close(device * dev) +{ + net_local * lp = (net_local *)dev->priv; + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: ->wavelan_close(dev=0x%x)\n", dev->name, + (unsigned int) dev); +#endif + + /* Not do the job twice. */ + if(dev->start == 0) + return 0; + + dev->tbusy = 1; + dev->start = 0; + + /* If watchdog was activated, kill it! */ + if(lp->watchdog.prev != (timer_list *) NULL) + del_timer(&lp->watchdog); + + /* + * Flush the Tx and disable Rx. + */ + wv_82586_stop(dev); + + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = (device *) NULL; + + MOD_DEC_USE_COUNT; + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: <-wavelan_close()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Probe an I/O address, and if the WaveLAN is there configure the + * device structure + * (called by wavelan_probe() & via init_module()) + */ +static int +wavelan_config(device * dev) +{ + u_long ioaddr = dev->base_addr; + u_char irq_mask; + int irq; + net_local * lp; + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: ->wavelan_config(dev=0x%x, ioaddr=0x%x)\n", dev->name, + (unsigned int)dev, ioaddr); +#endif + + /* Check irq arg on command line */ + if(dev->irq != 0) + { + irq_mask = wv_irq_to_psa(dev->irq); + + if(irq_mask == 0) + { +#ifdef DEBUG_CONFIG_ERROR + printk(KERN_WARNING "%s: wavelan_config(): invalid irq %d -- ignored.\n", + dev->name, dev->irq); +#endif + dev->irq = 0; + } + else + { +#ifdef DEBUG_CONFIG_INFO + printk(KERN_DEBUG "%s: wavelan_config(): changing irq to %d\n", + dev->name, dev->irq); +#endif + psa_write(ioaddr, HACR_DEFAULT, + psaoff(0, psa_int_req_no), &irq_mask, 1); + wv_hacr_reset(ioaddr); + } + } + + psa_read(ioaddr, HACR_DEFAULT, psaoff(0, psa_int_req_no), &irq_mask, 1); + if((irq = wv_psa_to_irq(irq_mask)) == -1) + { +#ifdef DEBUG_CONFIG_ERROR + printk(KERN_INFO "%s: wavelan_config(): could not wavelan_map_irq(%d).\n", + dev->name, irq_mask); +#endif + return EAGAIN; + } + + dev->irq = irq; + + request_region(ioaddr, sizeof(ha_t), "wavelan"); + + dev->mem_start = 0x0000; + dev->mem_end = 0x0000; + dev->if_port = 0; + + /* Initialize device structures */ + dev->priv = kmalloc(sizeof(net_local), GFP_KERNEL); + if(dev->priv == NULL) + return -ENOMEM; + memset(dev->priv, 0x00, sizeof(net_local)); + lp = (net_local *)dev->priv; + + /* Back link to the device structure. */ + lp->dev = dev; + /* Add the device at the beginning of the linked list. */ + lp->next = wavelan_list; + wavelan_list = lp; + + lp->hacr = HACR_DEFAULT; + + lp->watchdog.function = wavelan_watchdog; + lp->watchdog.data = (unsigned long) dev; + lp->promiscuous = 0; + lp->mc_count = 0; + + /* + * Fill in the fields of the device structure + * with Ethernet-generic values. + */ + ether_setup(dev); + + dev->open = wavelan_open; + dev->stop = wavelan_close; + dev->hard_start_xmit = wavelan_packet_xmit; + dev->get_stats = wavelan_get_stats; + dev->set_multicast_list = &wavelan_set_multicast_list; + dev->set_mac_address = &wavelan_set_mac_address; + +#ifdef WIRELESS_EXT /* If wireless extension exist in the kernel */ + dev->do_ioctl = wavelan_ioctl; + dev->get_wireless_stats = wavelan_get_wireless_stats; +#endif + + dev->mtu = WAVELAN_MTU; + + /* Display nice info */ + wv_init_info(dev); + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: <-wavelan_config()\n", dev->name); +#endif + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Check for a network adaptor of this type. Return '0' iff one + * exists. (There seem to be different interpretations of + * the initial value of dev->base_addr. + * We follow the example in drivers/net/ne.c.) + * (called in "Space.c") + * As this function is called outside the wavelan module, it should be + * declared extern, but it seem to cause troubles... + */ +/* extern */ int +wavelan_probe(device * dev) +{ + short base_addr; + mac_addr mac; /* MAC address (check WaveLAN existence) */ + int i; + int r; + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: ->wavelan_probe(dev=0x%x (base_addr=0x%x))\n", + dev->name, (unsigned int)dev, (unsigned int)dev->base_addr); +#endif + +#ifdef STRUCT_CHECK + if (wv_struct_check() != (char *) NULL) + { + printk(KERN_WARNING "%s: wavelan_probe(): structure/compiler botch: \"%s\"\n", + dev->name, wv_struct_check()); + return ENODEV; + } +#endif /* STRUCT_CHECK */ + + /* Check the value of the command line parameter for base address */ + base_addr = dev->base_addr; + + /* Don't probe at all. */ + if(base_addr < 0) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_WARNING "%s: wavelan_probe(): invalid base address\n", + dev->name); +#endif + return ENXIO; + } + + /* Check a single specified location. */ + if(base_addr > 0x100) + { + /* Check if the is something at this base address */ + if((r = wv_check_ioaddr(base_addr, mac)) == 0) + { + memcpy(dev->dev_addr, mac, 6); /* Copy MAC address */ + r = wavelan_config(dev); + } + +#ifdef DEBUG_CONFIG_INFO + if(r != 0) + printk(KERN_DEBUG "%s: wavelan_probe(): no device at specified base address (0x%X) or address already in use\n", + dev->name, base_addr); +#endif + +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: <-wavelan_probe()\n", dev->name); +#endif + return r; + } + + /* Scan all possible addresses of the WaveLAN hardware */ + for(i = 0; i < NELS(iobase); i++) + { + /* Check whether there is something at this base address */ + if(wv_check_ioaddr(iobase[i], mac) == 0) + { + dev->base_addr = iobase[i]; /* Copy base address. */ + memcpy(dev->dev_addr, mac, 6); /* Copy MAC address. */ + if(wavelan_config(dev) == 0) + { +#ifdef DEBUG_CALLBACK_TRACE + printk(KERN_DEBUG "%s: <-wavelan_probe()\n", dev->name); +#endif + return 0; + } + } + } + + /* We may have touch base_addr: another driver may not like it. */ + dev->base_addr = base_addr; + +#ifdef DEBUG_CONFIG_INFO + printk(KERN_DEBUG "%s: wavelan_probe(): no device found\n", + dev->name); +#endif + + return ENODEV; +} + +/****************************** MODULE ******************************/ +/* + * Module entry point: insertion & removal + */ + +#ifdef MODULE +/*------------------------------------------------------------------*/ +/* + * Insertion of the module. + * I'm now quite proud of the multi-device support. + */ +int +init_module(void) +{ + mac_addr mac; /* MAC address (check WaveLAN existence) */ + int ret = 0; + int i; + +#ifdef DEBUG_MODULE_TRACE + printk(KERN_DEBUG "-> init_module()\n"); +#endif + + /* If probing is asked */ + if(io[0] == 0) + { +#ifdef DEBUG_CONFIG_ERRORS + printk(KERN_WARNING "WaveLAN init_module(): doing device probing (bad !)\n"); + printk(KERN_WARNING "Specify base addresses while loading module to correct the problem\n"); +#endif + + /* Copy the basic set of address to be probed. */ + for(i = 0; i < NELS(iobase); i++) + io[i] = iobase[i]; + } + + + /* Loop on all possible base addresses */ + i = -1; + while((io[++i] != 0) && (i < NELS(io))) + { + /* Check if there is something at this base address. */ + if(wv_check_ioaddr(io[i], mac) == 0) + { + device * dev; + + /* Create device and set basics args */ + dev = kmalloc(sizeof(struct device), GFP_KERNEL); + memset(dev, 0x00, sizeof(struct device)); + dev->name = name[i]; + dev->base_addr = io[i]; + dev->irq = irq[i]; + dev->init = &wavelan_config; + memcpy(dev->dev_addr, mac, 6); /* Copy MAC address */ + + /* Try to create the device */ + if(register_netdev(dev) != 0) + { + /* DeAllocate everything */ + /* Note : if dev->priv is mallocated, there is no way to fail */ + kfree_s(dev, sizeof(struct device)); + ret = -EIO; + } + } /* if there is something at the address */ + } /* Loop on all addresses. */ + +#ifdef DEBUG_CONFIG_ERRORS + if(wavelan_list == (net_local *) NULL) + printk(KERN_WARNING "WaveLAN init_module(): no device found\n"); +#endif + +#ifdef DEBUG_MODULE_TRACE + printk(KERN_DEBUG "<- init_module()\n"); +#endif + return ret; +} + +/*------------------------------------------------------------------*/ +/* + * Removal of the module + */ +void +cleanup_module(void) +{ +#ifdef DEBUG_MODULE_TRACE + printk(KERN_DEBUG "-> cleanup_module()\n"); +#endif + + /* Loop on all devices and release them. */ + while(wavelan_list != (net_local *) NULL) + { + device * dev = wavelan_list->dev; + +#ifdef DEBUG_CONFIG_INFO + printk(KERN_DEBUG "%s: cleanup_module(): removing device at 0x%x\n", + dev->name, (unsigned int) dev); +#endif + + /* Release the ioport-region. */ + release_region(dev->base_addr, sizeof(ha_t)); + + /* Definitely remove the device. */ + unregister_netdev(dev); + + /* Unlink the device. */ + wavelan_list = wavelan_list->next; + + /* Free pieces. */ + kfree_s(dev->priv, sizeof(struct net_local)); + kfree_s(dev, sizeof(struct device)); + } + +#ifdef DEBUG_MODULE_TRACE + printk(KERN_DEBUG "<- cleanup_module()\n"); +#endif +} +#endif /* MODULE */ + +/* + * This software may only be used and distributed + * according to the terms of the GNU Public License. + * + * This software was developed as a component of the + * Linux operating system. + * It is based on other device drivers and information + * either written or supplied by: + * Ajay Bakre (bakre@paul.rutgers.edu), + * Donald Becker (becker@cesdis.gsfc.nasa.gov), + * Loeke Brederveld (Loeke.Brederveld@Utrecht.NCR.com), + * Anders Klemets (klemets@it.kth.se), + * Vladimir V. Kolpakov (w@stier.koenig.ru), + * Marc Meertens (Marc.Meertens@Utrecht.NCR.com), + * Pauline Middelink (middelin@polyware.iaf.nl), + * Robert Morris (rtm@das.harvard.edu), + * Jean Tourrilhes (jt@hplb.hpl.hp.com), + * Girish Welling (welling@paul.rutgers.edu), + * + * Thanks go also to: + * James Ashton (jaa101@syseng.anu.edu.au), + * Alan Cox (iialan@iiit.swan.ac.uk), + * Allan Creighton (allanc@cs.usyd.edu.au), + * Matthew Geier (matthew@cs.usyd.edu.au), + * Remo di Giovanni (remo@cs.usyd.edu.au), + * Eckhard Grah (grah@wrcs1.urz.uni-wuppertal.de), + * Vipul Gupta (vgupta@cs.binghamton.edu), + * Mark Hagan (mhagan@wtcpost.daytonoh.NCR.COM), + * Tim Nicholson (tim@cs.usyd.edu.au), + * Ian Parkin (ian@cs.usyd.edu.au), + * John Rosenberg (johnr@cs.usyd.edu.au), + * George Rossi (george@phm.gov.au), + * Arthur Scott (arthur@cs.usyd.edu.au), + * Peter Storey, + * for their assistance and advice. + * + * Please send bug reports, updates, comments to: + * + * Bruce Janson Email: bruce@cs.usyd.edu.au + * Basser Department of Computer Science Phone: +61-2-9351-3423 + * University of Sydney, N.S.W., 2006, AUSTRALIA Fax: +61-2-9351-3838 + */ diff --git a/linux/src/drivers/net/wavelan.h b/linux/src/drivers/net/wavelan.h new file mode 100644 index 00000000..2e92c798 --- /dev/null +++ b/linux/src/drivers/net/wavelan.h @@ -0,0 +1,346 @@ +/* + * Wavelan ISA driver + * + * Jean II - HPLB '96 + * + * Reorganisation and extension of the driver. + * Original copyrigth follow. See wavelan.p.h for details. + * + * This file contain the declarations of the Wavelan hardware. Note that + * the Wavelan ISA include a i82586 controler (see definitions in + * file i82586.h). + * + * The main difference between the ISA hardware and the pcmcia one is + * the Ethernet Controler (i82586 instead of i82593). + * The i82586 allow multiple transmit buffers. The PSA need to be accessed + * through the host interface. + */ + +#ifndef _WAVELAN_H +#define _WAVELAN_H + +/* The detection of the wavelan card is made by reading the MAC + * address from the card and checking it. If you have a non AT&T + * product (OEM, like DEC RoamAbout, or Digital Ocean, Epson, ...), + * you might need to modify this part to accomodate your hardware... + */ +const char MAC_ADDRESSES[][3] = +{ + { 0x08, 0x00, 0x0E }, /* AT&T Wavelan (standard) & DEC RoamAbout */ + { 0x08, 0x00, 0x6A }, /* AT&T Wavelan (alternate) */ + /* Add your card here and send me the patch ! */ +}; + +#define WAVELAN_ADDR_SIZE 6 /* Size of a MAC address */ + +#define WAVELAN_MTU 1500 /* Maximum size of WaveLAN packet */ + +#define MAXDATAZ (WAVELAN_ADDR_SIZE + WAVELAN_ADDR_SIZE + 2 + WAVELAN_MTU) + +/*************************** PC INTERFACE ****************************/ + +/* + * Host Adaptor structure. + * (base is board port address). + */ +typedef union hacs_u hacs_u; +union hacs_u +{ + unsigned short hu_command; /* Command register */ +#define HACR_RESET 0x0001 /* Reset board */ +#define HACR_CA 0x0002 /* Set Channel Attention for 82586 */ +#define HACR_16BITS 0x0004 /* 16 bits operation (0 => 8bits) */ +#define HACR_OUT0 0x0008 /* General purpose output pin 0 */ + /* not used - must be 1 */ +#define HACR_OUT1 0x0010 /* General purpose output pin 1 */ + /* not used - must be 1 */ +#define HACR_82586_INT_ENABLE 0x0020 /* Enable 82586 interrupts */ +#define HACR_MMC_INT_ENABLE 0x0040 /* Enable MMC interrupts */ +#define HACR_INTR_CLR_ENABLE 0x0080 /* Enable interrupt status read/clear */ + unsigned short hu_status; /* Status Register */ +#define HASR_82586_INTR 0x0001 /* Interrupt request from 82586 */ +#define HASR_MMC_INTR 0x0002 /* Interrupt request from MMC */ +#define HASR_MMC_BUSY 0x0004 /* MMC busy indication */ +#define HASR_PSA_BUSY 0x0008 /* LAN parameter storage area busy */ +}; + +typedef struct ha_t ha_t; +struct ha_t +{ + hacs_u ha_cs; /* Command and status registers */ +#define ha_command ha_cs.hu_command +#define ha_status ha_cs.hu_status + unsigned short ha_mmcr; /* Modem Management Ctrl Register */ + unsigned short ha_pior0; /* Program I/O Address Register Port 0 */ + unsigned short ha_piop0; /* Program I/O Port 0 */ + unsigned short ha_pior1; /* Program I/O Address Register Port 1 */ + unsigned short ha_piop1; /* Program I/O Port 1 */ + unsigned short ha_pior2; /* Program I/O Address Register Port 2 */ + unsigned short ha_piop2; /* Program I/O Port 2 */ +}; + +#define HA_SIZE 16 + +#define hoff(p,f) (unsigned short)((void *)(&((ha_t *)((void *)0 + (p)))->f) - (void *)0) +#define HACR(p) hoff(p, ha_command) +#define HASR(p) hoff(p, ha_status) +#define MMCR(p) hoff(p, ha_mmcr) +#define PIOR0(p) hoff(p, ha_pior0) +#define PIOP0(p) hoff(p, ha_piop0) +#define PIOR1(p) hoff(p, ha_pior1) +#define PIOP1(p) hoff(p, ha_piop1) +#define PIOR2(p) hoff(p, ha_pior2) +#define PIOP2(p) hoff(p, ha_piop2) + +/* + * Program I/O Mode Register values. + */ +#define STATIC_PIO 0 /* Mode 1: static mode */ + /* RAM access ??? */ +#define AUTOINCR_PIO 1 /* Mode 2: auto increment mode */ + /* RAM access ??? */ +#define AUTODECR_PIO 2 /* Mode 3: auto decrement mode */ + /* RAM access ??? */ +#define PARAM_ACCESS_PIO 3 /* Mode 4: LAN parameter access mode */ + /* Parameter access. */ +#define PIO_MASK 3 /* register mask */ +#define PIOM(cmd,piono) ((u_short)cmd << 10 << (piono * 2)) + +#define HACR_DEFAULT (HACR_OUT0 | HACR_OUT1 | HACR_16BITS | PIOM(STATIC_PIO, 0) | PIOM(AUTOINCR_PIO, 1) | PIOM(PARAM_ACCESS_PIO, 2)) +#define HACR_INTRON (HACR_82586_INT_ENABLE | HACR_MMC_INT_ENABLE | HACR_INTR_CLR_ENABLE) + +/************************** MEMORY LAYOUT **************************/ + +/* + * Onboard 64k RAM layout. + * (Offsets from 0x0000.) + */ +#define OFFSET_RU 0x0000 /* 75 % memory */ +#define OFFSET_CU 0xC000 /* 25 % memory */ +#define OFFSET_SCB (OFFSET_ISCP - sizeof(scb_t)) +#define OFFSET_ISCP (OFFSET_SCP - sizeof(iscp_t)) +#define OFFSET_SCP I82586_SCP_ADDR + +#define RXBLOCKZ (sizeof(fd_t) + sizeof(rbd_t) + MAXDATAZ) +#define TXBLOCKZ (sizeof(ac_tx_t) + sizeof(ac_nop_t) + sizeof(tbd_t) + MAXDATAZ) + +#define NRXBLOCKS ((OFFSET_CU - OFFSET_RU) / RXBLOCKZ) +#define NTXBLOCKS ((OFFSET_SCB - OFFSET_CU) / TXBLOCKZ) + +/********************** PARAMETER STORAGE AREA **********************/ + +/* + * Parameter Storage Area (PSA). + */ +typedef struct psa_t psa_t; +struct psa_t +{ + unsigned char psa_io_base_addr_1; /* [0x00] Base address 1 ??? */ + unsigned char psa_io_base_addr_2; /* [0x01] Base address 2 */ + unsigned char psa_io_base_addr_3; /* [0x02] Base address 3 */ + unsigned char psa_io_base_addr_4; /* [0x03] Base address 4 */ + unsigned char psa_rem_boot_addr_1; /* [0x04] Remote Boot Address 1 */ + unsigned char psa_rem_boot_addr_2; /* [0x05] Remote Boot Address 2 */ + unsigned char psa_rem_boot_addr_3; /* [0x06] Remote Boot Address 3 */ + unsigned char psa_holi_params; /* [0x07] HOst Lan Interface (HOLI) Parameters */ + unsigned char psa_int_req_no; /* [0x08] Interrupt Request Line */ + unsigned char psa_unused0[7]; /* [0x09-0x0F] unused */ + + unsigned char psa_univ_mac_addr[WAVELAN_ADDR_SIZE]; /* [0x10-0x15] Universal (factory) MAC Address */ + unsigned char psa_local_mac_addr[WAVELAN_ADDR_SIZE]; /* [0x16-1B] Local MAC Address */ + unsigned char psa_univ_local_sel; /* [0x1C] Universal Local Selection */ +#define PSA_UNIVERSAL 0 /* Universal (factory) */ +#define PSA_LOCAL 1 /* Local */ + unsigned char psa_comp_number; /* [0x1D] Compatability Number: */ +#define PSA_COMP_PC_AT_915 0 /* PC-AT 915 MHz */ +#define PSA_COMP_PC_MC_915 1 /* PC-MC 915 MHz */ +#define PSA_COMP_PC_AT_2400 2 /* PC-AT 2.4 GHz */ +#define PSA_COMP_PC_MC_2400 3 /* PC-MC 2.4 GHz */ +#define PSA_COMP_PCMCIA_915 4 /* PCMCIA 915 MHz or 2.0 */ + unsigned char psa_thr_pre_set; /* [0x1E] Modem Threshold Preset */ + unsigned char psa_feature_select; /* [0x1F] Call code required (1=on) */ +#define PSA_FEATURE_CALL_CODE 0x01 /* Call code required (Japan) */ + unsigned char psa_subband; /* [0x20] Subband */ +#define PSA_SUBBAND_915 0 /* 915 MHz or 2.0 */ +#define PSA_SUBBAND_2425 1 /* 2425 MHz */ +#define PSA_SUBBAND_2460 2 /* 2460 MHz */ +#define PSA_SUBBAND_2484 3 /* 2484 MHz */ +#define PSA_SUBBAND_2430_5 4 /* 2430.5 MHz */ + unsigned char psa_quality_thr; /* [0x21] Modem Quality Threshold */ + unsigned char psa_mod_delay; /* [0x22] Modem Delay ??? (reserved) */ + unsigned char psa_nwid[2]; /* [0x23-0x24] Network ID */ + unsigned char psa_nwid_select; /* [0x25] Network ID Select On Off */ + unsigned char psa_encryption_select; /* [0x26] Encryption On Off */ + unsigned char psa_encryption_key[8]; /* [0x27-0x2E] Encryption Key */ + unsigned char psa_databus_width; /* [0x2F] AT bus width select 8/16 */ + unsigned char psa_call_code[8]; /* [0x30-0x37] (Japan) Call Code */ + unsigned char psa_nwid_prefix[2]; /* [0x38-0x39] Roaming domain */ + unsigned char psa_reserved[2]; /* [0x3A-0x3B] Reserved - fixed 00 */ + unsigned char psa_conf_status; /* [0x3C] Conf Status, bit 0=1:config*/ + unsigned char psa_crc[2]; /* [0x3D] CRC-16 over PSA */ + unsigned char psa_crc_status; /* [0x3F] CRC Valid Flag */ +}; + +#define PSA_SIZE 64 + +/* Calculate offset of a field in the above structure + * Warning : only even addresses are used */ +#define psaoff(p,f) ((unsigned short) ((void *)(&((psa_t *) ((void *) NULL + (p)))->f) - (void *) NULL)) + +/******************** MODEM MANAGEMENT INTERFACE ********************/ + +/* + * Modem Management Controller (MMC) write structure. + */ +typedef struct mmw_t mmw_t; +struct mmw_t +{ + unsigned char mmw_encr_key[8]; /* encryption key */ + unsigned char mmw_encr_enable; /* enable/disable encryption */ +#define MMW_ENCR_ENABLE_MODE 0x02 /* Mode of security option */ +#define MMW_ENCR_ENABLE_EN 0x01 /* Enable security option */ + unsigned char mmw_unused0[1]; /* unused */ + unsigned char mmw_des_io_invert; /* Encryption option */ +#define MMW_DES_IO_INVERT_RES 0x0F /* Reserved */ +#define MMW_DES_IO_INVERT_CTRL 0xF0 /* Control ??? (set to 0) */ + unsigned char mmw_unused1[5]; /* unused */ + unsigned char mmw_loopt_sel; /* looptest selection */ +#define MMW_LOOPT_SEL_DIS_NWID 0x40 /* disable NWID filtering */ +#define MMW_LOOPT_SEL_INT 0x20 /* activate Attention Request */ +#define MMW_LOOPT_SEL_LS 0x10 /* looptest w/o collision avoidance */ +#define MMW_LOOPT_SEL_LT3A 0x08 /* looptest 3a */ +#define MMW_LOOPT_SEL_LT3B 0x04 /* looptest 3b */ +#define MMW_LOOPT_SEL_LT3C 0x02 /* looptest 3c */ +#define MMW_LOOPT_SEL_LT3D 0x01 /* looptest 3d */ + unsigned char mmw_jabber_enable; /* jabber timer enable */ + /* Abort transmissions > 200 ms */ + unsigned char mmw_freeze; /* freeze / unfreeeze signal level */ + /* 0 : signal level & qual updated for every new message, 1 : frozen */ + unsigned char mmw_anten_sel; /* antenna selection */ +#define MMW_ANTEN_SEL_SEL 0x01 /* direct antenna selection */ +#define MMW_ANTEN_SEL_ALG_EN 0x02 /* antenna selection algo. enable */ + unsigned char mmw_ifs; /* inter frame spacing */ + /* min time between transmission in bit periods (.5 us) - bit 0 ignored */ + unsigned char mmw_mod_delay; /* modem delay (synchro) */ + unsigned char mmw_jam_time; /* jamming time (after collision) */ + unsigned char mmw_unused2[1]; /* unused */ + unsigned char mmw_thr_pre_set; /* level threshold preset */ + /* Discard all packet with signal < this value (4) */ + unsigned char mmw_decay_prm; /* decay parameters */ + unsigned char mmw_decay_updat_prm; /* decay update parameterz */ + unsigned char mmw_quality_thr; /* quality (z-quotient) threshold */ + /* Discard all packet with quality < this value (3) */ + unsigned char mmw_netw_id_l; /* NWID low order byte */ + unsigned char mmw_netw_id_h; /* NWID high order byte */ + /* Network ID or Domain : create virtual net on the air */ + + /* 2.0 Hardware extension - frequency selection support */ + unsigned char mmw_mode_select; /* for analog tests (set to 0) */ + unsigned char mmw_unused3[1]; /* unused */ + unsigned char mmw_fee_ctrl; /* frequency eeprom control */ +#define MMW_FEE_CTRL_PRE 0x10 /* Enable protected instructions */ +#define MMW_FEE_CTRL_DWLD 0x08 /* Download eeprom to mmc */ +#define MMW_FEE_CTRL_CMD 0x07 /* EEprom commands : */ +#define MMW_FEE_CTRL_READ 0x06 /* Read */ +#define MMW_FEE_CTRL_WREN 0x04 /* Write enable */ +#define MMW_FEE_CTRL_WRITE 0x05 /* Write data to address */ +#define MMW_FEE_CTRL_WRALL 0x04 /* Write data to all addresses */ +#define MMW_FEE_CTRL_WDS 0x04 /* Write disable */ +#define MMW_FEE_CTRL_PRREAD 0x16 /* Read addr from protect register */ +#define MMW_FEE_CTRL_PREN 0x14 /* Protect register enable */ +#define MMW_FEE_CTRL_PRCLEAR 0x17 /* Unprotect all registers */ +#define MMW_FEE_CTRL_PRWRITE 0x15 /* Write addr in protect register */ +#define MMW_FEE_CTRL_PRDS 0x14 /* Protect register disable */ + /* Never issue this command (PRDS) : it's irreversible !!! */ + + unsigned char mmw_fee_addr; /* EEprom address */ +#define MMW_FEE_ADDR_CHANNEL 0xF0 /* Select the channel */ +#define MMW_FEE_ADDR_OFFSET 0x0F /* Offset in channel data */ +#define MMW_FEE_ADDR_EN 0xC0 /* FEE_CTRL enable operations */ +#define MMW_FEE_ADDR_DS 0x00 /* FEE_CTRL disable operations */ +#define MMW_FEE_ADDR_ALL 0x40 /* FEE_CTRL all operations */ +#define MMW_FEE_ADDR_CLEAR 0xFF /* FEE_CTRL clear operations */ + + unsigned char mmw_fee_data_l; /* Write data to EEprom */ + unsigned char mmw_fee_data_h; /* high octet */ + unsigned char mmw_ext_ant; /* Setting for external antenna */ +#define MMW_EXT_ANT_EXTANT 0x01 /* Select external antenna */ +#define MMW_EXT_ANT_POL 0x02 /* Polarity of the antenna */ +#define MMW_EXT_ANT_INTERNAL 0x00 /* Internal antenna */ +#define MMW_EXT_ANT_EXTERNAL 0x03 /* External antenna */ +#define MMW_EXT_ANT_IQ_TEST 0x1C /* IQ test pattern (set to 0) */ +}; + +#define MMW_SIZE 37 + +#define mmwoff(p,f) (unsigned short)((void *)(&((mmw_t *)((void *)0 + (p)))->f) - (void *)0) + +/* + * Modem Management Controller (MMC) read structure. + */ +typedef struct mmr_t mmr_t; +struct mmr_t +{ + unsigned char mmr_unused0[8]; /* unused */ + unsigned char mmr_des_status; /* encryption status */ + unsigned char mmr_des_avail; /* encryption available (0x55 read) */ +#define MMR_DES_AVAIL_DES 0x55 /* DES available */ +#define MMR_DES_AVAIL_AES 0x33 /* AES (AT&T) available */ + unsigned char mmr_des_io_invert; /* des I/O invert register */ + unsigned char mmr_unused1[5]; /* unused */ + unsigned char mmr_dce_status; /* DCE status */ +#define MMR_DCE_STATUS_RX_BUSY 0x01 /* receiver busy */ +#define MMR_DCE_STATUS_LOOPT_IND 0x02 /* loop test indicated */ +#define MMR_DCE_STATUS_TX_BUSY 0x04 /* transmitter on */ +#define MMR_DCE_STATUS_JBR_EXPIRED 0x08 /* jabber timer expired */ + unsigned char mmr_dsp_id; /* DSP id (AA = Daedalus rev A) */ + unsigned char mmr_unused2[2]; /* unused */ + unsigned char mmr_correct_nwid_l; /* # of correct NWID's rxd (low) */ + unsigned char mmr_correct_nwid_h; /* # of correct NWID's rxd (high) */ + /* Warning : Read high order octet first !!! */ + unsigned char mmr_wrong_nwid_l; /* # of wrong NWID's rxd (low) */ + unsigned char mmr_wrong_nwid_h; /* # of wrong NWID's rxd (high) */ + unsigned char mmr_thr_pre_set; /* level threshold preset */ +#define MMR_THR_PRE_SET 0x3F /* level threshold preset */ +#define MMR_THR_PRE_SET_CUR 0x80 /* Current signal above it */ + unsigned char mmr_signal_lvl; /* signal level */ +#define MMR_SIGNAL_LVL 0x3F /* signal level */ +#define MMR_SIGNAL_LVL_VALID 0x80 /* Updated since last read */ + unsigned char mmr_silence_lvl; /* silence level (noise) */ +#define MMR_SILENCE_LVL 0x3F /* silence level */ +#define MMR_SILENCE_LVL_VALID 0x80 /* Updated since last read */ + unsigned char mmr_sgnl_qual; /* signal quality */ +#define MMR_SGNL_QUAL 0x0F /* signal quality */ +#define MMR_SGNL_QUAL_ANT 0x80 /* current antenna used */ + unsigned char mmr_netw_id_l; /* NWID low order byte ??? */ + unsigned char mmr_unused3[3]; /* unused */ + + /* 2.0 Hardware extension - frequency selection support */ + unsigned char mmr_fee_status; /* Status of frequency eeprom */ +#define MMR_FEE_STATUS_ID 0xF0 /* Modem revision id */ +#define MMR_FEE_STATUS_DWLD 0x08 /* Download in progress */ +#define MMR_FEE_STATUS_BUSY 0x04 /* EEprom busy */ + unsigned char mmr_unused4[1]; /* unused */ + unsigned char mmr_fee_data_l; /* Read data from eeprom (low) */ + unsigned char mmr_fee_data_h; /* Read data from eeprom (high) */ +}; + +#define MMR_SIZE 36 + +#define mmroff(p,f) (unsigned short)((void *)(&((mmr_t *)((void *)0 + (p)))->f) - (void *)0) + +/* Make the two above structures one */ +typedef union mm_t +{ + struct mmw_t w; /* Write to the mmc */ + struct mmr_t r; /* Read from the mmc */ +} mm_t; + +#endif /* _WAVELAN_H */ + +/* + * This software may only be used and distributed + * according to the terms of the GNU Public License. + * + * For more details, see wavelan.c. + */ diff --git a/linux/src/drivers/net/wavelan.p.h b/linux/src/drivers/net/wavelan.p.h new file mode 100644 index 00000000..3a6124e7 --- /dev/null +++ b/linux/src/drivers/net/wavelan.p.h @@ -0,0 +1,635 @@ +/* + * Wavelan ISA driver + * + * Jean II - HPLB '96 + * + * Reorganisation and extension of the driver. + * + * This file contain all definition and declarations necessary for the + * wavelan isa driver. This file is a private header, so it should + * be included only on wavelan.c !!! + */ + +#ifndef WAVELAN_P_H +#define WAVELAN_P_H + +/************************** DOCUMENTATION **************************/ +/* + * This driver provide a Linux interface to the Wavelan ISA hardware + * The Wavelan is a product of Lucent ("http://wavelan.netland.nl/"). + * This division was formerly part of NCR and then AT&T. + * Wavelan are also distributed by DEC (RoamAbout), Digital Ocean and + * Aironet (Arlan). If you have one of those product, you will need to + * make some changes below... + * + * This driver is still a beta software. A lot of bugs have been corrected, + * a lot of functionalities are implemented, the whole appear pretty stable, + * but there is still some area of improvement (encryption, performance...). + * + * To know how to use this driver, read the NET3 HOWTO. + * If you want to exploit the many other fonctionalities, look comments + * in the code... + * + * This driver is the result of the effort of many peoples (see below). + */ + +/* ------------------------ SPECIFIC NOTES ------------------------ */ +/* + * wavelan.o is darn too big + * ------------------------- + * That's true ! There is a very simple way to reduce the driver + * object by 33% (yes !). Comment out the following line : + * #include <linux/wireless.h> + * + * MAC address and hardware detection : + * ---------------------------------- + * The detection code of the wavelan chech that the first 3 + * octets of the MAC address fit the company code. This type of + * detection work well for AT&T cards (because the AT&T code is + * hardcoded in wavelan.h), but of course will fail for other + * manufacturer. + * + * If you are sure that your card is derived from the wavelan, + * here is the way to configure it : + * 1) Get your MAC address + * a) With your card utilities (wfreqsel, instconf, ...) + * b) With the driver : + * o compile the kernel with DEBUG_CONFIG_INFO enabled + * o Boot and look the card messages + * 2) Set your MAC code (3 octets) in MAC_ADDRESSES[][3] (wavelan.h) + * 3) Compile & verify + * 4) Send me the MAC code - I will include it in the next version... + * + * "CU Inactive" message at boot up : + * ----------------------------------- + * It seem that there is some weird timings problems with the + * Intel microcontroler. In fact, this message is triggered by a + * bad reading of the on board ram the first time we read the + * control block. If you ignore this message, all is ok (but in + * fact, currently, it reset the wavelan hardware). + * + * To get rid of that problem, there is two solution. The first + * is to add a dummy read of the scb at the end of + * wv_82586_config. The second is to add the timers + * wv_synchronous_cmd and wv_ack (the udelay just after the + * waiting loops - seem that the controler is not totally ready + * when it say it is !). + * + * In the current code, I use the second solution (to be + * consistent with the original solution of Bruce Janson). + */ + +/* --------------------- WIRELESS EXTENSIONS --------------------- */ +/* + * This driver is the first one to support "wireless extensions". + * This set of extensions provide you some way to control the wireless + * caracteristics of the hardware in a standard way and support for + * applications for taking advantage of it (like Mobile IP). + * + * You will need to enable the CONFIG_NET_RADIO define in the kernel + * configuration to enable the wireless extensions (this is the one + * giving access to the radio network device choice). + * + * It might also be a good idea as well to fetch the wireless tools to + * configure the device and play a bit. + */ + +/* ---------------------------- FILES ---------------------------- */ +/* + * wavelan.c : The actual code for the driver - C functions + * + * wavelan.p.h : Private header : local types / vars for the driver + * + * wavelan.h : Description of the hardware interface & structs + * + * i82586.h : Description if the Ethernet controler + */ + +/* --------------------------- HISTORY --------------------------- */ +/* + * (Made with information in drivers headers. It may not be accurate, + * and I garantee nothing except my best effort...) + * + * The history of the Wavelan drivers is as complicated as history of + * the Wavelan itself (NCR -> AT&T -> Lucent). + * + * All started with Anders Klemets <klemets@paul.rutgers.edu>, + * writting a Wavelan ISA driver for the MACH microkernel. Girish + * Welling <welling@paul.rutgers.edu> had also worked on it. + * Keith Moore modify this for the Pcmcia hardware. + * + * Robert Morris <rtm@das.harvard.edu> port these two drivers to BSDI + * and add specific Pcmcia support (there is currently no equivalent + * of the PCMCIA package under BSD...). + * + * Jim Binkley <jrb@cs.pdx.edu> port both BSDI drivers to freeBSD. + * + * Bruce Janson <bruce@cs.usyd.edu.au> port the BSDI ISA driver to Linux. + * + * Anthony D. Joseph <adj@lcs.mit.edu> started modify Bruce driver + * (with help of the BSDI PCMCIA driver) for PCMCIA. + * Yunzhou Li <yunzhou@strat.iol.unh.edu> finished is work. + * Joe Finney <joe@comp.lancs.ac.uk> patched the driver to start + * correctly 2.00 cards (2.4 GHz with frequency selection). + * David Hinds <dhinds@hyper.stanford.edu> integrated the whole in his + * Pcmcia package (+ bug corrections). + * + * I (Jean Tourrilhes - jt@hplb.hpl.hp.com) then started to make some + * patchs to the Pcmcia driver. After, I added code in the ISA driver + * for Wireless Extensions and full support of frequency selection + * cards. Then, I've done the same to the Pcmcia driver + some + * reorganisation. Finally, I came back to the ISA driver to + * upgrade it at the same level as the Pcmcia one and reorganise + * the code + * Loeke Brederveld <lbrederv@wavelan.com> from Lucent has given me + * much needed informations on the Wavelan hardware. + */ + +/* The original copyrights and litteratures mention others names and + * credits. I don't know what there part in this development was... + */ + +/* By the way : for the copyright & legal stuff : + * Almost everybody wrote code under GNU or BSD license (or alike), + * and want that their original copyright remain somewhere in the + * code (for myself, I go with the GPL). + * Nobody want to take responsibility for anything, except the fame... + */ + +/* --------------------------- CREDITS --------------------------- */ +/* + * This software was developed as a component of the + * Linux operating system. + * It is based on other device drivers and information + * either written or supplied by: + * Ajay Bakre (bakre@paul.rutgers.edu), + * Donald Becker (becker@cesdis.gsfc.nasa.gov), + * Loeke Brederveld (Loeke.Brederveld@Utrecht.NCR.com), + * Brent Elphick <belphick@uwaterloo.ca>, + * Anders Klemets (klemets@it.kth.se), + * Vladimir V. Kolpakov (w@stier.koenig.ru), + * Marc Meertens (Marc.Meertens@Utrecht.NCR.com), + * Pauline Middelink (middelin@polyware.iaf.nl), + * Robert Morris (rtm@das.harvard.edu), + * Jean Tourrilhes (jt@hplb.hpl.hp.com), + * Girish Welling (welling@paul.rutgers.edu), + * Clark Woodworth <clark@hiway1.exit109.com> + * Yongguang Zhang <ygz@isl.hrl.hac.com>... + * + * Thanks go also to: + * James Ashton (jaa101@syseng.anu.edu.au), + * Alan Cox (iialan@iiit.swan.ac.uk), + * Allan Creighton (allanc@cs.usyd.edu.au), + * Matthew Geier (matthew@cs.usyd.edu.au), + * Remo di Giovanni (remo@cs.usyd.edu.au), + * Eckhard Grah (grah@wrcs1.urz.uni-wuppertal.de), + * Vipul Gupta (vgupta@cs.binghamton.edu), + * Mark Hagan (mhagan@wtcpost.daytonoh.NCR.COM), + * Tim Nicholson (tim@cs.usyd.edu.au), + * Ian Parkin (ian@cs.usyd.edu.au), + * John Rosenberg (johnr@cs.usyd.edu.au), + * George Rossi (george@phm.gov.au), + * Arthur Scott (arthur@cs.usyd.edu.au), + * Stanislav Sinyagin <stas@isf.ru> + * Peter Storey, + * for their assistance and advice. + * + * Additional Credits: + * + * My developpement has been done under Linux 2.0.x (Debian 1.1) with + * an HP Vectra XP/60. + * + */ + +/* ------------------------- IMPROVEMENTS ------------------------- */ +/* + * I proudly present : + * + * Changes mades in first pre-release : + * ---------------------------------- + * - Reorganisation of the code, function name change + * - Creation of private header (wavelan.p.h) + * - Reorganised debug messages + * - More comments, history, ... + * - mmc_init : configure the PSA if not done + * - mmc_init : correct default value of level threshold for pcmcia + * - mmc_init : 2.00 detection better code for 2.00 init + * - better info at startup + * - irq setting (note : this setting is permanent...) + * - Watchdog : change strategy (+ solve module removal problems) + * - add wireless extensions (ioctl & get_wireless_stats) + * get/set nwid/frequency on fly, info for /proc/net/wireless + * - More wireless extension : SETSPY and GETSPY + * - Make wireless extensions optional + * - Private ioctl to set/get quality & level threshold, histogram + * - Remove /proc/net/wavelan + * - Supress useless stuff from lp (net_local) + * - kernel 2.1 support (copy_to/from_user instead of memcpy_to/fromfs) + * - Add message level (debug stuff in /var/adm/debug & errors not + * displayed at console and still in /var/adm/messages) + * - multi device support + * - Start fixing the probe (init code) + * - More inlines + * - man page + * - Lot of others minor details & cleanups + * + * Changes made in second pre-release : + * ---------------------------------- + * - Cleanup init code (probe & module init) + * - Better multi device support (module) + * - name assignement (module) + * + * Changes made in third pre-release : + * --------------------------------- + * - Be more conservative on timers + * - Preliminary support for multicast (I still lack some details...) + * + * Changes made in fourth pre-release : + * ---------------------------------- + * - multicast (revisited and finished) + * - Avoid reset in set_multicast_list (a really big hack) + * if somebody could apply this code for other i82586 based driver... + * - Share on board memory 75% RU / 25% CU (instead of 50/50) + * + * Changes made for release in 2.1.15 : + * ---------------------------------- + * - Change the detection code for multi manufacturer code support + * + * Changes made for release in 2.1.17 : + * ---------------------------------- + * - Update to wireless extensions changes + * - Silly bug in card initial configuration (psa_conf_status) + * + * Changes made for release in 2.1.27 & 2.0.30 : + * ------------------------------------------- + * - Small bug in debug code (probably not the last one...) + * - Remove extern kerword for wavelan_probe() + * - Level threshold is now a standard wireless extension (version 4 !) + * + * Changes made for release in 2.1.36 : + * ---------------------------------- + * - Encryption setting from Brent Elphick (thanks a lot !) + * - 'ioaddr' to 'u_long' for the Alpha (thanks to Stanislav Sinyagin) + * + * Wishes & dreams : + * --------------- + * - Roaming + */ + +/***************************** INCLUDES *****************************/ + +#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/stat.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/malloc.h> +#include <linux/timer.h> + +#include <linux/wireless.h> /* Wireless extensions */ + +/* Wavelan declarations */ +#include "i82586.h" +#include "wavelan.h" + +/****************************** DEBUG ******************************/ + +#undef DEBUG_MODULE_TRACE /* Module insertion/removal */ +#undef DEBUG_CALLBACK_TRACE /* Calls made by Linux */ +#undef DEBUG_INTERRUPT_TRACE /* Calls to handler */ +#undef DEBUG_INTERRUPT_INFO /* type of interrupt & so on */ +#define DEBUG_INTERRUPT_ERROR /* problems */ +#undef DEBUG_CONFIG_TRACE /* Trace the config functions */ +#undef DEBUG_CONFIG_INFO /* What's going on... */ +#define DEBUG_CONFIG_ERRORS /* Errors on configuration */ +#undef DEBUG_TX_TRACE /* Transmission calls */ +#undef DEBUG_TX_INFO /* Header of the transmited packet */ +#define DEBUG_TX_ERROR /* unexpected conditions */ +#undef DEBUG_RX_TRACE /* Transmission calls */ +#undef DEBUG_RX_INFO /* Header of the transmited packet */ +#define DEBUG_RX_ERROR /* unexpected conditions */ +#undef DEBUG_PACKET_DUMP 16 /* Dump packet on the screen */ +#undef DEBUG_IOCTL_TRACE /* Misc call by Linux */ +#undef DEBUG_IOCTL_INFO /* Various debug info */ +#define DEBUG_IOCTL_ERROR /* What's going wrong */ +#define DEBUG_BASIC_SHOW /* Show basic startup info */ +#undef DEBUG_VERSION_SHOW /* Print version info */ +#undef DEBUG_PSA_SHOW /* Dump psa to screen */ +#undef DEBUG_MMC_SHOW /* Dump mmc to screen */ +#undef DEBUG_SHOW_UNUSED /* Show also unused fields */ +#undef DEBUG_I82586_SHOW /* Show i82586 status */ +#undef DEBUG_DEVICE_SHOW /* Show device parameters */ + +/* Options : */ +#define USE_PSA_CONFIG /* Use info from the PSA */ +#define IGNORE_NORMAL_XMIT_ERRS /* Don't bother with normal conditions */ +#undef STRUCT_CHECK /* Verify padding of structures */ +#undef PSA_CRC /* Check CRC in PSA */ +#undef OLDIES /* Old code (to redo) */ +#undef RECORD_SNR /* To redo */ +#undef EEPROM_IS_PROTECTED /* Doesn't seem to be necessary */ +#define MULTICAST_AVOID /* Avoid extra multicast (I'm sceptical) */ + +#ifdef WIRELESS_EXT /* If wireless extension exist in the kernel */ +/* Warning : these stuff will slow down the driver... */ +#define WIRELESS_SPY /* Enable spying addresses */ +#undef HISTOGRAM /* Enable histogram of sig level... */ +#endif + +/************************ CONSTANTS & MACROS ************************/ + +#ifdef DEBUG_VERSION_SHOW +static const char *version = "wavelan.c : v16 (wireless extensions) 17/4/97\n"; +#endif + +/* Watchdog temporisation */ +#define WATCHDOG_JIFFIES 32 /* TODO: express in HZ. */ + +/* Macro to get the number of elements in an array */ +#define NELS(a) (sizeof(a) / sizeof(a[0])) + +/* ------------------------ PRIVATE IOCTL ------------------------ */ + +#define SIOCSIPQTHR SIOCDEVPRIVATE /* Set quality threshold */ +#define SIOCGIPQTHR SIOCDEVPRIVATE + 1 /* Get quality threshold */ +#define SIOCSIPLTHR SIOCDEVPRIVATE + 2 /* Set level threshold */ +#define SIOCGIPLTHR SIOCDEVPRIVATE + 3 /* Get level threshold */ + +#define SIOCSIPHISTO SIOCDEVPRIVATE + 6 /* Set histogram ranges */ +#define SIOCGIPHISTO SIOCDEVPRIVATE + 7 /* Get histogram values */ + +/* ----------------------- VERSION SUPPORT ----------------------- */ + +/* This ugly patch is needed to cope with old version of the kernel */ +#ifndef copy_from_user +#define copy_from_user memcpy_fromfs +#define copy_to_user memcpy_tofs +#endif + +/****************************** TYPES ******************************/ + +/* Shortcuts */ +typedef struct device device; +typedef struct enet_statistics en_stats; +typedef struct iw_statistics iw_stats; +typedef struct iw_quality iw_qual; +typedef struct iw_freq iw_freq; +typedef struct net_local net_local; +typedef struct timer_list timer_list; + +/* Basic types */ +typedef u_char mac_addr[WAVELAN_ADDR_SIZE]; /* Hardware address */ + +/* + * Static specific data for the interface. + * + * For each network interface, Linux keep data in two structure. "device" + * keep the generic data (same format for everybody) and "net_local" keep + * the additional specific data. + * Note that some of this specific data is in fact generic (en_stats, for + * example). + */ +struct net_local +{ + net_local * next; /* Linked list of the devices */ + device * dev; /* Reverse link... */ + en_stats stats; /* Ethernet interface statistics */ + int nresets; /* Number of hw resets */ + u_char reconfig_82586; /* Need to reconfigure the controler */ + u_char promiscuous; /* Promiscuous mode */ + int mc_count; /* Number of multicast addresses */ + timer_list watchdog; /* To avoid blocking state */ + u_short hacr; /* Current host interface state */ + + int tx_n_in_use; + u_short rx_head; + u_short rx_last; + u_short tx_first_free; + u_short tx_first_in_use; + +#ifdef WIRELESS_EXT + iw_stats wstats; /* Wireless specific stats */ +#endif + +#ifdef WIRELESS_SPY + int spy_number; /* Number of addresses to spy */ + mac_addr spy_address[IW_MAX_SPY]; /* The addresses to spy */ + iw_qual spy_stat[IW_MAX_SPY]; /* Statistics gathered */ +#endif /* WIRELESS_SPY */ +#ifdef HISTOGRAM + int his_number; /* Number of intervals */ + u_char his_range[16]; /* Boundaries of interval ]n-1; n] */ + u_long his_sum[16]; /* Sum in interval */ +#endif /* HISTOGRAM */ +}; + +/**************************** PROTOTYPES ****************************/ + +/* ----------------------- MISC SUBROUTINES ------------------------ */ +static inline unsigned long /* flags */ + wv_splhi(void); /* Disable interrupts */ +static inline void + wv_splx(unsigned long); /* ReEnable interrupts : flags */ +static u_char + wv_irq_to_psa(int); +static int + wv_psa_to_irq(u_char); +/* ------------------- HOST ADAPTER SUBROUTINES ------------------- */ +static inline u_short /* data */ + hasr_read(u_long); /* Read the host interface : base address */ +static inline void + hacr_write(u_long, /* Write to host interface : base address */ + u_short), /* data */ + hacr_write_slow(u_long, + u_short), + set_chan_attn(u_long, /* ioaddr */ + u_short), /* hacr */ + wv_hacr_reset(u_long), /* ioaddr */ + wv_16_off(u_long, /* ioaddr */ + u_short), /* hacr */ + wv_16_on(u_long, /* ioaddr */ + u_short), /* hacr */ + wv_ints_off(device *), + wv_ints_on(device *); +/* ----------------- MODEM MANAGEMENT SUBROUTINES ----------------- */ +static void + psa_read(u_long, /* Read the Parameter Storage Area */ + u_short, /* hacr */ + int, /* offset in PSA */ + u_char *, /* buffer to fill */ + int), /* size to read */ + psa_write(u_long, /* Write to the PSA */ + u_short, /* hacr */ + int, /* Offset in psa */ + u_char *, /* Buffer in memory */ + int); /* Length of buffer */ +static inline void + mmc_out(u_long, /* Write 1 byte to the Modem Manag Control */ + u_short, + u_char), + mmc_write(u_long, /* Write n bytes to the MMC */ + u_char, + u_char *, + int); +static inline u_char /* Read 1 byte from the MMC */ + mmc_in(u_long, + u_short); +static inline void + mmc_read(u_long, /* Read n bytes from the MMC */ + u_char, + u_char *, + int), + fee_wait(u_long, /* Wait for frequency EEprom : base address */ + int, /* Base delay to wait for */ + int); /* Number of time to wait */ +static void + fee_read(u_long, /* Read the frequency EEprom : base address */ + u_short, /* destination offset */ + u_short *, /* data buffer */ + int); /* number of registers */ +/* ---------------------- I82586 SUBROUTINES ----------------------- */ +static /*inline*/ void + obram_read(u_long, /* ioaddr */ + u_short, /* o */ + u_char *, /* b */ + int); /* n */ +static inline void + obram_write(u_long, /* ioaddr */ + u_short, /* o */ + u_char *, /* b */ + int); /* n */ +static void + wv_ack(device *); +static inline int + wv_synchronous_cmd(device *, + const char *), + wv_config_complete(device *, + u_long, + net_local *); +static int + wv_complete(device *, + u_long, + net_local *); +static inline void + wv_82586_reconfig(device *); +/* ------------------- DEBUG & INFO SUBROUTINES ------------------- */ +#ifdef DEBUG_I82586_SHOW +static void + wv_scb_show(unsigned short); +#endif +static inline void + wv_init_info(device *); /* display startup info */ +/* ------------------- IOCTL, STATS & RECONFIG ------------------- */ +static en_stats * + wavelan_get_stats(device *); /* Give stats /proc/net/dev */ +static void + wavelan_set_multicast_list(device *); +/* ----------------------- PACKET RECEPTION ----------------------- */ +static inline void + wv_packet_read(device *, /* Read a packet from a frame */ + u_short, + int), + wv_receive(device *); /* Read all packets waiting */ +/* --------------------- PACKET TRANSMISSION --------------------- */ +static inline void + wv_packet_write(device *, /* Write a packet to the Tx buffer */ + void *, + short); +static int + wavelan_packet_xmit(struct sk_buff *, /* Send a packet */ + device *); +/* -------------------- HARDWARE CONFIGURATION -------------------- */ +static inline int + wv_mmc_init(device *), /* Initialize the modem */ + wv_ru_start(device *), /* Start the i82586 receiver unit */ + wv_cu_start(device *), /* Start the i82586 command unit */ + wv_82586_start(device *); /* Start the i82586 */ +static void + wv_82586_config(device *); /* Configure the i82586 */ +static inline void + wv_82586_stop(device *); +static int + wv_hw_reset(device *), /* Reset the wavelan hardware */ + wv_check_ioaddr(u_long, /* ioaddr */ + u_char *); /* mac address (read) */ +/* ---------------------- INTERRUPT HANDLING ---------------------- */ +static void + wavelan_interrupt(int, /* Interrupt handler */ + void *, + struct pt_regs *); +static void + wavelan_watchdog(u_long); /* Transmission watchdog */ +/* ------------------- CONFIGURATION CALLBACKS ------------------- */ +static int + wavelan_open(device *), /* Open the device */ + wavelan_close(device *), /* Close the device */ + wavelan_config(device *); /* Configure one device */ +extern int + wavelan_probe(device *); /* See Space.c */ + +/**************************** VARIABLES ****************************/ + +/* + * This is the root of the linked list of wavelan drivers + * It is use to verify that we don't reuse the same base address + * for two differents drivers and to make the cleanup when + * removing the module. + */ +static net_local * wavelan_list = (net_local *) NULL; + +/* + * This table is used to translate the psa value to irq number + * and vice versa... + */ +static u_char irqvals[] = +{ + 0, 0, 0, 0x01, + 0x02, 0x04, 0, 0x08, + 0, 0, 0x10, 0x20, + 0x40, 0, 0, 0x80, +}; + +/* + * Table of the available i/o address (base address) for wavelan + */ +static unsigned short iobase[] = +{ +#if 0 + /* Leave out 0x3C0 for now -- seems to clash with some video + * controllers. + * Leave out the others too -- we will always use 0x390 and leave + * 0x300 for the Ethernet device. + * Jean II : 0x3E0 is really fine as well... + */ + 0x300, 0x390, 0x3E0, 0x3C0 +#endif /* 0 */ + 0x390, 0x3E0 +}; + +#ifdef MODULE +/* Name of the devices (memory allocation) */ +static char devname[4][IFNAMSIZ] = { "", "", "", "" }; + +/* Parameters set by insmod */ +static int io[4] = { 0, 0, 0, 0 }; +static int irq[4] = { 0, 0, 0, 0 }; +static char * name[4] = { devname[0], devname[1], devname[2], devname[3] }; +#endif /* MODULE */ + +#endif /* WAVELAN_P_H */ diff --git a/linux/src/drivers/net/wd.c b/linux/src/drivers/net/wd.c new file mode 100644 index 00000000..a737a01d --- /dev/null +++ b/linux/src/drivers/net/wd.c @@ -0,0 +1,513 @@ +/* wd.c: A WD80x3 ethernet driver for linux. */ +/* + Written 1993-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + This is a driver for WD8003 and WD8013 "compatible" ethercards. + + Thanks to Russ Nelson (nelson@crnwyr.com) for loaning me a WD8013. + + Changelog: + + Paul Gortmaker : multiple card support for module users, support + for non-standard memory sizes. + + +*/ + +static const char *version = + "wd.c:v1.10 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include "8390.h" + +/* A zero-terminated list of I/O addresses to be probed. */ +static unsigned int wd_portlist[] = +{0x300, 0x280, 0x380, 0x240, 0}; + +int wd_probe(struct device *dev); +int wd_probe1(struct device *dev, int ioaddr); + +static int wd_open(struct device *dev); +static void wd_reset_8390(struct device *dev); +static void wd_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, + int ring_page); +static void wd_block_input(struct device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void wd_block_output(struct device *dev, int count, + const unsigned char *buf, const int start_page); +static int wd_close_card(struct device *dev); + + +#define WD_START_PG 0x00 /* First page of TX buffer */ +#define WD03_STOP_PG 0x20 /* Last page +1 of RX ring */ +#define WD13_STOP_PG 0x40 /* Last page +1 of RX ring */ + +#define WD_CMDREG 0 /* Offset to ASIC command register. */ +#define WD_RESET 0x80 /* Board reset, in WD_CMDREG. */ +#define WD_MEMENB 0x40 /* Enable the shared memory. */ +#define WD_CMDREG5 5 /* Offset to 16-bit-only ASIC register 5. */ +#define ISA16 0x80 /* Enable 16 bit access from the ISA bus. */ +#define NIC16 0x40 /* Enable 16 bit access from the 8390. */ +#define WD_NIC_OFFSET 16 /* Offset to the 8390 from the base_addr. */ +#define WD_IO_EXTENT 32 + + +/* Probe for the WD8003 and WD8013. These cards have the station + address PROM at I/O ports <base>+8 to <base>+13, with a checksum + following. A Soundblaster can have the same checksum as an WDethercard, + so we have an extra exclusionary check for it. + + The wd_probe1() routine initializes the card and fills the + station address field. */ + +#ifdef HAVE_DEVLIST +struct netdev_entry wd_drv = +{"wd", wd_probe1, WD_IO_EXTENT, wd_portlist}; +#else + +int wd_probe(struct device *dev) +{ + int i; + int base_addr = dev ? dev->base_addr : 0; + + if (base_addr > 0x1ff) /* Check a single specified location. */ + return wd_probe1(dev, base_addr); + else if (base_addr != 0) /* Don't probe at all. */ + return ENXIO; + + for (i = 0; wd_portlist[i]; i++) { + int ioaddr = wd_portlist[i]; + if (check_region(ioaddr, WD_IO_EXTENT)) + continue; + if (wd_probe1(dev, ioaddr) == 0) + return 0; + } + + return ENODEV; +} +#endif + +int wd_probe1(struct device *dev, int ioaddr) +{ + int i; + int checksum = 0; + int ancient = 0; /* An old card without config registers. */ + int word16 = 0; /* 0 = 8 bit, 1 = 16 bit */ + const char *model_name; + static unsigned version_printed = 0; + + for (i = 0; i < 8; i++) + checksum += inb(ioaddr + 8 + i); + if (inb(ioaddr + 8) == 0xff /* Extra check to avoid soundcard. */ + || inb(ioaddr + 9) == 0xff + || (checksum & 0xff) != 0xFF) + return ENODEV; + + /* We should have a "dev" from Space.c or the static module table. */ + if (dev == NULL) { + printk("wd.c: Passed a NULL device.\n"); + dev = init_etherdev(0, 0); + } + + /* Check for semi-valid mem_start/end values if supplied. */ + if ((dev->mem_start % 0x2000) || (dev->mem_end % 0x2000)) { + printk(KERN_WARNING "wd.c: user supplied mem_start or mem_end not on 8kB boundary - ignored.\n"); + dev->mem_start = 0; + dev->mem_end = 0; + } + + if (ei_debug && version_printed++ == 0) + printk(version); + + printk("%s: WD80x3 at %#3x, ", dev->name, ioaddr); + for (i = 0; i < 6; i++) + printk(" %2.2X", dev->dev_addr[i] = inb(ioaddr + 8 + i)); + + /* The following PureData probe code was contributed by + Mike Jagdis <jaggy@purplet.demon.co.uk>. Puredata does software + configuration differently from others so we have to check for them. + This detects an 8 bit, 16 bit or dumb (Toshiba, jumpered) card. + */ + if (inb(ioaddr+0) == 'P' && inb(ioaddr+1) == 'D') { + unsigned char reg5 = inb(ioaddr+5); + + switch (inb(ioaddr+2)) { + case 0x03: word16 = 0; model_name = "PDI8023-8"; break; + case 0x05: word16 = 0; model_name = "PDUC8023"; break; + case 0x0a: word16 = 1; model_name = "PDI8023-16"; break; + /* Either 0x01 (dumb) or they've released a new version. */ + default: word16 = 0; model_name = "PDI8023"; break; + } + dev->mem_start = ((reg5 & 0x1c) + 0xc0) << 12; + dev->irq = (reg5 & 0xe0) == 0xe0 ? 10 : (reg5 >> 5) + 1; + } else { /* End of PureData probe */ + /* This method of checking for a 16-bit board is borrowed from the + we.c driver. A simpler method is just to look in ASIC reg. 0x03. + I'm comparing the two method in alpha test to make certain they + return the same result. */ + /* Check for the old 8 bit board - it has register 0/8 aliasing. + Do NOT check i>=6 here -- it hangs the old 8003 boards! */ + for (i = 0; i < 6; i++) + if (inb(ioaddr+i) != inb(ioaddr+8+i)) + break; + if (i >= 6) { + ancient = 1; + model_name = "WD8003-old"; + word16 = 0; + } else { + int tmp = inb(ioaddr+1); /* fiddle with 16bit bit */ + outb( tmp ^ 0x01, ioaddr+1 ); /* attempt to clear 16bit bit */ + if (((inb( ioaddr+1) & 0x01) == 0x01) /* A 16 bit card */ + && (tmp & 0x01) == 0x01 ) { /* In a 16 slot. */ + int asic_reg5 = inb(ioaddr+WD_CMDREG5); + /* Magic to set ASIC to word-wide mode. */ + outb( NIC16 | (asic_reg5&0x1f), ioaddr+WD_CMDREG5); + outb(tmp, ioaddr+1); + model_name = "WD8013"; + word16 = 1; /* We have a 16bit board here! */ + } else { + model_name = "WD8003"; + word16 = 0; + } + outb(tmp, ioaddr+1); /* Restore original reg1 value. */ + } +#ifndef final_version + if ( !ancient && (inb(ioaddr+1) & 0x01) != (word16 & 0x01)) + printk("\nWD80?3: Bus width conflict, %d (probe) != %d (reg report).", + word16 ? 16 : 8, (inb(ioaddr+1) & 0x01) ? 16 : 8); +#endif + } + +#if defined(WD_SHMEM) && WD_SHMEM > 0x80000 + /* Allow a compile-time override. */ + dev->mem_start = WD_SHMEM; +#else + if (dev->mem_start == 0) { + /* Sanity and old 8003 check */ + int reg0 = inb(ioaddr); + if (reg0 == 0xff || reg0 == 0) { + /* Future plan: this could check a few likely locations first. */ + dev->mem_start = 0xd0000; + printk(" assigning address %#lx", dev->mem_start); + } else { + int high_addr_bits = inb(ioaddr+WD_CMDREG5) & 0x1f; + /* Some boards don't have the register 5 -- it returns 0xff. */ + if (high_addr_bits == 0x1f || word16 == 0) + high_addr_bits = 0x01; + dev->mem_start = ((reg0&0x3f) << 13) + (high_addr_bits << 19); + } + } +#endif + + /* The 8390 isn't at the base address -- the ASIC regs are there! */ + dev->base_addr = ioaddr+WD_NIC_OFFSET; + + if (dev->irq < 2) { + int irqmap[] = {9,3,5,7,10,11,15,4}; + int reg1 = inb(ioaddr+1); + int reg4 = inb(ioaddr+4); + if (ancient || reg1 == 0xff) { /* Ack!! No way to read the IRQ! */ + short nic_addr = ioaddr+WD_NIC_OFFSET; + + /* We have an old-style ethercard that doesn't report its IRQ + line. Do autoirq to find the IRQ line. Note that this IS NOT + a reliable way to trigger an interrupt. */ + outb_p(E8390_NODMA + E8390_STOP, nic_addr); + outb(0x00, nic_addr+EN0_IMR); /* Disable all intrs. */ + autoirq_setup(0); + outb_p(0xff, nic_addr + EN0_IMR); /* Enable all interrupts. */ + outb_p(0x00, nic_addr + EN0_RCNTLO); + outb_p(0x00, nic_addr + EN0_RCNTHI); + outb(E8390_RREAD+E8390_START, nic_addr); /* Trigger it... */ + dev->irq = autoirq_report(2); + outb_p(0x00, nic_addr+EN0_IMR); /* Mask all intrs. again. */ + + if (ei_debug > 2) + printk(" autoirq is %d", dev->irq); + if (dev->irq < 2) + dev->irq = word16 ? 10 : 5; + } else + dev->irq = irqmap[((reg4 >> 5) & 0x03) + (reg1 & 0x04)]; + } else if (dev->irq == 2) /* Fixup bogosity: IRQ2 is really IRQ9 */ + dev->irq = 9; + + /* Snarf the interrupt now. There's no point in waiting since we cannot + share and the board will usually be enabled. */ + if (request_irq(dev->irq, ei_interrupt, 0, model_name, NULL)) { + printk (" unable to get IRQ %d.\n", dev->irq); + return EAGAIN; + } + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk (" unable to get memory for dev->priv.\n"); + free_irq(dev->irq, NULL); + return -ENOMEM; + } + + /* OK, were are certain this is going to work. Setup the device. */ + request_region(ioaddr, WD_IO_EXTENT, model_name); + + ei_status.name = model_name; + ei_status.word16 = word16; + ei_status.tx_start_page = WD_START_PG; + ei_status.rx_start_page = WD_START_PG + TX_PAGES; + + /* Don't map in the shared memory until the board is actually opened. */ + dev->rmem_start = dev->mem_start + TX_PAGES*256; + + /* Some cards (eg WD8003EBT) can be jumpered for more (32k!) memory. */ + if (dev->mem_end != 0) { + ei_status.stop_page = (dev->mem_end - dev->mem_start)/256; + } else { + ei_status.stop_page = word16 ? WD13_STOP_PG : WD03_STOP_PG; + dev->mem_end = dev->mem_start + (ei_status.stop_page - WD_START_PG)*256; + } + dev->rmem_end = dev->mem_end; + + printk(" %s, IRQ %d, shared memory at %#lx-%#lx.\n", + model_name, dev->irq, dev->mem_start, dev->mem_end-1); + + ei_status.reset_8390 = &wd_reset_8390; + ei_status.block_input = &wd_block_input; + ei_status.block_output = &wd_block_output; + ei_status.get_8390_hdr = &wd_get_8390_hdr; + dev->open = &wd_open; + dev->stop = &wd_close_card; + NS8390_init(dev, 0); + +#if 1 + /* Enable interrupt generation on softconfig cards -- M.U */ + /* .. but possibly potentially unsafe - Donald */ + if (inb(ioaddr+14) & 0x20) + outb(inb(ioaddr+4)|0x80, ioaddr+4); +#endif + + return 0; +} + +static int +wd_open(struct device *dev) +{ + int ioaddr = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ + + /* Map in the shared memory. Always set register 0 last to remain + compatible with very old boards. */ + ei_status.reg0 = ((dev->mem_start>>13) & 0x3f) | WD_MEMENB; + ei_status.reg5 = ((dev->mem_start>>19) & 0x1f) | NIC16; + + if (ei_status.word16) + outb(ei_status.reg5, ioaddr+WD_CMDREG5); + outb(ei_status.reg0, ioaddr); /* WD_CMDREG */ + + ei_open(dev); + MOD_INC_USE_COUNT; + return 0; +} + +static void +wd_reset_8390(struct device *dev) +{ + int wd_cmd_port = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ + + outb(WD_RESET, wd_cmd_port); + if (ei_debug > 1) printk("resetting the WD80x3 t=%lu...", jiffies); + ei_status.txing = 0; + + /* Set up the ASIC registers, just in case something changed them. */ + outb((((dev->mem_start>>13) & 0x3f)|WD_MEMENB), wd_cmd_port); + if (ei_status.word16) + outb(NIC16 | ((dev->mem_start>>19) & 0x1f), wd_cmd_port+WD_CMDREG5); + + if (ei_debug > 1) printk("reset done\n"); + return; +} + +/* Grab the 8390 specific header. Similar to the block_input routine, but + we don't need to be concerned with ring wrap as the header will be at + the start of a page, so we optimize accordingly. */ + +static void +wd_get_8390_hdr(struct device *dev, struct e8390_pkt_hdr *hdr, int ring_page) +{ + + int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ + unsigned long hdr_start = dev->mem_start + ((ring_page - WD_START_PG)<<8); + + /* We'll always get a 4 byte header read followed by a packet read, so + we enable 16 bit mode before the header, and disable after the body. */ + if (ei_status.word16) + outb(ISA16 | ei_status.reg5, wd_cmdreg+WD_CMDREG5); + +#ifdef notdef + /* Officially this is what we are doing, but the readl() is faster */ + memcpy_fromio(hdr, hdr_start, sizeof(struct e8390_pkt_hdr)); +#else + ((unsigned int*)hdr)[0] = readl(hdr_start); +#endif +} + +/* Block input and output are easy on shared memory ethercards, and trivial + on the Western digital card where there is no choice of how to do it. + The only complications are that the ring buffer wraps, and need to map + switch between 8- and 16-bit modes. */ + +static void +wd_block_input(struct device *dev, int count, struct sk_buff *skb, int ring_offset) +{ + int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ + unsigned long xfer_start = dev->mem_start + ring_offset - (WD_START_PG<<8); + + if (xfer_start + count > dev->rmem_end) { + /* We must wrap the input move. */ + int semi_count = dev->rmem_end - xfer_start; + memcpy_fromio(skb->data, xfer_start, semi_count); + count -= semi_count; + memcpy_fromio(skb->data + semi_count, dev->rmem_start, count); + } else { + /* Packet is in one chunk -- we can copy + cksum. */ + eth_io_copy_and_sum(skb, xfer_start, count, 0); + } + + /* Turn off 16 bit access so that reboot works. ISA brain-damage */ + if (ei_status.word16) + outb(ei_status.reg5, wd_cmdreg+WD_CMDREG5); +} + +static void +wd_block_output(struct device *dev, int count, const unsigned char *buf, + int start_page) +{ + int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ + long shmem = dev->mem_start + ((start_page - WD_START_PG)<<8); + + + if (ei_status.word16) { + /* Turn on and off 16 bit access so that reboot works. */ + outb(ISA16 | ei_status.reg5, wd_cmdreg+WD_CMDREG5); + memcpy_toio(shmem, buf, count); + outb(ei_status.reg5, wd_cmdreg+WD_CMDREG5); + } else + memcpy_toio(shmem, buf, count); +} + + +static int +wd_close_card(struct device *dev) +{ + int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ + + if (ei_debug > 1) + printk("%s: Shutting down ethercard.\n", dev->name); + ei_close(dev); + + /* Change from 16-bit to 8-bit shared memory so reboot works. */ + if (ei_status.word16) + outb(ei_status.reg5, wd_cmdreg + WD_CMDREG5 ); + + /* And disable the shared memory. */ + outb(ei_status.reg0 & ~WD_MEMENB, wd_cmdreg); + + MOD_DEC_USE_COUNT; + + return 0; +} + + +#ifdef MODULE +#define MAX_WD_CARDS 4 /* Max number of wd cards per module */ +#define NAMELEN 8 /* # of chars for storing dev->name */ +static char namelist[NAMELEN * MAX_WD_CARDS] = { 0, }; +static struct device dev_wd[MAX_WD_CARDS] = { + { + NULL, /* assign a chunk of namelist[] below */ + 0, 0, 0, 0, + 0, 0, + 0, 0, 0, NULL, NULL + }, +}; + +static int io[MAX_WD_CARDS] = { 0, }; +static int irq[MAX_WD_CARDS] = { 0, }; +static int mem[MAX_WD_CARDS] = { 0, }; +static int mem_end[MAX_WD_CARDS] = { 0, }; /* for non std. mem size */ + +/* This is set up so that only a single autoprobe takes place per call. +ISA device autoprobes on a running machine are not recommended. */ +int +init_module(void) +{ + int this_dev, found = 0; + + for (this_dev = 0; this_dev < MAX_WD_CARDS; this_dev++) { + struct device *dev = &dev_wd[this_dev]; + dev->name = namelist+(NAMELEN*this_dev); + dev->irq = irq[this_dev]; + dev->base_addr = io[this_dev]; + dev->mem_start = mem[this_dev]; + dev->mem_end = mem_end[this_dev]; + dev->init = wd_probe; + if (io[this_dev] == 0) { + if (this_dev != 0) break; /* only autoprobe 1st one */ + printk(KERN_NOTICE "wd.c: Presently autoprobing (not recommended) for a single card.\n"); + } + if (register_netdev(dev) != 0) { + printk(KERN_WARNING "wd.c: No wd80x3 card found (i/o = 0x%x).\n", io[this_dev]); + if (found != 0) return 0; /* Got at least one. */ + return -ENXIO; + } + found++; + } + + return 0; +} + +void +cleanup_module(void) +{ + int this_dev; + + for (this_dev = 0; this_dev < MAX_WD_CARDS; this_dev++) { + struct device *dev = &dev_wd[this_dev]; + if (dev->priv != NULL) { + int ioaddr = dev->base_addr - WD_NIC_OFFSET; + kfree(dev->priv); + dev->priv = NULL; + free_irq(dev->irq, NULL); + irq2dev_map[dev->irq] = NULL; + release_region(ioaddr, WD_IO_EXTENT); + unregister_netdev(dev); + } + } +} +#endif /* MODULE */ + + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c wd.c" + * version-control: t + * tab-width: 4 + * kept-new-versions: 5 + * End: + */ diff --git a/linux/src/drivers/net/yellowfin.c b/linux/src/drivers/net/yellowfin.c new file mode 100644 index 00000000..c8e5314d --- /dev/null +++ b/linux/src/drivers/net/yellowfin.c @@ -0,0 +1,1302 @@ +/* yellowfin.c: A Packet Engines G-NIC ethernet driver for linux. */ +/* + Written 1997-1998 by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU Public License, incorporated herein by reference. + + This driver is for the Packet Engines G-NIC PCI Gigabit Ethernet adapter. + It also supports the Symbios Logic version of the same chip core. + + The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + Center of Excellence in Space Data and Information Sciences + Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Support and updates available at + http://cesdis.gsfc.nasa.gov/linux/drivers/yellowfin.html +*/ + +static const char *version = "yellowfin.c:v0.99A 4/7/98 becker@cesdis.gsfc.nasa.gov\n"; + +/* A few user-configurable values. */ + +static int max_interrupt_work = 20; +static int min_pci_latency = 64; +static int mtu = 0; +#ifdef YF_PROTOTYPE /* Support for prototype hardware errata. */ +/* System-wide count of bogus-rx frames. */ +static int bogus_rx = 0; +static int dma_ctrl = 0x004A0263; /* Constrained by errata */ +static int fifo_cfg = 0x0020; /* Bypass external Tx FIFO. */ +#elif YF_NEW /* A future perfect board :->. */ +static int dma_ctrl = 0x00CAC277; /* Override when loading module! */ +static int fifo_cfg = 0x0028; +#else +static int dma_ctrl = 0x004A0263; /* Constrained by errata */ +static int fifo_cfg = 0x0020; /* Bypass external Tx FIFO. */ +#endif + +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + Setting to > 1518 effectively disables this feature. */ +static const int rx_copybreak = 100; + +/* Keep the ring sizes a power of two for efficiency. + Making the Tx ring too large decreases the effectiveness of channel + bonding and packet priority. + There are no ill effects from too-large receive rings. */ +#define TX_RING_SIZE 16 +#define RX_RING_SIZE 32 + +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((2000*HZ)/1000) + +#include <linux/config.h> +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/bitops.h> +#include <asm/io.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +/* Kernel compatibility defines, common to David Hind's PCMCIA package. + This is only in the support-all-kernels source code. */ +#include <linux/version.h> /* Evil, but neccessary */ + +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10300 +#define RUN_AT(x) (x) /* What to put in timer->expires. */ +#define DEV_ALLOC_SKB(len) alloc_skb(len, GFP_ATOMIC) +#define virt_to_bus(addr) ((unsigned long)addr) +#define bus_to_virt(addr) ((void*)addr) + +#else /* 1.3.0 and later */ +#define RUN_AT(x) (jiffies + (x)) +#define DEV_ALLOC_SKB(len) dev_alloc_skb(len + 2) +#endif + +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10338 +#ifdef MODULE +#if !defined(CONFIG_MODVERSIONS) && !defined(__NO_VERSION__) +char kernel_version[] = UTS_RELEASE; +#endif +#else +#undef MOD_INC_USE_COUNT +#define MOD_INC_USE_COUNT +#undef MOD_DEC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif +#endif /* 1.3.38 */ + +#if (LINUX_VERSION_CODE >= 0x10344) +#define NEW_MULTICAST +#include <linux/delay.h> +#endif +#if (LINUX_VERSION_CODE >= 0x20100) +char kernel_version[] = UTS_RELEASE; +#endif +#ifdef SA_SHIRQ +#define IRQ(irq, dev_id, pt_regs) (irq, dev_id, pt_regs) +#else +#define IRQ(irq, dev_id, pt_regs) (irq, pt_regs) +#endif +#if (LINUX_VERSION_CODE < 0x20123) +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif + +static const char *card_name = "Yellowfin G-NIC Gbit Ethernet"; + +/* The PCI I/O space extent. */ +#define YELLOWFIN_TOTAL_SIZE 0x100 + +#ifdef HAVE_DEVLIST +struct netdev_entry yellowfin_drv = +{card_name, yellowfin_pci_probe, YELLOWFIN_TOTAL_SIZE, NULL}; +#endif + +#ifdef YELLOWFIN_DEBUG +int yellowfin_debug = YELLOWFIN_DEBUG; +#else +int yellowfin_debug = 1; +#endif + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the Packet Engines "Yellowfin" Gigabit +Ethernet adapter. The only PCA currently supported is the G-NIC 64-bit +PCI card. + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board. The system BIOS preferably should assign the +PCI INTA signal to an otherwise unused system IRQ line. +Note: Kernel versions earlier than 1.3.73 do not support shared PCI +interrupt lines. + +III. Driver operation + +IIIa. Ring buffers + +The Yellowfin uses the Descriptor Based DMA Architecture specified by Apple. +This is a descriptor list scheme similar to that used by the EEPro100 and +Tulip. This driver uses two statically allocated fixed-size descriptor lists +formed into rings by a branch from the final descriptor to the beginning of +the list. The ring sizes are set at compile time by RX/TX_RING_SIZE. + +The driver allocates full frame size skbuffs for the Rx ring buffers at +open() time and passes the skb->data field to the Yellowfin as receive data +buffers. When an incoming frame is less than RX_COPYBREAK bytes long, +a fresh skbuff is allocated and the frame is copied to the new skbuff. +When the incoming frame is larger, the skbuff is passed directly up the +protocol stack and replaced by a newly allocated skbuff. + +The RX_COPYBREAK value is chosen to trade-off the memory wasted by +using a full-sized skbuff for small frames vs. the copying costs of larger +frames. For small frames the copying cost is negligible (esp. considering +that we are pre-loading the cache with immediately useful header +information). For large frames the copying cost is non-trivial, and the +larger copy might flush the cache of useful data. + +IIIC. Synchronization + +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +The send packet thread has partial control over the Tx ring and 'dev->tbusy' +flag. It sets the tbusy flag whenever it's queuing a Tx packet. If the next +queue slot is empty, it clears the tbusy flag when finished otherwise it sets +the 'yp->tx_full' flag. + +The interrupt handler has exclusive control over the Rx ring and records stats +from the Tx ring. After reaping the stats, it marks the Tx queue entry as +empty by incrementing the dirty_tx mark. Iff the 'yp->tx_full' flag is set, it +clears both the tx_full and tbusy flags. + +IV. Notes + +Thanks to Kim Stearns of Packet Engines for providing a pair of G-NIC boards. + +IVb. References + +Yellowfin Engineering Design Specification, 4/23/97 Preliminary/Confidential +http://cesdis.gsfc.nasa.gov/linux/misc/100mbps.html + +IVc. Errata + +See Packet Engines confidential appendix. + +*/ + +/* A few values that may be tweaked. */ +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +#ifndef PCI_VENDOR_ID_PKT_ENG /* To be defined in linux/pci.h */ +#define PCI_VENDOR_ID_PKT_ENG 0x1000 /* Hmm, likely number.. */ +#define PCI_DEVICE_ID_YELLOWFIN 0x0702 +#endif + +/* The rest of these values should never change. */ + +static void yellowfin_timer(unsigned long data); + +/* Offsets to the Yellowfin registers. Various sizes and alignments. */ +enum yellowfin_offsets { + TxCtrl=0x00, TxStatus=0x04, TxPtr=0x0C, + TxIntrSel=0x10, TxBranchSel=0x14, TxWaitSel=0x18, + RxCtrl=0x40, RxStatus=0x44, RxPtr=0x4C, + RxIntrSel=0x50, RxBranchSel=0x54, RxWaitSel=0x58, + EventStatus=0x80, IntrEnb=0x82, IntrClear=0x84, IntrStatus=0x86, + ChipRev=0x8C, DMACtrl=0x90, Cnfg=0xA0, RxDepth=0xB8, FlowCtrl=0xBC, + AddrMode=0xD0, StnAddr=0xD2, HashTbl=0xD8, FIFOcfg=0xF8, +}; + +/* The Yellowfin Rx and Tx buffer descriptors. */ +struct yellowfin_desc { + u16 request_cnt; + u16 cmd; + u32 addr; + u32 branch_addr; + u16 result_cnt; + u16 status; +}; + +struct tx_status_words { + u16 tx_cnt; + u16 tx_errs; + u16 total_tx_cnt; + u16 paused; +}; + +/* Bits in yellowfin_desc.cmd */ +enum desc_cmd_bits { + CMD_TX_PKT=0x1000, CMD_RX_BUF=0x2000, CMD_TXSTATUS=0x3000, + CMD_NOP=0x6000, CMD_STOP=0x7000, + BRANCH_ALWAYS=0x0C, INTR_ALWAYS=0x30, WAIT_ALWAYS=0x03, + BRANCH_IFTRUE=0x04, +}; + +/* Bits in yellowfin_desc.status */ +enum desc_status_bits { RX_EOP=0x0040, }; + +/* Bits in the interrupt status/mask registers. */ +enum intr_status_bits { + IntrRxDone=0x01, IntrRxInvalid=0x02, IntrRxPCIFault=0x04,IntrRxPCIErr=0x08, + IntrTxDone=0x10, IntrTxInvalid=0x20, IntrTxPCIFault=0x40,IntrTxPCIErr=0x80, + IntrEarlyRx=0x100, IntrWakeup=0x200, }; + +struct yellowfin_private { + /* Descriptor rings first for alignment. Tx requires a second descriptor + for status. */ + struct yellowfin_desc rx_ring[RX_RING_SIZE]; + struct yellowfin_desc tx_ring[TX_RING_SIZE*2]; + const char *product_name; + struct device *next_module; + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + struct tx_status_words tx_status[TX_RING_SIZE]; + /* The addresses of receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + int chip_id; + struct enet_statistics stats; + struct timer_list timer; /* Media selection timer. */ + int in_interrupt; + unsigned int cur_rx, cur_tx; /* The next free ring entry */ + unsigned int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ + unsigned int tx_full:1; /* The Tx queue is full. */ + unsigned int full_duplex:1; /* Full-duplex operation requested. */ + unsigned int medialock:1; /* Do not sense media. */ + unsigned int default_port:4; /* Last dev->if_port value. */ + u32 pad[4]; /* Used for 32-byte alignment */ +}; + +#ifdef MODULE +/* Used to pass the media type, etc. */ +#define MAX_UNITS 8 /* More are supported, limit only on options */ +static int options[MAX_UNITS] = {-1, -1, -1, -1, -1, -1, -1, -1}; + +#if LINUX_VERSION_CODE > 0x20115 +MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>"); +MODULE_DESCRIPTION("Packet Engines Yellowfin G-NIC Gigabit Ethernet driver"); +MODULE_PARM(max_interrupt_work, "i"); +MODULE_PARM(min_pci_latency, "i"); +MODULE_PARM(mtu, "i"); +MODULE_PARM(debug, "i"); +MODULE_PARM(rx_copybreak, "i"); +MODULE_PARM(options, "1-" __MODULE_STRING(MAX_UNITS) "i"); +#endif + +#endif + +static struct device *yellowfin_probe1(struct device *dev, int ioaddr, int irq, + int chip_id, int options); +static int yellowfin_open(struct device *dev); +static void yellowfin_timer(unsigned long data); +static void yellowfin_tx_timeout(struct device *dev); +static void yellowfin_init_ring(struct device *dev); +static int yellowfin_start_xmit(struct sk_buff *skb, struct device *dev); +static int yellowfin_rx(struct device *dev); +static void yellowfin_interrupt IRQ(int irq, void *dev_instance, struct pt_regs *regs); +static int yellowfin_close(struct device *dev); +static struct enet_statistics *yellowfin_get_stats(struct device *dev); +#ifdef NEW_MULTICAST +static void set_rx_mode(struct device *dev); +#else +static void set_rx_mode(struct device *dev, int num_addrs, void *addrs); +#endif + + + +#ifdef MODULE +/* A list of all installed Yellowfin devices, for removing the driver module. */ +static struct device *root_yellowfin_dev = NULL; +#endif + +int yellowfin_probe(struct device *dev) +{ + int cards_found = 0; + static int pci_index = 0; /* Static, for multiple probe calls. */ + + /* Ideally we would detect all network cards in slot order. That would + be best done a central PCI probe dispatch, which wouldn't work + well with the current structure. So instead we detect just the + Yellowfin cards in slot order. */ + + if (pcibios_present()) { + unsigned char pci_bus, pci_device_fn; + + for (;pci_index < 0xff; pci_index++) { + u8 pci_irq_line, pci_latency; + u16 pci_command, vendor, device; + u32 pci_ioaddr, chip_idx = 0; + +#ifdef REVERSE_PROBE_ORDER + if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8, + 0xfe - pci_index, + &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + continue; +#else + if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8, + pci_index, + &pci_bus, &pci_device_fn) + != PCIBIOS_SUCCESSFUL) + break; +#endif + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + + if (vendor != PCI_VENDOR_ID_PKT_ENG) + continue; + + if (device != PCI_DEVICE_ID_YELLOWFIN) + continue; + + if (yellowfin_debug > 2) + printk("Found Packet Engines Yellowfin G-NIC at I/O %#x, IRQ %d.\n", + pci_ioaddr, pci_irq_line); + + if (check_region(pci_ioaddr, YELLOWFIN_TOTAL_SIZE)) + continue; + +#ifdef MODULE + dev = yellowfin_probe1(dev, pci_ioaddr, pci_irq_line, chip_idx, + cards_found < MAX_UNITS ? options[cards_found] : 0); +#else + dev = yellowfin_probe1(dev, pci_ioaddr, pci_irq_line, chip_idx, + dev ? dev->mem_start : 0); +#endif + + if (dev) { + /* Get and check the bus-master and latency values. */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + if ( ! (pci_command & PCI_COMMAND_MASTER)) { + printk(" PCI Master Bit has not been set! Setting...\n"); + pci_command |= PCI_COMMAND_MASTER; + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, pci_command); + } + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < min_pci_latency) { + printk(" PCI latency timer (CFLT) is unreasonably low at %d." + " Setting to %d clocks.\n", + pci_latency, min_pci_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, min_pci_latency); + } else if (yellowfin_debug > 1) + printk(" PCI latency timer (CFLT) is %#x.\n", pci_latency); + dev = 0; + cards_found++; + } + } + } + +#if defined (MODULE) + return cards_found; +#else + return 0; +#endif +} + +static struct device *yellowfin_probe1(struct device *dev, int ioaddr, int irq, + int chip_id, int options) +{ + static int did_version = 0; /* Already printed version info. */ + struct yellowfin_private *yp; + int i; + + if (yellowfin_debug > 0 && did_version++ == 0) + printk(version); + + dev = init_etherdev(dev, sizeof(struct yellowfin_private)); + + printk("%s: P-E Yellowfin type %8x at %#3x, ", + dev->name, inl(ioaddr + ChipRev), ioaddr); + + for (i = 0; i < 5; i++) + printk("%2.2x:", inb(ioaddr + StnAddr + i)); + printk("%2.2x, IRQ %d.\n", inb(ioaddr + StnAddr + i), irq); + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb(ioaddr + StnAddr + i); + + /* Reset the chip. */ + outl(0x80000000, ioaddr + DMACtrl); + + + /* We do a request_region() only to register /proc/ioports info. */ + request_region(ioaddr, YELLOWFIN_TOTAL_SIZE, card_name); + + dev->base_addr = ioaddr; + dev->irq = irq; + + /* Make certain the descriptor lists are aligned. */ + yp = (void *)(((long)kmalloc(sizeof(*yp), GFP_KERNEL) + 31) & ~31); + memset(yp, 0, sizeof(*yp)); + dev->priv = yp; + +#ifdef MODULE + yp->next_module = root_yellowfin_dev; + root_yellowfin_dev = dev; +#endif + + yp->chip_id = chip_id; + + yp->full_duplex = 1; +#ifdef YELLOWFIN_DEFAULT_MEDIA + yp->default_port = YELLOWFIN_DEFAULT_MEDIA; +#endif +#ifdef YELLOWFIN_NO_MEDIA_SWITCH + yp->medialock = 1; +#endif + + /* The lower four bits are the media type. */ + if (options > 0) { + yp->full_duplex = (options & 16) ? 1 : 0; + yp->default_port = options & 15; + if (yp->default_port) + yp->medialock = 1; + } + + /* The Yellowfin-specific entries in the device structure. */ + dev->open = &yellowfin_open; + dev->hard_start_xmit = &yellowfin_start_xmit; + dev->stop = &yellowfin_close; + dev->get_stats = &yellowfin_get_stats; + dev->set_multicast_list = &set_rx_mode; + if (mtu) + dev->mtu = mtu; + + /* todo: Reset the xcvr interface and turn on heartbeat. */ + + return dev; +} + + +static int +yellowfin_open(struct device *dev) +{ + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + int ioaddr = dev->base_addr; + + /* Reset the chip. */ + outl(0x80000000, ioaddr + DMACtrl); + +#ifdef SA_SHIRQ + if (request_irq(dev->irq, &yellowfin_interrupt, SA_SHIRQ, + card_name, dev)) { + return -EAGAIN; + } +#else + if (irq2dev_map[dev->irq] != NULL + || (irq2dev_map[dev->irq] = dev) == NULL + || dev->irq == 0 + || request_irq(dev->irq, &yellowfin_interrupt, 0, card_name)) { + return -EAGAIN; + } +#endif + + if (yellowfin_debug > 1) + printk("%s: yellowfin_open() irq %d.\n", dev->name, dev->irq); + + MOD_INC_USE_COUNT; + + yellowfin_init_ring(dev); + + outl(virt_to_bus(yp->rx_ring), ioaddr + RxPtr); + outl(virt_to_bus(yp->tx_ring), ioaddr + TxPtr); + + /* Set up various condition 'select' registers. + There are no options here. */ + outl(0x00800080, ioaddr + TxIntrSel); /* Interrupt on Tx abort */ + outl(0x00800080, ioaddr + TxBranchSel); /* Branch on Tx abort */ + outl(0x00400040, ioaddr + TxWaitSel); /* Wait on Tx status */ + outl(0x00400040, ioaddr + RxIntrSel); /* Interrupt on Rx done */ + outl(0x00400040, ioaddr + RxBranchSel); /* Branch on Rx error */ + outl(0x00400040, ioaddr + RxWaitSel); /* Wait on Rx done */ + + /* Initialize other registers: with so many this eventually this will + converted to an offset/value list. */ + outl(dma_ctrl, ioaddr + DMACtrl); + outw(fifo_cfg, ioaddr + FIFOcfg); + /* Enable automatic generation of flow control frames, period 0xffff. */ + outl(0x0030FFFF, ioaddr + FlowCtrl); + + if (dev->if_port == 0) + dev->if_port = yp->default_port; + + dev->tbusy = 0; + dev->interrupt = 0; + yp->in_interrupt = 0; + + /* We are always in full-duplex mode with the current chip! */ + yp->full_duplex = 1; + + /* Setting the Rx mode will start the Rx process. */ + outw(0x01CD | (yp->full_duplex ? 2 : 0), ioaddr + Cnfg); +#ifdef NEW_MULTICAST + set_rx_mode(dev); +#else + set_rx_mode(dev, 0, 0); +#endif + + dev->start = 1; + + /* Enable interrupts by setting the interrupt mask. */ + outw(0x81ff, ioaddr + IntrEnb); /* See enum intr_status_bits */ + outw(0x0000, ioaddr + EventStatus); /* Clear non-interrupting events */ + outl(0x80008000, ioaddr + RxCtrl); /* Start Rx and Tx channels. */ + outl(0x80008000, ioaddr + TxCtrl); + + if (yellowfin_debug > 2) { + printk("%s: Done yellowfin_open().\n", + dev->name); + } + /* Set the timer to check for link beat. */ + init_timer(&yp->timer); + yp->timer.expires = RUN_AT((24*HZ)/10); /* 2.4 sec. */ + yp->timer.data = (unsigned long)dev; + yp->timer.function = &yellowfin_timer; /* timer handler */ + add_timer(&yp->timer); + + return 0; +} + +static void yellowfin_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + int ioaddr = dev->base_addr; + int next_tick = 0; + + if (yellowfin_debug > 3) { + printk("%s: Yellowfin timer tick, status %8.8x.\n", + dev->name, inl(ioaddr + IntrStatus)); + } + if (next_tick) { + yp->timer.expires = RUN_AT(next_tick); + add_timer(&yp->timer); + } +} + +static void yellowfin_tx_timeout(struct device *dev) +{ + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + int ioaddr = dev->base_addr; + + printk("%s: Yellowfin transmit timed out, status %8.8x, resetting...\n", + dev->name, inl(ioaddr)); + +#ifndef __alpha__ + { + int i; + printk(" Rx ring %8.8x: ", (int)yp->rx_ring); + for (i = 0; i < RX_RING_SIZE; i++) + printk(" %8.8x", (unsigned int)yp->rx_ring[i].status); + printk("\n Tx ring %8.8x: ", (int)yp->tx_ring); + for (i = 0; i < TX_RING_SIZE; i++) + printk(" %4.4x /%4.4x", yp->tx_status[i].tx_errs, yp->tx_ring[i].status); + printk("\n"); + } +#endif + + /* Perhaps we should reinitialize the hardware here. */ + dev->if_port = 0; + /* Stop and restart the chip's Tx processes . */ + + /* Trigger an immediate transmit demand. */ + + dev->trans_start = jiffies; + yp->stats.tx_errors++; + return; +} + + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void +yellowfin_init_ring(struct device *dev) +{ + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + int i; + + yp->tx_full = 0; + yp->cur_rx = yp->cur_tx = 0; + yp->dirty_rx = yp->dirty_tx = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb; + int pkt_buf_sz = (dev->mtu <= 1500 ? PKT_BUF_SZ : dev->mtu + 32); + + yp->rx_ring[i].request_cnt = pkt_buf_sz; + yp->rx_ring[i].cmd = CMD_RX_BUF | INTR_ALWAYS; + + skb = DEV_ALLOC_SKB(pkt_buf_sz); + skb_reserve(skb, 2); /* 16 byte align the IP header. */ + yp->rx_skbuff[i] = skb; + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ +#if LINUX_VERSION_CODE > 0x10300 + yp->rx_ring[i].addr = virt_to_bus(skb->tail); +#else + yp->rx_ring[i].addr = virt_to_bus(skb->data); +#endif + yp->rx_ring[i].branch_addr = virt_to_bus(&yp->rx_ring[i+1]); + } + /* Mark the last entry as wrapping the ring. */ + yp->rx_ring[i-1].cmd = CMD_RX_BUF | INTR_ALWAYS | BRANCH_ALWAYS; + yp->rx_ring[i-1].branch_addr = virt_to_bus(&yp->rx_ring[0]); + +/*#define NO_TXSTATS*/ +#ifdef NO_TXSTATS + /* In this mode the Tx ring needs only a single descriptor. */ + for (i = 0; i < TX_RING_SIZE; i++) { + yp->tx_skbuff[i] = 0; + yp->tx_ring[i].cmd = CMD_STOP; + yp->tx_ring[i].branch_addr = virt_to_bus(&yp->tx_ring[i+1]); + } + yp->tx_ring[--i].cmd = CMD_STOP | BRANCH_ALWAYS; /* Wrap ring */ + yp->tx_ring[i].branch_addr = virt_to_bus(&yp->tx_ring[0]); +#else + /* Tx ring needs a pair of descriptors, the second for the status. */ + for (i = 0; i < TX_RING_SIZE*2; i++) { + yp->tx_skbuff[i/2] = 0; + yp->tx_ring[i].cmd = CMD_STOP; /* Branch on Tx error. */ + yp->tx_ring[i].branch_addr = virt_to_bus(&yp->tx_ring[i+1]); + i++; + yp->tx_ring[i].cmd = CMD_TXSTATUS; /* Interrupt, no wait. */ + yp->tx_ring[i].request_cnt = sizeof(yp->tx_status[i]); + yp->tx_ring[i].addr = virt_to_bus(&yp->tx_status[i/2]); + yp->tx_ring[i].branch_addr = virt_to_bus(&yp->tx_ring[i+1]); + } + /* Wrap ring */ + yp->tx_ring[--i].cmd = CMD_TXSTATUS | BRANCH_ALWAYS | INTR_ALWAYS; + yp->tx_ring[i].branch_addr = virt_to_bus(&yp->tx_ring[0]); +#endif +} + +static int +yellowfin_start_xmit(struct sk_buff *skb, struct device *dev) +{ + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + unsigned entry; + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start < TX_TIMEOUT) + return 1; + yellowfin_tx_timeout(dev); + return 1; + } + + /* Caution: the write order is important here, set the base address + with the "ownership" bits last. */ + + /* Calculate the next Tx descriptor entry. */ + entry = yp->cur_tx % TX_RING_SIZE; + + yp->tx_skbuff[entry] = skb; + +#ifdef NO_TXSTATS + yp->tx_ring[entry].request_cnt = skb->len; + yp->tx_ring[entry].addr = virt_to_bus(skb->data); + yp->tx_ring[entry].status = 0; + if (entry >= TX_RING_SIZE-1) { + yp->tx_ring[0].cmd = CMD_STOP; /* New stop command. */ + yp->tx_ring[TX_RING_SIZE-1].cmd = CMD_TX_PKT | BRANCH_ALWAYS; + } else { + yp->tx_ring[entry+1].cmd = CMD_STOP; /* New stop command. */ + yp->tx_ring[entry].cmd = CMD_TX_PKT | BRANCH_IFTRUE; + } + yp->cur_tx++; +#else + yp->tx_ring[entry<<1].request_cnt = skb->len; + yp->tx_ring[entry<<1].addr = virt_to_bus(skb->data); + /* The input_last (status-write) command is constant, but we must rewrite + the subsequent 'stop' command. */ + + yp->cur_tx++; + { + unsigned next_entry = yp->cur_tx % TX_RING_SIZE; + yp->tx_ring[next_entry<<1].cmd = CMD_STOP; + } + /* Final step -- overwrite the old 'stop' command. */ + + yp->tx_ring[entry<<1].cmd = + (entry % 6) == 0 ? CMD_TX_PKT | INTR_ALWAYS | BRANCH_IFTRUE : + CMD_TX_PKT | BRANCH_IFTRUE; +#endif + + /* Todo: explicitly flush cache lines here. */ + + /* Wake the potentially-idle transmit channel. */ + outl(0x10001000, dev->base_addr + TxCtrl); + + if (yp->cur_tx - yp->dirty_tx < TX_RING_SIZE - 1) + clear_bit(0, (void*)&dev->tbusy); /* Typical path */ + else + yp->tx_full = 1; + dev->trans_start = jiffies; + + if (yellowfin_debug > 4) { + printk("%s: Yellowfin transmit frame #%d queued in slot %d.\n", + dev->name, yp->cur_tx, entry); + } + return 0; +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static void yellowfin_interrupt IRQ(int irq, void *dev_instance, struct pt_regs *regs) +{ +#ifdef SA_SHIRQ /* Use the now-standard shared IRQ implementation. */ + struct device *dev = (struct device *)dev_instance; +#else + struct device *dev = (struct device *)(irq2dev_map[irq]); +#endif + + struct yellowfin_private *lp; + int ioaddr, boguscnt = max_interrupt_work; + + if (dev == NULL) { + printk ("yellowfin_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + ioaddr = dev->base_addr; + lp = (struct yellowfin_private *)dev->priv; + if (test_and_set_bit(0, (void*)&lp->in_interrupt)) { + dev->interrupt = 1; + printk(KERN_ERR "%s: Re-entering the interrupt handler.\n", dev->name); + return; + } + + do { + u16 intr_status = inw(ioaddr + IntrClear); + unsigned dirty_tx = lp->dirty_tx; + + if (yellowfin_debug > 4) + printk("%s: Yellowfin interrupt, status %4.4x.\n", + dev->name, intr_status); + + if (intr_status == 0) + break; + + if (intr_status & (IntrRxDone | IntrEarlyRx)) + yellowfin_rx(dev); + +#ifdef NO_TXSTATS + for (; lp->cur_tx - dirty_tx > 0; dirty_tx++) { + int entry = dirty_tx % TX_RING_SIZE; + if (lp->tx_ring[entry].status == 0) + break; + /* Free the original skb. */ + dev_kfree_skb(lp->tx_skbuff[entry], FREE_WRITE); + lp->tx_skbuff[entry] = 0; + lp->stats.tx_packets++; + } + if (lp->tx_full && dev->tbusy + && lp->cur_tx - dirty_tx < TX_RING_SIZE - 4) { + /* The ring is no longer full, clear tbusy. */ + lp->tx_full = 0; + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + lp->dirty_tx = dirty_tx; +#else + if (intr_status & IntrTxDone + || lp->tx_status[dirty_tx % TX_RING_SIZE].tx_errs) { + + for (dirty_tx = lp->dirty_tx; lp->cur_tx - dirty_tx > 0; + dirty_tx++) { + /* Todo: optimize this. */ + int entry = dirty_tx % TX_RING_SIZE; + u16 tx_errs = lp->tx_status[entry].tx_errs; + + if (tx_errs == 0) + break; /* It still hasn't been Txed */ + if (tx_errs & 0xF8100000) { + /* There was an major error, log it. */ +#ifndef final_version + if (yellowfin_debug > 1) + printk("%s: Transmit error, Tx status %4.4x.\n", + dev->name, tx_errs); +#endif + lp->stats.tx_errors++; + if (tx_errs & 0xF800) lp->stats.tx_aborted_errors++; + if (tx_errs & 0x0800) lp->stats.tx_carrier_errors++; + if (tx_errs & 0x2000) lp->stats.tx_window_errors++; + if (tx_errs & 0x8000) lp->stats.tx_fifo_errors++; +#ifdef ETHER_STATS + if (tx_errs & 0x1000) lp->stats.collisions16++; +#endif + } else { +#ifdef ETHER_STATS + if (status & 0x0400) lp->stats.tx_deferred++; +#endif + lp->stats.collisions += tx_errs & 15; + lp->stats.tx_packets++; + } + + /* Free the original skb. */ + dev_kfree_skb(lp->tx_skbuff[entry], FREE_WRITE); + lp->tx_skbuff[entry] = 0; + /* Mark status as empty. */ + lp->tx_status[entry].tx_errs = 0; + } + +#ifndef final_version + if (lp->cur_tx - dirty_tx > TX_RING_SIZE) { + printk("%s: Out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dev->name, dirty_tx, lp->cur_tx, lp->tx_full); + dirty_tx += TX_RING_SIZE; + } +#endif + + if (lp->tx_full && dev->tbusy + && lp->cur_tx - dirty_tx < TX_RING_SIZE - 2) { + /* The ring is no longer full, clear tbusy. */ + lp->tx_full = 0; + clear_bit(0, (void*)&dev->tbusy); + mark_bh(NET_BH); + } + + lp->dirty_tx = dirty_tx; + } +#endif + + /* Log errors and other events. */ + if (intr_status & 0x2ee) { /* Abnormal error summary. */ + printk("%s: Something Wicked happened! %4.4x.\n", + dev->name, intr_status); + /* Hmmmmm, it's not clear what to do here. */ + if (intr_status & (IntrTxPCIErr | IntrTxPCIFault)) + lp->stats.tx_errors++; + if (intr_status & (IntrRxPCIErr | IntrRxPCIFault)) + lp->stats.rx_errors++; + } + if (--boguscnt < 0) { + printk("%s: Too much work at interrupt, status=0x%4.4x.\n", + dev->name, intr_status); + break; + } + } while (1); + + if (yellowfin_debug > 3) + printk("%s: exiting interrupt, status=%#4.4x.\n", + dev->name, inw(ioaddr + IntrStatus)); + + /* Code that should never be run! Perhaps remove after testing.. */ + { + static int stopit = 10; + if (dev->start == 0 && --stopit < 0) { + printk("%s: Emergency stop, looping startup interrupt.\n", + dev->name); +#ifdef SA_SHIRQ + free_irq(irq, dev); +#else + free_irq(irq); +#endif + } + } + + dev->interrupt = 0; + clear_bit(0, (void*)&lp->in_interrupt); + return; +} + +/* This routine is logically part of the interrupt handler, but separated + for clarity and better register allocation. */ +static int +yellowfin_rx(struct device *dev) +{ + struct yellowfin_private *lp = (struct yellowfin_private *)dev->priv; + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + int entry = lp->cur_rx % RX_RING_SIZE; + int boguscnt = 20; + + if (yellowfin_debug > 4) { + printk(" In yellowfin_rx(), entry %d status %4.4x.\n", entry, + yp->rx_ring[entry].status); + printk(" #%d desc. %4.4x %4.4x %8.8x %4.4x %4.4x.\n", + entry, yp->rx_ring[entry].cmd, + yp->rx_ring[entry].request_cnt, yp->rx_ring[entry].addr, + yp->rx_ring[entry].result_cnt, yp->rx_ring[entry].status); + } + + + /* If EOP is set on the next entry, it's a new packet. Send it up. */ + while (yp->rx_ring[entry].status) { + /* Todo: optimize this mess. */ + u16 desc_status = yp->rx_ring[entry].status; + struct yellowfin_desc *desc = &lp->rx_ring[entry]; + int frm_size = desc->request_cnt - desc->result_cnt; + u8 *buf_addr = bus_to_virt(lp->rx_ring[entry].addr); + s16 frame_status = *(s16*)&(buf_addr[frm_size - 2]); + + if (yellowfin_debug > 4) + printk(" yellowfin_rx() status was %4.4x.\n", frame_status); + if (--boguscnt < 0) + break; + if ( ! (desc_status & RX_EOP)) { + printk("%s: Oversized Ethernet frame spanned multiple buffers," + " status %4.4x!\n", dev->name, desc_status); + lp->stats.rx_length_errors++; + } else if (frame_status & 0x0038) { + /* There was a error. */ + if (yellowfin_debug > 3) + printk(" yellowfin_rx() Rx error was %4.4x.\n", frame_status); + lp->stats.rx_errors++; + if (frame_status & 0x0060) lp->stats.rx_length_errors++; + if (frame_status & 0x0008) lp->stats.rx_frame_errors++; + if (frame_status & 0x0010) lp->stats.rx_crc_errors++; + if (frame_status < 0) lp->stats.rx_dropped++; +#ifdef YF_PROTOTYPE /* Support for prototype hardware errata. */ + } else if (memcmp(bus_to_virt(lp->rx_ring[entry].addr), + dev->dev_addr, 6) != 0 + && memcmp(bus_to_virt(lp->rx_ring[entry].addr), + "\377\377\377\377\377\377", 6) != 0) { + printk("%s: Bad frame to %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x.\n", + dev->name, + ((char *)bus_to_virt(lp->rx_ring[entry].addr))[0], + ((char *)bus_to_virt(lp->rx_ring[entry].addr))[1], + ((char *)bus_to_virt(lp->rx_ring[entry].addr))[2], + ((char *)bus_to_virt(lp->rx_ring[entry].addr))[3], + ((char *)bus_to_virt(lp->rx_ring[entry].addr))[4], + ((char *)bus_to_virt(lp->rx_ring[entry].addr))[5]); + bogus_rx++; +#endif + } else { + u8 bogus_cnt = buf_addr[frm_size - 8]; + int pkt_len = frm_size - 8 - bogus_cnt; + struct sk_buff *skb; + int rx_in_place = 0; + + /* Check if the packet is long enough to just accept without + copying to a properly sized skbuff. */ + if (pkt_len > rx_copybreak) { + struct sk_buff *newskb; + char *temp; + + /* Get a fresh skbuff to replace the filled one. */ + newskb = DEV_ALLOC_SKB(dev->mtu <= 1500 ? PKT_BUF_SZ + : dev->mtu + 32); + if (newskb == NULL) { + skb = 0; /* No memory, drop the packet. */ + goto memory_squeeze; + } + /* Pass up the skb already on the Rx ring. */ + skb = lp->rx_skbuff[entry]; + temp = skb_put(skb, pkt_len); + if (bus_to_virt(lp->rx_ring[entry].addr) != temp) + printk("%s: Warning -- the skbuff addresses do not match" + " in yellowfin_rx: %p vs. %p / %p.\n", dev->name, + bus_to_virt(lp->rx_ring[entry].addr), + skb->head, temp); + rx_in_place = 1; + lp->rx_skbuff[entry] = newskb; + newskb->dev = dev; + skb_reserve(newskb, 2); /* 16 byte align IP header */ + lp->rx_ring[entry].addr = virt_to_bus(newskb->tail); + } else + skb = DEV_ALLOC_SKB(pkt_len + 2); + memory_squeeze: + if (skb == NULL) { + printk("%s: Memory squeeze, deferring packet.\n", dev->name); + /* todo: Check that at least two ring entries are free. + If not, free one and mark stats->rx_dropped++. */ + break; + } + skb->dev = dev; + if (! rx_in_place) { + skb_reserve(skb, 2); /* 16 byte align the data fields */ + memcpy(skb_put(skb, pkt_len), + bus_to_virt(lp->rx_ring[entry].addr), pkt_len); + } +#if LINUX_VERSION_CODE > 0x10300 + skb->protocol = eth_type_trans(skb, dev); +#else + skb->len = pkt_len; +#endif + netif_rx(skb); + lp->stats.rx_packets++; + } + + /* Mark this entry as being the end-of-list, and the prior entry + as now valid. */ + lp->rx_ring[entry].cmd = CMD_STOP; + yp->rx_ring[entry].status = 0; + { + int prev_entry = entry - 1; + if (prev_entry < 0) + lp->rx_ring[RX_RING_SIZE - 1].cmd = + CMD_RX_BUF | INTR_ALWAYS | BRANCH_ALWAYS; + else + lp->rx_ring[prev_entry].cmd = CMD_RX_BUF | INTR_ALWAYS; + } + entry = (++lp->cur_rx) % RX_RING_SIZE; + } + /* todo: restart Rx engine if stopped. For now we just make the Rx ring + large enough to avoid this. */ + + return 0; +} + +static int +yellowfin_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + int i; + + dev->start = 0; + dev->tbusy = 1; + + if (yellowfin_debug > 1) { + printk("%s: Shutting down ethercard, status was Tx %4.4x Rx %4.4x Int %2.2x.\n", + dev->name, inw(ioaddr + TxStatus), + inw(ioaddr + RxStatus), inl(ioaddr + IntrStatus)); + printk("%s: Queue pointers were Tx %d / %d, Rx %d / %d.\n", + dev->name, yp->cur_tx, yp->dirty_tx, yp->cur_rx, yp->dirty_rx); + } + + /* Disable interrupts by clearing the interrupt mask. */ + outw(0x0000, ioaddr + IntrEnb); + + /* Stop the chip's Tx and Rx processes. */ + outl(0x80000000, ioaddr + RxCtrl); + outl(0x80000000, ioaddr + TxCtrl); + + del_timer(&yp->timer); + +#ifdef __i386__ + if (yellowfin_debug > 2) { + printk("\n Tx ring at %8.8x:\n", (int)virt_to_bus(yp->tx_ring)); + for (i = 0; i < TX_RING_SIZE*2; i++) + printk(" %c #%d desc. %4.4x %4.4x %8.8x %8.8x %4.4x %4.4x.\n", + inl(ioaddr + TxPtr) == (long)&yp->tx_ring[i] ? '>' : ' ', + i, yp->tx_ring[i].cmd, + yp->tx_ring[i].request_cnt, yp->tx_ring[i].addr, + yp->tx_ring[i].branch_addr, + yp->tx_ring[i].result_cnt, yp->tx_ring[i].status); + printk(" Tx status %p:\n", yp->tx_status); + for (i = 0; i < TX_RING_SIZE; i++) + printk(" #%d status %4.4x %4.4x %4.4x %4.4x.\n", + i, yp->tx_status[i].tx_cnt, yp->tx_status[i].tx_errs, + yp->tx_status[i].total_tx_cnt, yp->tx_status[i].paused); + + printk("\n Rx ring %8.8x:\n", (int)virt_to_bus(yp->rx_ring)); + for (i = 0; i < RX_RING_SIZE; i++) { + printk(" %c #%d desc. %4.4x %4.4x %8.8x %4.4x %4.4x\n", + inl(ioaddr + RxPtr) == (long)&yp->rx_ring[i] ? '>' : ' ', + i, yp->rx_ring[i].cmd, + yp->rx_ring[i].request_cnt, yp->rx_ring[i].addr, + yp->rx_ring[i].result_cnt, yp->rx_ring[i].status); + if (yellowfin_debug > 5) { + if (*(u8*)yp->rx_ring[i].addr != 0x69) { + int j; + for (j = 0; j < 0x50; j++) + printk(" %4.4x", ((u16*)yp->rx_ring[i].addr)[j]); + printk("\n"); + } + } + } + } +#endif /* __i386__ debugging only */ + +#ifdef SA_SHIRQ + free_irq(dev->irq, dev); +#else + free_irq(dev->irq); + irq2dev_map[dev->irq] = 0; +#endif + + /* Free all the skbuffs in the Rx queue. */ + for (i = 0; i < RX_RING_SIZE; i++) { + yp->rx_ring[i].cmd = CMD_STOP; + yp->rx_ring[i].addr = 0xBADF00D0; /* An invalid address. */ + if (yp->rx_skbuff[i]) { +#if LINUX_VERSION_CODE < 0x20100 + yp->rx_skbuff[i]->free = 1; +#endif + dev_kfree_skb(yp->rx_skbuff[i], FREE_WRITE); + } + yp->rx_skbuff[i] = 0; + } + for (i = 0; i < TX_RING_SIZE; i++) { + if (yp->tx_skbuff[i]) + dev_kfree_skb(yp->tx_skbuff[i], FREE_WRITE); + yp->tx_skbuff[i] = 0; + } + +#ifdef YF_PROTOTYPE /* Support for prototype hardware errata. */ + if (yellowfin_debug > 0) { + printk("%s: Received %d frames that we should not have.\n", + dev->name, bogus_rx); + } +#endif + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct enet_statistics * +yellowfin_get_stats(struct device *dev) +{ + struct yellowfin_private *yp = (struct yellowfin_private *)dev->priv; + return &yp->stats; +} + +/* Set or clear the multicast filter for this adaptor. */ + +/* The little-endian AUTODIN32 ethernet CRC calculation. + N.B. Do not use for bulk data, use a table-based routine instead. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial_le = 0xedb88320U; +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + + +#ifdef NEW_MULTICAST +static void set_rx_mode(struct device *dev) +#else +static void set_rx_mode(struct device *dev, int num_addrs, void *addrs); +#endif +{ + int ioaddr = dev->base_addr; + u16 cfg_value = inw(ioaddr + Cnfg); + + /* Stop the Rx process to change any value. */ + outw(cfg_value & ~0x1000, ioaddr + Cnfg); + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + /* Unconditionally log net taps. */ + printk("%s: Promiscuous mode enabled.\n", dev->name); + outw(0x000F, ioaddr + AddrMode); + } else if ((dev->mc_count > 64) || (dev->flags & IFF_ALLMULTI)) { + /* Too many to filter well, or accept all multicasts. */ + outw(0x000B, ioaddr + AddrMode); + } else if (dev->mc_count > 0) { /* Must use the multicast hash table. */ + struct dev_mc_list *mclist; + u16 hash_table[4]; + int i; + memset(hash_table, 0, sizeof(hash_table)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) { + /* Due to a bug in the early chip versions, multiple filter + slots must be set for each address. */ + set_bit((ether_crc_le(3, mclist->dmi_addr) >> 3) & 0x3f, + hash_table); + set_bit((ether_crc_le(4, mclist->dmi_addr) >> 3) & 0x3f, + hash_table); + set_bit((ether_crc_le(5, mclist->dmi_addr) >> 3) & 0x3f, + hash_table); + set_bit((ether_crc_le(6, mclist->dmi_addr) >> 3) & 0x3f, + hash_table); + } + /* Copy the hash table to the chip. */ + for (i = 0; i < 4; i++) + outw(hash_table[i], ioaddr + HashTbl + i*2); + outw(0x0003, ioaddr + AddrMode); + } else { /* Normal, unicast/broadcast-only mode. */ + outw(0x0001, ioaddr + AddrMode); + } + /* Restart the Rx process. */ + outw(cfg_value | 0x1000, ioaddr + Cnfg); +} + +#ifdef MODULE + +/* An additional parameter that may be passed in... */ +static int debug = -1; + +int +init_module(void) +{ + int cards_found; + + if (debug >= 0) + yellowfin_debug = debug; + + root_yellowfin_dev = NULL; + cards_found = yellowfin_probe(0); + + return cards_found ? 0 : -ENODEV; +} + +void +cleanup_module(void) +{ + struct device *next_dev; + + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + while (root_yellowfin_dev) { + next_dev = ((struct yellowfin_private *)root_yellowfin_dev->priv)->next_module; + unregister_netdev(root_yellowfin_dev); + release_region(root_yellowfin_dev->base_addr, YELLOWFIN_TOTAL_SIZE); + kfree(root_yellowfin_dev); + root_yellowfin_dev = next_dev; + } +} + +#endif /* MODULE */ + +/* + * Local variables: + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c yellowfin.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * SMP-compile-command: "gcc -D__SMP__ -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c yellowfin.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/src/drivers/net/znet.c b/linux/src/drivers/net/znet.c new file mode 100644 index 00000000..a9996fd6 --- /dev/null +++ b/linux/src/drivers/net/znet.c @@ -0,0 +1,746 @@ +/* znet.c: An Zenith Z-Note ethernet driver for linux. */ + +static const char *version = "znet.c:v1.02 9/23/94 becker@cesdis.gsfc.nasa.gov\n"; + +/* + Written by Donald Becker. + + The author may be reached as becker@cesdis.gsfc.nasa.gov. + This driver is based on the Linux skeleton driver. The copyright of the + skeleton driver is held by the United States Government, as represented + by DIRNSA, and it is released under the GPL. + + Thanks to Mike Hollick for alpha testing and suggestions. + + References: + The Crynwr packet driver. + + "82593 CSMA/CD Core LAN Controller" Intel datasheet, 1992 + Intel Microcommunications Databook, Vol. 1, 1990. + As usual with Intel, the documentation is incomplete and inaccurate. + I had to read the Crynwr packet driver to figure out how to actually + use the i82593, and guess at what register bits matched the loosely + related i82586. + + Theory of Operation + + The i82593 used in the Zenith Z-Note series operates using two(!) slave + DMA channels, one interrupt, and one 8-bit I/O port. + + While there several ways to configure '593 DMA system, I chose the one + that seemed commensurate with the highest system performance in the face + of moderate interrupt latency: Both DMA channels are configured as + recirculating ring buffers, with one channel (#0) dedicated to Rx and + the other channel (#1) to Tx and configuration. (Note that this is + different than the Crynwr driver, where the Tx DMA channel is initialized + before each operation. That approach simplifies operation and Tx error + recovery, but requires additional I/O in normal operation and precludes + transmit buffer chaining.) + + Both rings are set to 8192 bytes using {TX,RX}_RING_SIZE. This provides + a reasonable ring size for Rx, while simplifying DMA buffer allocation -- + DMA buffers must not cross a 128K boundary. (In truth the size selection + was influenced by my lack of '593 documentation. I thus was constrained + to use the Crynwr '593 initialization table, which sets the Rx ring size + to 8K.) + + Despite my usual low opinion about Intel-designed parts, I must admit + that the bulk data handling of the i82593 is a good design for + an integrated system, like a laptop, where using two slave DMA channels + doesn't pose a problem. I still take issue with using only a single I/O + port. In the same controlled environment there are essentially no + limitations on I/O space, and using multiple locations would eliminate + the need for multiple operations when looking at status registers, + setting the Rx ring boundary, or switching to promiscuous mode. + + I also question Zenith's selection of the '593: one of the advertised + advantages of earlier Intel parts was that if you figured out the magic + initialization incantation you could use the same part on many different + network types. Zenith's use of the "FriendlyNet" (sic) connector rather + than an on-board transceiver leads me to believe that they were planning + to take advantage of this. But, uhmmm, the '593 omits all but ethernet + functionality from the serial subsystem. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> + +#ifndef ZNET_DEBUG +#define ZNET_DEBUG 1 +#endif +static unsigned int znet_debug = ZNET_DEBUG; + +/* The DMA modes we need aren't in <dma.h>. */ +#define DMA_RX_MODE 0x14 /* Auto init, I/O to mem, ++, demand. */ +#define DMA_TX_MODE 0x18 /* Auto init, Mem to I/O, ++, demand. */ +#define dma_page_eq(ptr1, ptr2) ((long)(ptr1)>>17 == (long)(ptr2)>>17) +#define DMA_BUF_SIZE 8192 +#define RX_BUF_SIZE 8192 +#define TX_BUF_SIZE 8192 + +/* Commands to the i82593 channel 0. */ +#define CMD0_CHNL_0 0x00 +#define CMD0_CHNL_1 0x10 /* Switch to channel 1. */ +#define CMD0_NOP (CMD0_CHNL_0) +#define CMD0_PORT_1 CMD0_CHNL_1 +#define CMD1_PORT_0 1 +#define CMD0_IA_SETUP 1 +#define CMD0_CONFIGURE 2 +#define CMD0_MULTICAST_LIST 3 +#define CMD0_TRANSMIT 4 +#define CMD0_DUMP 6 +#define CMD0_DIAGNOSE 7 +#define CMD0_Rx_ENABLE 8 +#define CMD0_Rx_DISABLE 10 +#define CMD0_Rx_STOP 11 +#define CMD0_RETRANSMIT 12 +#define CMD0_ABORT 13 +#define CMD0_RESET 14 + +#define CMD0_ACK 0x80 + +#define CMD0_STAT0 (0 << 5) +#define CMD0_STAT1 (1 << 5) +#define CMD0_STAT2 (2 << 5) +#define CMD0_STAT3 (3 << 5) + +#define net_local znet_private +struct znet_private { + int rx_dma, tx_dma; + struct enet_statistics stats; + /* The starting, current, and end pointers for the packet buffers. */ + ushort *rx_start, *rx_cur, *rx_end; + ushort *tx_start, *tx_cur, *tx_end; + ushort tx_buf_len; /* Tx buffer length, in words. */ +}; + +/* Only one can be built-in;-> */ +static struct znet_private zn; +static ushort dma_buffer1[DMA_BUF_SIZE/2]; +static ushort dma_buffer2[DMA_BUF_SIZE/2]; +static ushort dma_buffer3[DMA_BUF_SIZE/2 + 8]; + +/* The configuration block. What an undocumented nightmare. The first + set of values are those suggested (without explanation) for ethernet + in the Intel 82586 databook. The rest appear to be completely undocumented, + except for cryptic notes in the Crynwr packet driver. This driver uses + the Crynwr values verbatim. */ + +static unsigned char i593_init[] = { + 0xAA, /* 0: 16-byte input & 80-byte output FIFO. */ + /* threshold, 96-byte FIFO, 82593 mode. */ + 0x88, /* 1: Continuous w/interrupts, 128-clock DMA.*/ + 0x2E, /* 2: 8-byte preamble, NO address insertion, */ + /* 6-byte Ethernet address, loopback off.*/ + 0x00, /* 3: Default priorities & backoff methods. */ + 0x60, /* 4: 96-bit interframe spacing. */ + 0x00, /* 5: 512-bit slot time (low-order). */ + 0xF2, /* 6: Slot time (high-order), 15 COLL retries. */ + 0x00, /* 7: Promisc-off, broadcast-on, default CRC. */ + 0x00, /* 8: Default carrier-sense, collision-detect. */ + 0x40, /* 9: 64-byte minimum frame length. */ + 0x5F, /* A: Type/length checks OFF, no CRC input, + "jabber" termination, etc. */ + 0x00, /* B: Full-duplex disabled. */ + 0x3F, /* C: Default multicast addresses & backoff. */ + 0x07, /* D: Default IFS retriggering. */ + 0x31, /* E: Internal retransmit, drop "runt" packets, + synchr. DRQ deassertion, 6 status bytes. */ + 0x22, /* F: Receive ring-buffer size (8K), + receive-stop register enable. */ +}; + +struct netidblk { + char magic[8]; /* The magic number (string) "NETIDBLK" */ + unsigned char netid[8]; /* The physical station address */ + char nettype, globalopt; + char vendor[8]; /* The machine vendor and product name. */ + char product[8]; + char irq1, irq2; /* Interrupts, only one is currently used. */ + char dma1, dma2; + short dma_mem_misc[8]; /* DMA buffer locations (unused in Linux). */ + short iobase1, iosize1; + short iobase2, iosize2; /* Second iobase unused. */ + char driver_options; /* Misc. bits */ + char pad; +}; + +int znet_probe(struct device *dev); +static int znet_open(struct device *dev); +static int znet_send_packet(struct sk_buff *skb, struct device *dev); +static void znet_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void znet_rx(struct device *dev); +static int znet_close(struct device *dev); +static struct enet_statistics *net_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev); +static void hardware_init(struct device *dev); +static void update_stop_hit(short ioaddr, unsigned short rx_stop_offset); + +#ifdef notdef +static struct sigaction znet_sigaction = { &znet_interrupt, 0, 0, NULL, }; +#endif + + +/* The Z-Note probe is pretty easy. The NETIDBLK exists in the safe-to-probe + BIOS area. We just scan for the signature, and pull the vital parameters + out of the structure. */ + +int znet_probe(struct device *dev) +{ + int i; + struct netidblk *netinfo; + char *p; + + /* This code scans the region 0xf0000 to 0xfffff for a "NETIDBLK". */ + for(p = (char *)0xf0000; p < (char *)0x100000; p++) + if (*p == 'N' && strncmp(p, "NETIDBLK", 8) == 0) + break; + + if (p >= (char *)0x100000) { + if (znet_debug > 1) + printk(KERN_INFO "No Z-Note ethernet adaptor found.\n"); + return ENODEV; + } + netinfo = (struct netidblk *)p; + dev->base_addr = netinfo->iobase1; + dev->irq = netinfo->irq1; + + printk(KERN_INFO "%s: ZNET at %#3lx,", dev->name, dev->base_addr); + + /* The station address is in the "netidblk" at 0x0f0000. */ + for (i = 0; i < 6; i++) + printk(" %2.2x", dev->dev_addr[i] = netinfo->netid[i]); + + printk(", using IRQ %d DMA %d and %d.\n", dev->irq, netinfo->dma1, + netinfo->dma2); + + if (znet_debug > 1) { + printk(KERN_INFO "%s: vendor '%16.16s' IRQ1 %d IRQ2 %d DMA1 %d DMA2 %d.\n", + dev->name, netinfo->vendor, + netinfo->irq1, netinfo->irq2, + netinfo->dma1, netinfo->dma2); + printk(KERN_INFO "%s: iobase1 %#x size %d iobase2 %#x size %d net type %2.2x.\n", + dev->name, netinfo->iobase1, netinfo->iosize1, + netinfo->iobase2, netinfo->iosize2, netinfo->nettype); + } + + if (znet_debug > 0) + printk("%s%s", KERN_INFO, version); + + dev->priv = (void *) &zn; + zn.rx_dma = netinfo->dma1; + zn.tx_dma = netinfo->dma2; + + /* These should never fail. You can't add devices to a sealed box! */ + if (request_irq(dev->irq, &znet_interrupt, 0, "ZNet", NULL) + || request_dma(zn.rx_dma,"ZNet rx") + || request_dma(zn.tx_dma,"ZNet tx")) { + printk(KERN_WARNING "%s: Not opened -- resource busy?!?\n", dev->name); + return EBUSY; + } + irq2dev_map[dev->irq] = dev; + + /* Allocate buffer memory. We can cross a 128K boundary, so we + must be careful about the allocation. It's easiest to waste 8K. */ + if (dma_page_eq(dma_buffer1, &dma_buffer1[RX_BUF_SIZE/2-1])) + zn.rx_start = dma_buffer1; + else + zn.rx_start = dma_buffer2; + + if (dma_page_eq(dma_buffer3, &dma_buffer3[RX_BUF_SIZE/2-1])) + zn.tx_start = dma_buffer3; + else + zn.tx_start = dma_buffer2; + zn.rx_end = zn.rx_start + RX_BUF_SIZE/2; + zn.tx_buf_len = TX_BUF_SIZE/2; + zn.tx_end = zn.tx_start + zn.tx_buf_len; + + /* The ZNET-specific entries in the device structure. */ + dev->open = &znet_open; + dev->hard_start_xmit = &znet_send_packet; + dev->stop = &znet_close; + dev->get_stats = net_get_stats; + dev->set_multicast_list = &set_multicast_list; + + /* Fill in the 'dev' with ethernet-generic values. */ + ether_setup(dev); + + return 0; +} + + +static int znet_open(struct device *dev) +{ + int ioaddr = dev->base_addr; + + if (znet_debug > 2) + printk(KERN_DEBUG "%s: znet_open() called.\n", dev->name); + + /* Turn on the 82501 SIA, using zenith-specific magic. */ + outb(0x10, 0xe6); /* Select LAN control register */ + outb(inb(0xe7) | 0x84, 0xe7); /* Turn on LAN power (bit 2). */ + /* According to the Crynwr driver we should wait 50 msec. for the + LAN clock to stabilize. My experiments indicates that the '593 can + be initialized immediately. The delay is probably needed for the + DC-to-DC converter to come up to full voltage, and for the oscillator + to be spot-on at 20Mhz before transmitting. + Until this proves to be a problem we rely on the higher layers for the + delay and save allocating a timer entry. */ + + /* This follows the packet driver's lead, and checks for success. */ + if (inb(ioaddr) != 0x10 && inb(ioaddr) != 0x00) + printk(KERN_WARNING "%s: Problem turning on the transceiver power.\n", + dev->name); + + dev->tbusy = 0; + dev->interrupt = 0; + hardware_init(dev); + dev->start = 1; + + return 0; +} + +static int znet_send_packet(struct sk_buff *skb, struct device *dev) +{ + int ioaddr = dev->base_addr; + + if (znet_debug > 4) + printk(KERN_DEBUG "%s: ZNet_send_packet(%ld).\n", dev->name, dev->tbusy); + + /* Transmitter timeout, likely just recovery after suspending the machine. */ + if (dev->tbusy) { + ushort event, tx_status, rx_offset, state; + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 10) + return 1; + outb(CMD0_STAT0, ioaddr); event = inb(ioaddr); + outb(CMD0_STAT1, ioaddr); tx_status = inw(ioaddr); + outb(CMD0_STAT2, ioaddr); rx_offset = inw(ioaddr); + outb(CMD0_STAT3, ioaddr); state = inb(ioaddr); + printk(KERN_WARNING "%s: transmit timed out, status %02x %04x %04x %02x," + " resetting.\n", dev->name, event, tx_status, rx_offset, state); + if (tx_status == 0x0400) + printk(KERN_WARNING "%s: Tx carrier error, check transceiver cable.\n", + dev->name); + outb(CMD0_RESET, ioaddr); + hardware_init(dev); + } + + if (skb == NULL) { + dev_tint(dev); + return 0; + } + + /* Check that the part hasn't reset itself, probably from suspend. */ + outb(CMD0_STAT0, ioaddr); + if (inw(ioaddr) == 0x0010 + && inw(ioaddr) == 0x0000 + && inw(ioaddr) == 0x0010) + hardware_init(dev); + + /* Block a timer-based transmit from overlapping. This could better be + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (set_bit(0, (void*)&dev->tbusy) != 0) + printk(KERN_WARNING "%s: Transmitter access conflict.\n", dev->name); + else { + short length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + unsigned char *buf = (void *)skb->data; + ushort *tx_link = zn.tx_cur - 1; + ushort rnd_len = (length + 1)>>1; + + { + short dma_port = ((zn.tx_dma&3)<<2) + IO_DMA2_BASE; + unsigned addr = inb(dma_port); + addr |= inb(dma_port) << 8; + addr <<= 1; + if (((int)zn.tx_cur & 0x1ffff) != addr) + printk(KERN_WARNING "Address mismatch at Tx: %#x vs %#x.\n", + (int)zn.tx_cur & 0xffff, addr); + zn.tx_cur = (ushort *)(((int)zn.tx_cur & 0xfe0000) | addr); + } + + if (zn.tx_cur >= zn.tx_end) + zn.tx_cur = zn.tx_start; + *zn.tx_cur++ = length; + if (zn.tx_cur + rnd_len + 1 > zn.tx_end) { + int semi_cnt = (zn.tx_end - zn.tx_cur)<<1; /* Cvrt to byte cnt. */ + memcpy(zn.tx_cur, buf, semi_cnt); + rnd_len -= semi_cnt>>1; + memcpy(zn.tx_start, buf + semi_cnt, length - semi_cnt); + zn.tx_cur = zn.tx_start + rnd_len; + } else { + memcpy(zn.tx_cur, buf, skb->len); + zn.tx_cur += rnd_len; + } + *zn.tx_cur++ = 0; + cli(); { + *tx_link = CMD0_TRANSMIT + CMD0_CHNL_1; + /* Is this always safe to do? */ + outb(CMD0_TRANSMIT + CMD0_CHNL_1,ioaddr); + } sti(); + + dev->trans_start = jiffies; + if (znet_debug > 4) + printk(KERN_DEBUG "%s: Transmitter queued, length %d.\n", dev->name, length); + } + dev_kfree_skb(skb, FREE_WRITE); + return 0; +} + +/* The ZNET interrupt handler. */ +static void znet_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct device *dev = irq2dev_map[irq]; + int ioaddr; + int boguscnt = 20; + + if (dev == NULL) { + printk(KERN_WARNING "znet_interrupt(): IRQ %d for unknown device.\n", irq); + return; + } + + dev->interrupt = 1; + ioaddr = dev->base_addr; + + outb(CMD0_STAT0, ioaddr); + do { + ushort status = inb(ioaddr); + if (znet_debug > 5) { + ushort result, rx_ptr, running; + outb(CMD0_STAT1, ioaddr); + result = inw(ioaddr); + outb(CMD0_STAT2, ioaddr); + rx_ptr = inw(ioaddr); + outb(CMD0_STAT3, ioaddr); + running = inb(ioaddr); + printk(KERN_DEBUG "%s: interrupt, status %02x, %04x %04x %02x serial %d.\n", + dev->name, status, result, rx_ptr, running, boguscnt); + } + if ((status & 0x80) == 0) + break; + + if ((status & 0x0F) == 4) { /* Transmit done. */ + struct net_local *lp = (struct net_local *)dev->priv; + int tx_status; + outb(CMD0_STAT1, ioaddr); + tx_status = inw(ioaddr); + /* It's undocumented, but tx_status seems to match the i82586. */ + if (tx_status & 0x2000) { + lp->stats.tx_packets++; + lp->stats.collisions += tx_status & 0xf; + } else { + 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++; + /* ...and the catch-all. */ + if ((tx_status | 0x0760) != 0x0760) + lp->stats.tx_errors++; + } + dev->tbusy = 0; + mark_bh(NET_BH); /* Inform upper layers. */ + } + + if ((status & 0x40) + || (status & 0x0f) == 11) { + znet_rx(dev); + } + /* Clear the interrupts we've handled. */ + outb(CMD0_ACK,ioaddr); + } while (boguscnt--); + + dev->interrupt = 0; + return; +} + +static void znet_rx(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + int ioaddr = dev->base_addr; + int boguscount = 1; + short next_frame_end_offset = 0; /* Offset of next frame start. */ + short *cur_frame_end; + short cur_frame_end_offset; + + outb(CMD0_STAT2, ioaddr); + cur_frame_end_offset = inw(ioaddr); + + if (cur_frame_end_offset == zn.rx_cur - zn.rx_start) { + printk(KERN_WARNING "%s: Interrupted, but nothing to receive, offset %03x.\n", + dev->name, cur_frame_end_offset); + return; + } + + /* Use same method as the Crynwr driver: construct a forward list in + the same area of the backwards links we now have. This allows us to + pass packets to the upper layers in the order they were received -- + important for fast-path sequential operations. */ + while (zn.rx_start + cur_frame_end_offset != zn.rx_cur + && ++boguscount < 5) { + unsigned short hi_cnt, lo_cnt, hi_status, lo_status; + int count, status; + + if (cur_frame_end_offset < 4) { + /* Oh no, we have a special case: the frame trailer wraps around + the end of the ring buffer. We've saved space at the end of + the ring buffer for just this problem. */ + memcpy(zn.rx_end, zn.rx_start, 8); + cur_frame_end_offset += (RX_BUF_SIZE/2); + } + cur_frame_end = zn.rx_start + cur_frame_end_offset - 4; + + lo_status = *cur_frame_end++; + hi_status = *cur_frame_end++; + status = ((hi_status & 0xff) << 8) + (lo_status & 0xff); + lo_cnt = *cur_frame_end++; + hi_cnt = *cur_frame_end++; + count = ((hi_cnt & 0xff) << 8) + (lo_cnt & 0xff); + + if (znet_debug > 5) + printk(KERN_DEBUG "Constructing trailer at location %03x, %04x %04x %04x %04x" + " count %#x status %04x.\n", + cur_frame_end_offset<<1, lo_status, hi_status, lo_cnt, hi_cnt, + count, status); + cur_frame_end[-4] = status; + cur_frame_end[-3] = next_frame_end_offset; + cur_frame_end[-2] = count; + next_frame_end_offset = cur_frame_end_offset; + cur_frame_end_offset -= ((count + 1)>>1) + 3; + if (cur_frame_end_offset < 0) + cur_frame_end_offset += RX_BUF_SIZE/2; + }; + + /* Now step forward through the list. */ + do { + ushort *this_rfp_ptr = zn.rx_start + next_frame_end_offset; + int status = this_rfp_ptr[-4]; + int pkt_len = this_rfp_ptr[-2]; + + if (znet_debug > 5) + printk(KERN_DEBUG "Looking at trailer ending at %04x status %04x length %03x" + " next %04x.\n", next_frame_end_offset<<1, status, pkt_len, + this_rfp_ptr[-3]<<1); + /* Once again we must assume that the i82586 docs apply. */ + if ( ! (status & 0x2000)) { /* There was an error. */ + lp->stats.rx_errors++; + if (status & 0x0800) lp->stats.rx_crc_errors++; + if (status & 0x0400) lp->stats.rx_frame_errors++; + if (status & 0x0200) lp->stats.rx_over_errors++; /* Wrong. */ + if (status & 0x0100) lp->stats.rx_fifo_errors++; + if (status & 0x0080) lp->stats.rx_length_errors++; + } else if (pkt_len > 1536) { + lp->stats.rx_length_errors++; + } else { + /* Malloc up new buffer. */ + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len); + if (skb == NULL) { + if (znet_debug) + printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name); + lp->stats.rx_dropped++; + break; + } + skb->dev = dev; + + if (&zn.rx_cur[(pkt_len+1)>>1] > zn.rx_end) { + int semi_cnt = (zn.rx_end - zn.rx_cur)<<1; + memcpy(skb_put(skb,semi_cnt), zn.rx_cur, semi_cnt); + memcpy(skb_put(skb,pkt_len-semi_cnt), zn.rx_start, + pkt_len - semi_cnt); + } else { + memcpy(skb_put(skb,pkt_len), zn.rx_cur, pkt_len); + if (znet_debug > 6) { + unsigned int *packet = (unsigned int *) skb->data; + printk(KERN_DEBUG "Packet data is %08x %08x %08x %08x.\n", packet[0], + packet[1], packet[2], packet[3]); + } + } + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + lp->stats.rx_packets++; + } + zn.rx_cur = this_rfp_ptr; + if (zn.rx_cur >= zn.rx_end) + zn.rx_cur -= RX_BUF_SIZE/2; + update_stop_hit(ioaddr, (zn.rx_cur - zn.rx_start)<<1); + next_frame_end_offset = this_rfp_ptr[-3]; + if (next_frame_end_offset == 0) /* Read all the frames? */ + break; /* Done for now */ + this_rfp_ptr = zn.rx_start + next_frame_end_offset; + } while (--boguscount); + + /* If any worth-while packets have been received, dev_rint() + has done a mark_bh(INET_BH) for us and will work on them + when we get to the bottom-half routine. */ + return; +} + +/* The inverse routine to znet_open(). */ +static int znet_close(struct device *dev) +{ + int ioaddr = dev->base_addr; + + dev->tbusy = 1; + dev->start = 0; + + outb(CMD0_RESET, ioaddr); /* CMD0_RESET */ + + disable_dma(zn.rx_dma); + disable_dma(zn.tx_dma); + + free_irq(dev->irq, NULL); + + if (znet_debug > 1) + printk(KERN_DEBUG "%s: Shutting down ethercard.\n", dev->name); + /* Turn off transceiver power. */ + outb(0x10, 0xe6); /* Select LAN control register */ + outb(inb(0xe7) & ~0x84, 0xe7); /* Turn on LAN power (bit 2). */ + + return 0; +} + +/* Get the current statistics. This may be called with the card open or + closed. */ +static struct enet_statistics *net_get_stats(struct device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + + return &lp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + As a side effect this routine must also initialize the device parameters. + This is taken advantage of in open(). + + N.B. that we change i593_init[] in place. This (properly) makes the + mode change persistent, but must be changed if this code is moved to + a multiple adaptor environment. + */ +static void set_multicast_list(struct device *dev) +{ + short ioaddr = dev->base_addr; + + if (dev->flags&IFF_PROMISC) { + /* Enable promiscuous mode */ + i593_init[7] &= ~3; i593_init[7] |= 1; + i593_init[13] &= ~8; i593_init[13] |= 8; + } else if (dev->mc_list || (dev->flags&IFF_ALLMULTI)) { + /* Enable accept-all-multicast mode */ + i593_init[7] &= ~3; i593_init[7] |= 0; + i593_init[13] &= ~8; i593_init[13] |= 8; + } else { /* Enable normal mode. */ + i593_init[7] &= ~3; i593_init[7] |= 0; + i593_init[13] &= ~8; i593_init[13] |= 0; + } + *zn.tx_cur++ = sizeof(i593_init); + memcpy(zn.tx_cur, i593_init, sizeof(i593_init)); + zn.tx_cur += sizeof(i593_init)/2; + outb(CMD0_CONFIGURE+CMD0_CHNL_1, ioaddr); +#ifdef not_tested + if (num_addrs > 0) { + int addrs_len = 6*num_addrs; + *zn.tx_cur++ = addrs_len; + memcpy(zn.tx_cur, addrs, addrs_len); + outb(CMD0_MULTICAST_LIST+CMD0_CHNL_1, ioaddr); + zn.tx_cur += addrs_len>>1; + } +#endif +} + +void show_dma(void) +{ + short dma_port = ((zn.tx_dma&3)<<2) + IO_DMA2_BASE; + unsigned addr = inb(dma_port); + addr |= inb(dma_port) << 8; + printk("Addr: %04x cnt:%3x...", addr<<1, get_dma_residue(zn.tx_dma)); +} + +/* Initialize the hardware. We have to do this when the board is open()ed + or when we come out of suspend mode. */ +static void hardware_init(struct device *dev) +{ + short ioaddr = dev->base_addr; + + zn.rx_cur = zn.rx_start; + zn.tx_cur = zn.tx_start; + + /* Reset the chip, and start it up. */ + outb(CMD0_RESET, ioaddr); + + cli(); { /* Protect against a DMA flip-flop */ + disable_dma(zn.rx_dma); /* reset by an interrupting task. */ + clear_dma_ff(zn.rx_dma); + set_dma_mode(zn.rx_dma, DMA_RX_MODE); + set_dma_addr(zn.rx_dma, (unsigned int) zn.rx_start); + set_dma_count(zn.rx_dma, RX_BUF_SIZE); + enable_dma(zn.rx_dma); + /* Now set up the Tx channel. */ + disable_dma(zn.tx_dma); + clear_dma_ff(zn.tx_dma); + set_dma_mode(zn.tx_dma, DMA_TX_MODE); + set_dma_addr(zn.tx_dma, (unsigned int) zn.tx_start); + set_dma_count(zn.tx_dma, zn.tx_buf_len<<1); + enable_dma(zn.tx_dma); + } sti(); + + if (znet_debug > 1) + printk(KERN_DEBUG "%s: Initializing the i82593, tx buf %p... ", dev->name, + zn.tx_start); + /* Do an empty configure command, just like the Crynwr driver. This + resets to chip to its default values. */ + *zn.tx_cur++ = 0; + *zn.tx_cur++ = 0; + printk("stat:%02x ", inb(ioaddr)); show_dma(); + outb(CMD0_CONFIGURE+CMD0_CHNL_1, ioaddr); + *zn.tx_cur++ = sizeof(i593_init); + memcpy(zn.tx_cur, i593_init, sizeof(i593_init)); + zn.tx_cur += sizeof(i593_init)/2; + printk("stat:%02x ", inb(ioaddr)); show_dma(); + outb(CMD0_CONFIGURE+CMD0_CHNL_1, ioaddr); + *zn.tx_cur++ = 6; + memcpy(zn.tx_cur, dev->dev_addr, 6); + zn.tx_cur += 3; + printk("stat:%02x ", inb(ioaddr)); show_dma(); + outb(CMD0_IA_SETUP + CMD0_CHNL_1, ioaddr); + printk("stat:%02x ", inb(ioaddr)); show_dma(); + + update_stop_hit(ioaddr, 8192); + if (znet_debug > 1) printk("enabling Rx.\n"); + outb(CMD0_Rx_ENABLE+CMD0_CHNL_0, ioaddr); + dev->tbusy = 0; +} + +static void update_stop_hit(short ioaddr, unsigned short rx_stop_offset) +{ + outb(CMD0_PORT_1, ioaddr); + if (znet_debug > 5) + printk(KERN_DEBUG "Updating stop hit with value %02x.\n", + (rx_stop_offset >> 6) | 0x80); + outb((rx_stop_offset >> 6) | 0x80, ioaddr); + outb(CMD1_PORT_0, ioaddr); +} + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c znet.c" + * version-control: t + * kept-new-versions: 5 + * c-indent-level: 4 + * tab-width: 4 + * End: + */ |