summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/3com/3c501.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/3com/3c501.c')
-rw-r--r--drivers/net/ethernet/3com/3c501.c896
1 files changed, 896 insertions, 0 deletions
diff --git a/drivers/net/ethernet/3com/3c501.c b/drivers/net/ethernet/3com/3c501.c
new file mode 100644
index 00000000000..68da81d476f
--- /dev/null
+++ b/drivers/net/ethernet/3com/3c501.c
@@ -0,0 +1,896 @@
+/* 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 General 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 original author may be reached as becker@scyld.com, or C/O
+ Scyld Computing Corporation
+ 410 Severn Ave., Suite 210
+ Annapolis MD 21403
+
+ Fixed (again!) the missing interrupt locking on TX/RX shifting.
+ Alan Cox <alan@lxorguk.ukuu.org.uk>
+
+ 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@lxorguk.ukuu.org.uk>
+
+ Cleaned up for 2.3.x because we broke SMP now.
+ 20000208 Alan Cox <alan@lxorguk.ukuu.org.uk>
+
+ Check up pass for 2.5. Nothing significant changed
+ 20021009 Alan Cox <alan@lxorguk.ukuu.org.uk>
+
+ Fixed zero fill corner case
+ 20030104 Alan Cox <alan@lxorguk.ukuu.org.uk>
+
+
+ For the avoidance of doubt the "preferred form" of this code is one which
+ is in an open non patent encumbered format. Where cryptographic key signing
+ forms part of the process of creating an executable the information
+ including keys needed to generate an equivalently functional executable
+ are deemed to be part of the source code.
+
+*/
+
+
+/**
+ * DOC: 3c501 Card Notes
+ *
+ * Some notes on this thing if you have to hack it. [Alan]
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * When transmitting lp->txing 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).
+ *
+ * DOC: 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.
+ *
+ */
+
+#define DRV_NAME "3c501"
+#define DRV_VERSION "2002/10/09"
+
+
+static const char version[] =
+ DRV_NAME ".c: " DRV_VERSION " Alan Cox (alan@lxorguk.ukuu.org.uk).\n";
+
+/*
+ * Braindamage remaining:
+ * The 3c501 board.
+ */
+
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/fcntl.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/ethtool.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+
+#include "3c501.h"
+
+/*
+ * The boilerplate probe code.
+ */
+
+static int io = 0x280;
+static int irq = 5;
+static int mem_start;
+
+/**
+ * el1_probe: - probe for a 3c501
+ * @dev: The device structure passed in to probe.
+ *
+ * This can be called from two places. The network layer will probe using
+ * a device structure passed in with the probe information completed. For a
+ * modular driver we use #init_module to fill in our own structure and probe
+ * for it.
+ *
+ * Returns 0 on success. ENXIO if asked not to probe and ENODEV if asked to
+ * probe and failing to find anything.
+ */
+
+struct net_device * __init el1_probe(int unit)
+{
+ struct net_device *dev = alloc_etherdev(sizeof(struct net_local));
+ static const unsigned ports[] = { 0x280, 0x300, 0};
+ const unsigned *port;
+ int err = 0;
+
+ if (!dev)
+ return ERR_PTR(-ENOMEM);
+
+ if (unit >= 0) {
+ sprintf(dev->name, "eth%d", unit);
+ netdev_boot_setup_check(dev);
+ io = dev->base_addr;
+ irq = dev->irq;
+ mem_start = dev->mem_start & 7;
+ }
+
+ if (io > 0x1ff) { /* Check a single specified location. */
+ err = el1_probe1(dev, io);
+ } else if (io != 0) {
+ err = -ENXIO; /* Don't probe at all. */
+ } else {
+ for (port = ports; *port && el1_probe1(dev, *port); port++)
+ ;
+ if (!*port)
+ err = -ENODEV;
+ }
+ if (err)
+ goto out;
+ err = register_netdev(dev);
+ if (err)
+ goto out1;
+ return dev;
+out1:
+ release_region(dev->base_addr, EL1_IO_EXTENT);
+out:
+ free_netdev(dev);
+ return ERR_PTR(err);
+}
+
+static const struct net_device_ops el_netdev_ops = {
+ .ndo_open = el_open,
+ .ndo_stop = el1_close,
+ .ndo_start_xmit = el_start_xmit,
+ .ndo_tx_timeout = el_timeout,
+ .ndo_set_rx_mode = set_multicast_list,
+ .ndo_change_mtu = eth_change_mtu,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
+/**
+ * el1_probe1:
+ * @dev: The device structure to use
+ * @ioaddr: An I/O address to probe at.
+ *
+ * The actual probe. This is iterated over by #el1_probe in order to
+ * check all the applicable device locations.
+ *
+ * Returns 0 for a success, in which case the device is activated,
+ * EAGAIN if the IRQ is in use by another driver, and ENODEV if the
+ * board cannot be found.
+ */
+
+static int __init el1_probe1(struct net_device *dev, int ioaddr)
+{
+ struct net_local *lp;
+ const char *mname; /* Vendor name */
+ unsigned char station_addr[6];
+ int autoirq = 0;
+ int i;
+
+ /*
+ * Reserve I/O resource for exclusive use by this driver
+ */
+
+ if (!request_region(ioaddr, EL1_IO_EXTENT, DRV_NAME))
+ return -ENODEV;
+
+ /*
+ * 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 {
+ release_region(ioaddr, EL1_IO_EXTENT);
+ return -ENODEV;
+ }
+
+ /*
+ * We auto-IRQ by shutting off the interrupt line and letting it
+ * float high.
+ */
+
+ dev->irq = irq;
+
+ if (dev->irq < 2) {
+ unsigned long irq_mask;
+
+ irq_mask = probe_irq_on();
+ inb(RX_STATUS); /* Clear pending interrupts. */
+ inb(TX_STATUS);
+ outb(AX_LOOP + 1, AX_CMD);
+
+ outb(0x00, AX_CMD);
+
+ mdelay(20);
+ autoirq = probe_irq_off(irq_mask);
+
+ if (autoirq == 0) {
+ pr_warning("%s probe at %#x failed to detect IRQ line.\n",
+ mname, ioaddr);
+ release_region(ioaddr, EL1_IO_EXTENT);
+ return -EAGAIN;
+ }
+ }
+
+ outb(AX_RESET+AX_LOOP, AX_CMD); /* Loopback mode. */
+ dev->base_addr = ioaddr;
+ memcpy(dev->dev_addr, station_addr, ETH_ALEN);
+
+ if (mem_start & 0xf)
+ el_debug = mem_start & 0x7;
+ if (autoirq)
+ dev->irq = autoirq;
+
+ pr_info("%s: %s EtherLink at %#lx, using %sIRQ %d.\n",
+ dev->name, mname, dev->base_addr,
+ autoirq ? "auto":"assigned ", dev->irq);
+
+#ifdef CONFIG_IP_MULTICAST
+ pr_warning("WARNING: Use of the 3c501 in a multicast kernel is NOT recommended.\n");
+#endif
+
+ if (el_debug)
+ pr_debug("%s", version);
+
+ lp = netdev_priv(dev);
+ memset(lp, 0, sizeof(struct net_local));
+ spin_lock_init(&lp->lock);
+
+ /*
+ * The EL1-specific entries in the device structure.
+ */
+
+ dev->netdev_ops = &el_netdev_ops;
+ dev->watchdog_timeo = HZ;
+ dev->ethtool_ops = &netdev_ethtool_ops;
+ return 0;
+}
+
+/**
+ * el1_open:
+ * @dev: device that is being opened
+ *
+ * When an ifconfig is issued which changes the device flags to include
+ * IFF_UP this function is called. It is only called when the change
+ * occurs, not when the interface remains up. #el1_close will be called
+ * when it goes down.
+ *
+ * Returns 0 for a successful open, or -EAGAIN if someone has run off
+ * with our interrupt line.
+ */
+
+static int el_open(struct net_device *dev)
+{
+ int retval;
+ int ioaddr = dev->base_addr;
+ struct net_local *lp = netdev_priv(dev);
+ unsigned long flags;
+
+ if (el_debug > 2)
+ pr_debug("%s: Doing el_open()...\n", dev->name);
+
+ retval = request_irq(dev->irq, el_interrupt, 0, dev->name, dev);
+ if (retval)
+ return retval;
+
+ spin_lock_irqsave(&lp->lock, flags);
+ el_reset(dev);
+ spin_unlock_irqrestore(&lp->lock, flags);
+
+ lp->txing = 0; /* Board in RX mode */
+ outb(AX_RX, AX_CMD); /* Aux control, irq and receive enabled */
+ netif_start_queue(dev);
+ return 0;
+}
+
+/**
+ * el_timeout:
+ * @dev: The 3c501 card that has timed out
+ *
+ * Attempt to restart the board. This is basically a mixture of extreme
+ * violence and prayer
+ *
+ */
+
+static void el_timeout(struct net_device *dev)
+{
+ struct net_local *lp = netdev_priv(dev);
+ int ioaddr = dev->base_addr;
+
+ if (el_debug)
+ pr_debug("%s: transmit timed out, txsr %#2x axsr=%02x rxsr=%02x.\n",
+ dev->name, inb(TX_STATUS),
+ inb(AX_STATUS), inb(RX_STATUS));
+ dev->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 */
+ lp->txing = 0; /* Ripped back in to RX */
+ netif_wake_queue(dev);
+}
+
+
+/**
+ * el_start_xmit:
+ * @skb: The packet that is queued to be sent
+ * @dev: The 3c501 card we want to throw it down
+ *
+ * Attempt to send a packet to a 3c501 card. There are some interesting
+ * catches here because the 3c501 is an extremely old and therefore
+ * stupid piece of technology.
+ *
+ * If we are handling an interrupt on the other CPU we cannot load a packet
+ * as we may still be attempting to retrieve the last RX packet buffer.
+ *
+ * When a transmit times out we dump the card into control mode and just
+ * start again. It happens enough that it isn't worth logging.
+ *
+ * We avoid holding the spin locks when doing the packet load to the board.
+ * The device is very slow, and its DMA mode is even slower. If we held the
+ * lock while loading 1500 bytes onto the controller we would drop a lot of
+ * serial port characters. This requires we do extra locking, but we have
+ * no real choice.
+ */
+
+static netdev_tx_t el_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_local *lp = netdev_priv(dev);
+ int ioaddr = dev->base_addr;
+ unsigned long flags;
+
+ /*
+ * Avoid incoming interrupts between us flipping txing and flipping
+ * mode as the driver assumes txing is a faithful indicator of card
+ * state
+ */
+
+ spin_lock_irqsave(&lp->lock, flags);
+
+ /*
+ * Avoid timer-based retransmission conflicts.
+ */
+
+ netif_stop_queue(dev);
+
+ do {
+ int len = skb->len;
+ int pad = 0;
+ int gp_start;
+ unsigned char *buf = skb->data;
+
+ if (len < ETH_ZLEN)
+ pad = ETH_ZLEN - len;
+
+ gp_start = 0x800 - (len + pad);
+
+ lp->tx_pkt_start = gp_start;
+ lp->collisions = 0;
+
+ dev->stats.tx_bytes += skb->len;
+
+ /*
+ * Command mode with status cleared should [in theory]
+ * mean no more interrupts can be pending on the card.
+ */
+
+ outb_p(AX_SYS, AX_CMD);
+ inb_p(RX_STATUS);
+ inb_p(TX_STATUS);
+
+ lp->loading = 1;
+ lp->txing = 1;
+
+ /*
+ * Turn interrupts back on while we spend a pleasant
+ * afternoon loading bytes into the board
+ */
+
+ spin_unlock_irqrestore(&lp->lock, flags);
+
+ /* Set rx packet area to 0. */
+ outw(0x00, RX_BUF_CLR);
+ /* aim - packet will be loaded into buffer start */
+ outw(gp_start, GP_LOW);
+ /* load buffer (usual thing each byte increments the pointer) */
+ outsb(DATAPORT, buf, len);
+ if (pad) {
+ while (pad--) /* Zero fill buffer tail */
+ outb(0, DATAPORT);
+ }
+ /* the board reuses the same register */
+ outw(gp_start, GP_LOW);
+
+ if (lp->loading != 2) {
+ /* fire ... Trigger xmit. */
+ outb(AX_XMIT, AX_CMD);
+ lp->loading = 0;
+ if (el_debug > 2)
+ pr_debug(" queued xmit.\n");
+ dev_kfree_skb(skb);
+ return NETDEV_TX_OK;
+ }
+ /* A receive upset our load, despite our best efforts */
+ if (el_debug > 2)
+ pr_debug("%s: burped during tx load.\n", dev->name);
+ spin_lock_irqsave(&lp->lock, flags);
+ } while (1);
+}
+
+/**
+ * el_interrupt:
+ * @irq: Interrupt number
+ * @dev_id: The 3c501 that burped
+ *
+ * Handle the ether interface interrupts. The 3c501 needs a lot more
+ * hand holding than most cards. In particular we get a transmit interrupt
+ * with a collision error because the board firmware isn't capable of rewinding
+ * its own transmit buffer pointers. It can however count to 16 for us.
+ *
+ * On the receive side the card is also very dumb. It has no buffering to
+ * speak of. We simply pull the packet out of its PIO buffer (which is slow)
+ * and queue it for the kernel. Then we reset the card for the next packet.
+ *
+ * We sometimes get surprise interrupts late both because the SMP IRQ delivery
+ * is message passing and because the card sometimes seems to deliver late. I
+ * think if it is part way through a receive and the mode is changed it carries
+ * on receiving and sends us an interrupt. We have to band aid all these cases
+ * to get a sensible 150kBytes/second performance. Even then you want a small
+ * TCP window.
+ */
+
+static irqreturn_t el_interrupt(int irq, void *dev_id)
+{
+ struct net_device *dev = dev_id;
+ struct net_local *lp;
+ int ioaddr;
+ int axsr; /* Aux. status reg. */
+
+ ioaddr = dev->base_addr;
+ lp = netdev_priv(dev);
+
+ spin_lock(&lp->lock);
+
+ /*
+ * What happened ?
+ */
+
+ axsr = inb(AX_STATUS);
+
+ /*
+ * Log it
+ */
+
+ if (el_debug > 3)
+ pr_debug("%s: el_interrupt() aux=%#02x\n", dev->name, axsr);
+
+ if (lp->loading == 1 && !lp->txing)
+ pr_warning("%s: Inconsistent state loading while not in tx\n",
+ dev->name);
+
+ if (lp->txing) {
+ /*
+ * Board in transmit mode. May be loading. If we are
+ * loading we shouldn't have got this.
+ */
+ int txsr = inb(TX_STATUS);
+
+ if (lp->loading == 1) {
+ if (el_debug > 2)
+ pr_debug("%s: Interrupt while loading [txsr=%02x gp=%04x rp=%04x]\n",
+ dev->name, txsr, inw(GP_LOW), inw(RX_LOW));
+
+ /* Force a reload */
+ lp->loading = 2;
+ spin_unlock(&lp->lock);
+ goto out;
+ }
+ if (el_debug > 6)
+ pr_debug("%s: txsr=%02x gp=%04x rp=%04x\n", dev->name,
+ 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)
+ pr_debug("%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));
+ lp->txing = 0;
+ netif_wake_queue(dev);
+ } else if (txsr & TX_16COLLISIONS) {
+ /*
+ * Timed out
+ */
+ if (el_debug)
+ pr_debug("%s: Transmit failed 16 times, Ethernet jammed?\n", dev->name);
+ outb(AX_SYS, AX_CMD);
+ lp->txing = 0;
+ dev->stats.tx_aborted_errors++;
+ netif_wake_queue(dev);
+ } else if (txsr & TX_COLLISION) {
+ /*
+ * Retrigger xmit.
+ */
+
+ if (el_debug > 6)
+ pr_debug("%s: retransmitting after a collision.\n", dev->name);
+ /*
+ * 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);
+ dev->stats.collisions++;
+ spin_unlock(&lp->lock);
+ goto out;
+ } else {
+ /*
+ * It worked.. we will now fall through and receive
+ */
+ dev->stats.tx_packets++;
+ if (el_debug > 6)
+ pr_debug("%s: Tx succeeded %s\n", dev->name,
+ (txsr & TX_RDY) ? "." : "but tx is busy!");
+ /*
+ * This is safe the interrupt is atomic WRT itself.
+ */
+ lp->txing = 0;
+ /* In case more to transmit */
+ netif_wake_queue(dev);
+ }
+ } else {
+ /*
+ * In receive mode.
+ */
+
+ int rxsr = inb(RX_STATUS);
+ if (el_debug > 5)
+ pr_debug("%s: rxsr=%02x txsr=%02x rp=%04x\n",
+ dev->name, rxsr, inb(TX_STATUS), inw(RX_LOW));
+ /*
+ * Just reading rx_status fixes most errors.
+ */
+ if (rxsr & RX_MISSED)
+ dev->stats.rx_missed_errors++;
+ else if (rxsr & RX_RUNT) {
+ /* Handled to avoid board lock-up. */
+ dev->stats.rx_length_errors++;
+ if (el_debug > 5)
+ pr_debug("%s: runt.\n", dev->name);
+ } else if (rxsr & RX_GOOD) {
+ /*
+ * Receive worked.
+ */
+ el_receive(dev);
+ } else {
+ /*
+ * Nothing? Something is broken!
+ */
+ if (el_debug > 2)
+ pr_debug("%s: No packet seen, rxsr=%02x **resetting 3c501***\n",
+ dev->name, rxsr);
+ el_reset(dev);
+ }
+ }
+
+ /*
+ * 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);
+ spin_unlock(&lp->lock);
+out:
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * el_receive:
+ * @dev: Device to pull the packets from
+ *
+ * We have a good packet. Well, not really "good", just mostly not broken.
+ * We must check everything to see if it is good. In particular we occasionally
+ * get wild packet sizes from the card. If the packet seems sane we PIO it
+ * off the card and queue it for the protocol layers.
+ */
+
+static void el_receive(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+ int pkt_len;
+ struct sk_buff *skb;
+
+ pkt_len = inw(RX_LOW);
+
+ if (el_debug > 4)
+ pr_debug(" el_receive %d.\n", pkt_len);
+
+ if (pkt_len < 60 || pkt_len > 1536) {
+ if (el_debug)
+ pr_debug("%s: bogus packet, length=%d\n",
+ dev->name, pkt_len);
+ dev->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) {
+ pr_info("%s: Memory squeeze, dropping packet.\n", dev->name);
+ dev->stats.rx_dropped++;
+ return;
+ } else {
+ skb_reserve(skb, 2); /* Force 16 byte alignment */
+ /*
+ * 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);
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += pkt_len;
+ }
+}
+
+/**
+ * el_reset: Reset a 3c501 card
+ * @dev: The 3c501 card about to get zapped
+ *
+ * Even resetting a 3c501 isn't simple. When you activate reset it loses all
+ * its configuration. You must hold the lock when doing this. The function
+ * cannot take the lock itself as it is callable from the irq handler.
+ */
+
+static void el_reset(struct net_device *dev)
+{
+ struct net_local *lp = netdev_priv(dev);
+ int ioaddr = dev->base_addr;
+
+ if (el_debug > 2)
+ pr_info("3c501 reset...\n");
+ outb(AX_RESET, AX_CMD); /* Reset the chip */
+ /* Aux control, irq and loopback enabled */
+ outb(AX_LOOP, AX_CMD);
+ {
+ 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. */
+ 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);
+ lp->txing = 0;
+}
+
+/**
+ * el1_close:
+ * @dev: 3c501 card to shut down
+ *
+ * Close a 3c501 card. The IFF_UP flag has been cleared by the user via
+ * the SIOCSIFFLAGS ioctl. We stop any further transmissions being queued,
+ * and then disable the interrupts. Finally we reset the chip. The effects
+ * of the rest will be cleaned up by #el1_open. Always returns 0 indicating
+ * a success.
+ */
+
+static int el1_close(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+
+ if (el_debug > 2)
+ pr_info("%s: Shutting down Ethernet card at %#x.\n",
+ dev->name, ioaddr);
+
+ netif_stop_queue(dev);
+
+ /*
+ * Free and disable the IRQ.
+ */
+
+ free_irq(dev->irq, dev);
+ outb(AX_RESET, AX_CMD); /* Reset the chip */
+
+ return 0;
+}
+
+/**
+ * set_multicast_list:
+ * @dev: The device to adjust
+ *
+ * Set or clear the multicast filter for this adaptor to use the best-effort
+ * filtering supported. The 3c501 supports only three modes of filtering.
+ * It always receives broadcasts and packets for itself. You can choose to
+ * optionally receive all packets, or all multicast packets on top of this.
+ */
+
+static void set_multicast_list(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+
+ if (dev->flags & IFF_PROMISC) {
+ outb(RX_PROM, RX_CMD);
+ inb(RX_STATUS);
+ } else if (!netdev_mc_empty(dev) || dev->flags & IFF_ALLMULTI) {
+ /* Multicast or all multicast is the same */
+ outb(RX_MULT, RX_CMD);
+ inb(RX_STATUS); /* Clear status. */
+ } else {
+ outb(RX_NORM, RX_CMD);
+ inb(RX_STATUS);
+ }
+}
+
+
+static void netdev_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ strcpy(info->driver, DRV_NAME);
+ strcpy(info->version, DRV_VERSION);
+ sprintf(info->bus_info, "ISA 0x%lx", dev->base_addr);
+}
+
+static u32 netdev_get_msglevel(struct net_device *dev)
+{
+ return debug;
+}
+
+static void netdev_set_msglevel(struct net_device *dev, u32 level)
+{
+ debug = level;
+}
+
+static const struct ethtool_ops netdev_ethtool_ops = {
+ .get_drvinfo = netdev_get_drvinfo,
+ .get_msglevel = netdev_get_msglevel,
+ .set_msglevel = netdev_set_msglevel,
+};
+
+#ifdef MODULE
+
+static struct net_device *dev_3c501;
+
+module_param(io, int, 0);
+module_param(irq, int, 0);
+MODULE_PARM_DESC(io, "EtherLink I/O base address");
+MODULE_PARM_DESC(irq, "EtherLink IRQ number");
+
+/**
+ * init_module:
+ *
+ * When the driver is loaded as a module this function is called. We fake up
+ * a device structure with the base I/O and interrupt set as if it were being
+ * called from Space.c. This minimises the extra code that would otherwise
+ * be required.
+ *
+ * Returns 0 for success or -EIO if a card is not found. Returning an error
+ * here also causes the module to be unloaded
+ */
+
+int __init init_module(void)
+{
+ dev_3c501 = el1_probe(-1);
+ if (IS_ERR(dev_3c501))
+ return PTR_ERR(dev_3c501);
+ return 0;
+}
+
+/**
+ * cleanup_module:
+ *
+ * The module is being unloaded. We unhook our network device from the system
+ * and then free up the resources we took when the card was found.
+ */
+
+void __exit cleanup_module(void)
+{
+ struct net_device *dev = dev_3c501;
+ unregister_netdev(dev);
+ release_region(dev->base_addr, EL1_IO_EXTENT);
+ free_netdev(dev);
+}
+
+#endif /* MODULE */
+
+MODULE_AUTHOR("Donald Becker, Alan Cox");
+MODULE_DESCRIPTION("Support for the ancient 3Com 3c501 ethernet card");
+MODULE_LICENSE("GPL");
+