a4b770972b
This patch removes from drivers/net/ all the unnecessary return; statements that precede the last closing brace of void functions. It does not remove the returns that are immediately preceded by a label as gcc doesn't like that. It also does not remove null void functions with return. Done via: $ grep -rP --include=*.[ch] -l "return;\n}" net/ | \ xargs perl -i -e 'local $/ ; while (<>) { s/\n[ \t\n]+return;\n}/\n}/g; print; }' with some cleanups by hand. Compile tested x86 allmodconfig only. Signed-off-by: Joe Perches <joe@perches.com> Signed-off-by: David S. Miller <davem@davemloft.net>
896 lines
23 KiB
C
896 lines
23 KiB
C
/* 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 unsigned ports[] = { 0x280, 0x300, 0};
|
|
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_multicast_list = 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 isnt 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 isnt 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 isnt 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");
|
|
|