patch-2.4.0-test9 linux/drivers/net/atp.c
Next file: linux/drivers/net/atp.h
Previous file: linux/drivers/net/arcnet/com20020-pci.c
Back to the patch index
Back to the overall index
- Lines: 1025
- Date:
Thu Sep 21 13:33:32 2000
- Orig file:
v2.4.0-test8/linux/drivers/net/atp.c
- Orig date:
Fri Aug 11 15:57:57 2000
diff -u --recursive --new-file v2.4.0-test8/linux/drivers/net/atp.c linux/drivers/net/atp.c
@@ -1,27 +1,59 @@
/* atp.c: Attached (pocket) ethernet adapter driver for linux. */
/*
- This is a driver for a commonly OEMed pocket (parallel port)
- ethernet adapter.
+ This is a driver for commonly OEM pocket (parallel port)
+ ethernet adapters based on the Realtek RTL8002 and RTL8012 chips.
- Written 1993,1994,1995 by Donald Becker.
+ Written 1993-2000 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 General Public License (GPL), incorporated herein by reference.
+ Drivers based on or derived from this code fall under the GPL and must
+ retain the authorship, copyright and license notice. This file is not
+ a complete program and may only be used when the entire operating
+ system is licensed under the GPL.
+
+ Copyright 1993 United States Government as represented by the Director,
+ National Security Agency. Copyright 1994-2000 retained by the original
+ author, Donald Becker. The timer-based reset code was supplied in 1995
+ by Bill Carlson, wwc@super.org.
+
+ The author may be reached as becker@scyld.com, or C/O
+ Scyld Computing Corporation
+ 410 Severn Ave., Suite 210
+ Annapolis MD 21403
- This software may be used and distributed according to the terms
- of the GNU Public License, incorporated herein by reference.
+ Support information and updates available at
+ http://www.scyld.com/network/atp.html
- 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.
-
Modular support/softnet added by Alan Cox.
+
*/
-static const char *version =
- "atp.c:v1.01 1/18/95 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n";
+static const char versionA[] =
+"atp.c:v1.09 8/9/2000 Donald Becker <becker@scyld.com>\n";
+static const char versionB[] =
+" http://www.scyld.com/network/atp.html\n";
+
+/* The user-configurable values.
+ These may be modified when a driver module is loaded.*/
+
+static int debug = 1; /* 1 normal messages, 0 quiet .. 7 verbose. */
+#define net_debug debug
+
+/* Maximum events (Rx packets, etc.) to handle at each interrupt. */
+static int max_interrupt_work = 15;
+
+#define NUM_UNITS 2
+/* The standard set of ISA module parameters. */
+static int io[NUM_UNITS] = {0, 0};
+static int irq[NUM_UNITS] = {0, 0};
+static int xcvr[NUM_UNITS] = {0, 0}; /* The data transfer mode. */
+
+/* Operational parameters that are set at compile time. */
+
+/* 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
@@ -35,8 +67,12 @@
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.
@@ -60,6 +96,10 @@
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
@@ -105,18 +145,40 @@
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
+#include <linux/delay.h>
#include "atp.h"
-/* 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;
+MODULE_AUTHOR("Donald Becker <becker@scyld.com>");
+MODULE_DESCRIPTION("RealTek RTL8002/8012 parallel port Ethernet driver");
+MODULE_PARM(max_interrupt_work, "i");
+MODULE_PARM(debug, "i");
+MODULE_PARM(io, "1-" __MODULE_STRING(MAX_UNITS) "i");
+MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_UNITS) "i");
+MODULE_PARM(xcvr, "1-" __MODULE_STRING(MAX_UNITS) "i");
+
+#define RUN_AT(x) (jiffies + (x))
/* 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,};
+
+struct net_local {
+ spinlock_t lock;
+ struct net_device *next_module;
+ struct net_device_stats stats;
+ struct timer_list timer; /* Media selection timer. */
+ long last_rx_time; /* Last Rx, in jiffies, to handle Rx hang. */
+ int saved_tx_size;
+ unsigned int 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;
+};
+
/* This code, written by wwc@super.org, resets the adapter every
TIMED_CHECKER ticks. This recovers from an unknown error which
hangs the device. */
@@ -124,40 +186,41 @@
#ifdef TIMED_CHECKER
#include <linux/timer.h>
static void atp_timed_checker(unsigned long ignored);
-static struct net_device *atp_timed_dev;
-static struct timer_list atp_timer = { function: atp_timed_checker };
#endif
/* Index to functions, as function prototypes. */
-static int atp_probe1(struct net_device *dev, short ioaddr);
+static int atp_probe1(struct net_device *dev, long ioaddr);
static void get_node_ID(struct net_device *dev);
-static unsigned short eeprom_op(short ioaddr, unsigned int cmd);
+static unsigned short eeprom_op(long ioaddr, unsigned int cmd);
static int net_open(struct net_device *dev);
static void hardware_init(struct net_device *dev);
-static void tx_timeout(struct net_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 net_device *dev);
-static void net_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static void write_packet(long ioaddr, int length, unsigned char *packet, int mode);
+static void trigger_send(long ioaddr, int length);
+static int atp_send_packet(struct sk_buff *skb, struct net_device *dev);
+static void atp_interrupt(int irq, void *dev_id, struct pt_regs *regs);
static void net_rx(struct net_device *dev);
-static void read_block(short ioaddr, int length, unsigned char *buffer, int data_mode);
+static void read_block(long ioaddr, int length, unsigned char *buffer, int data_mode);
static int net_close(struct net_device *dev);
static struct net_device_stats *net_get_stats(struct net_device *dev);
-static void set_multicast_list(struct net_device *dev);
+static void set_rx_mode_8002(struct net_device *dev);
+static void set_rx_mode_8012(struct net_device *dev);
+static void tx_timeout(struct net_device *dev);
+
+
+/* A list of all installed ATP devices, for removing the driver module. */
+static struct net_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).
*/
-
static int __init atp_init(struct net_device *dev)
{
int *port, ports[] = {0x378, 0x278, 0x3bc, 0};
- int base_addr = dev->base_addr;
+ int base_addr = dev ? dev->base_addr : io[0];
if (base_addr > 0x1ff) /* Check a single specified location. */
return atp_probe1(dev, base_addr);
@@ -165,7 +228,7 @@
return -ENXIO;
for (port = ports; *port; port++) {
- int ioaddr = *port;
+ long ioaddr = *port;
outb(0x57, ioaddr + PAR_DATA);
if (inb(ioaddr + PAR_DATA) != 0x57)
continue;
@@ -176,36 +239,68 @@
return -ENODEV;
}
-static int __init atp_probe1(struct net_device *dev, short ioaddr)
+static int __init atp_probe1(struct net_device *dev, long ioaddr)
{
- int saved_ctrl_reg, status;
+ 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);
+ if (net_debug > 3)
+ printk("atp: Control register was %#2.2x.\n", saved_ctrl_reg);
/* IRQEN=0, SLCTB=high INITB=high, AUTOFDB=high, STBB=high. */
outb(0x04, ioaddr + PAR_CONTROL);
+#ifndef final_version
+ if (net_debug > 3) {
+ /* Turn off the printer multiplexer on the 8012. */
+ for (i = 0; i < 8; i++)
+ outb(mux_8012[i], ioaddr + PAR_DATA);
+ write_reg(ioaddr, MODSEL, 0x00);
+ printk("atp: Registers are ");
+ for (i = 0; i < 32; i++)
+ printk(" %2.2x", read_nibble(ioaddr, i));
+ printk(".\n");
+ }
+#endif
+ /* 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);
- udelay(100);
+ /* udelay() here? */
status = read_nibble(ioaddr, CMR1);
+ if (net_debug > 3) {
+ printk(KERN_DEBUG "atp: Status nibble was %#2.2x..", status);
+ for (i = 0; i < 32; i++)
+ printk(" %2.2x", read_nibble(ioaddr, i));
+ printk("\n");
+ }
+
if ((status & 0x78) != 0x08) {
/* The pocket adapter probe failed, restore the control register. */
outb(saved_ctrl_reg, ioaddr + PAR_CONTROL);
- return 1;
+ return -ENODEV;
}
status = read_nibble(ioaddr, CMR2_h);
if ((status & 0x78) != 0x10) {
outb(saved_ctrl_reg, ioaddr + PAR_CONTROL);
- return 1;
+ return -ENODEV;
}
+
+ dev = init_etherdev(dev, sizeof(struct net_local));
+ if (!dev)
+ return -ENOMEM;
+
/* 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)
+ if (irq[0])
+ dev->irq = irq[0];
+ else if (ioaddr == 0x378)
dev->irq = 7;
else
dev->irq = 5;
@@ -217,69 +312,73 @@
/* Read the station address PROM. */
get_node_ID(dev);
- printk("%s: Pocket adapter found at %#3lx, IRQ %d, SAPROM "
+#ifndef MODULE
+ if (net_debug)
+ printk(KERN_INFO "%s" KERN_INFO "%s", versionA, versionB);
+#endif
+
+ printk(KERN_NOTICE "%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]);
- /* Leave the hardware in a reset state. */
- write_reg_high(ioaddr, CMR1, CMR1h_RESET);
-
- if (net_debug)
- printk(version);
+ /* Reset the ethernet hardware and activate the printer pass-through. */
+ write_reg_high(ioaddr, CMR1, CMR1h_RESET | CMR1h_MUX);
/* Initialize the device structure. */
ether_setup(dev);
- dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
+ 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));
- {
- struct net_local *lp = (struct net_local *)dev->priv;
- lp->addr_mode = CMR2h_Normal;
- spin_lock_init(&lp->lock);
- }
+
+ lp = (struct net_local *)dev->priv;
+ lp->chip_type = RTL8002;
+ lp->addr_mode = CMR2h_Normal;
+ spin_lock_init(&lp->lock);
+
+ 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 (xcvr[0])
+ dev->if_port = xcvr[0];
+ else
+ 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->hard_start_xmit = atp_send_packet;
dev->get_stats = net_get_stats;
- dev->set_multicast_list = set_multicast_list;
+ dev->set_multicast_list =
+ lp->chip_type == RTL8002 ? &set_rx_mode_8002 : &set_rx_mode_8012;
dev->tx_timeout = tx_timeout;
- dev->watchdog_timeo = HZ/20;
+ dev->watchdog_timeo = TX_TIMEOUT;
-#ifdef TIMED_CHECKER
- del_timer(&atp_timer);
- atp_timer.expires = jiffies + TIMED_CHECKER;
- atp_timed_dev = dev;
- add_timer(&atp_timer);
-#endif
return 0;
}
/* Read the station address PROM, usually a word-wide EEPROM. */
static void __init get_node_ID(struct net_device *dev)
{
- short ioaddr = dev->base_addr;
+ long 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)));
-
+ ((u16 *)dev->dev_addr)[i] =
+ be16_to_cpu(eeprom_op(ioaddr, EE_READ(sa_offset + i)));
+
write_reg(ioaddr, CMR2, CMR2_NULL);
}
@@ -295,26 +394,24 @@
* DO : _________X_______X
*/
-static unsigned short __init eeprom_op(short ioaddr, unsigned int cmd)
+static unsigned short __init eeprom_op(long 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);
- udelay(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++;
- udelay(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.
@@ -327,17 +424,25 @@
*/
static int net_open(struct net_device *dev)
{
+ struct net_local *lp = (struct net_local *)dev->priv;
+
+ MOD_INC_USE_COUNT;
/* 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. */
-
- if (request_irq(dev->irq, &net_interrupt, 0, "ATP", dev)) {
+ if (request_irq(dev->irq, &atp_interrupt, 0, "ATP Ethernet", dev)) {
+ MOD_DEC_USE_COUNT;
return -EAGAIN;
}
+
hardware_init(dev);
- MOD_INC_USE_COUNT;
+ 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);
netif_start_queue(dev);
return 0;
@@ -348,54 +453,57 @@
static void hardware_init(struct net_device *dev)
{
struct net_local *lp = (struct net_local *)dev->priv;
- int ioaddr = dev->base_addr;
- int i;
+ long 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++)
+
+ 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,
+ printk(KERN_DEBUG "%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);
+ 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);
+ 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->pac_cnt_in_tx_buf = 0;
lp->saved_tx_size = 0;
}
-static void trigger_send(short ioaddr, int length)
+static void trigger_send(long 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)
+static void write_packet(long 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) {
+ 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 {
+ } else {
/* Write the packet out in slow mode. */
unsigned char outbyte = *packet++;
@@ -409,77 +517,85 @@
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);
+ }
+ /* Terminate the Tx frame. End of write: ECB. */
+ outb(0xff, ioaddr + PAR_DATA);
+ outb(Ctrl_HNibWrite | Ctrl_SelData | Ctrl_IRQEN, ioaddr + PAR_CONTROL);
}
static void tx_timeout(struct net_device *dev)
{
- struct net_local *lp = (struct net_local *)dev->priv;
- int ioaddr = dev->base_addr;
- /* If we get here, some higher level has decided we are broken. */
- printk(KERN_WARNING "%s: transmit timed out, %s?\n", dev->name,
+ struct net_local *np = (struct net_local *)dev->priv;
+ long ioaddr = dev->base_addr;
+
+ printk(KERN_WARNING "%s: Transmit timed out, %s?\n", dev->name,
inb(ioaddr + PAR_CONTROL) & 0x10 ? "network cable problem"
: "IRQ conflict");
- lp->stats.tx_errors++;
+ np->stats.tx_errors++;
/* Try to restart the adapter. */
hardware_init(dev);
dev->trans_start = jiffies;
netif_wake_queue(dev);
+ np->stats.tx_errors++;
+ return;
}
-static int net_send_packet(struct sk_buff *skb, struct net_device *dev)
+static int atp_send_packet(struct sk_buff *skb, struct net_device *dev)
{
struct net_local *lp = (struct net_local *)dev->priv;
- int ioaddr = dev->base_addr;
- short length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN;
- unsigned char *buf = skb->data;
- unsigned long flags;
-
+ long ioaddr = dev->base_addr;
+ int length;
+ long flags;
+
+ length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN;
+
netif_stop_queue(dev);
-
+
/* Disable interrupts by writing 0x00 to the Interrupt Mask Register.
This sequence must not be interrupted by an incoming packet. */
-
+
spin_lock_irqsave(&lp->lock, flags);
write_reg(ioaddr, IMR, 0);
write_reg_high(ioaddr, IMR, 0);
spin_unlock_irqrestore(&lp->lock, flags);
-
- write_packet(ioaddr, length, buf, dev->if_port);
+
+ write_packet(ioaddr, length, skb->data, 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;
+ 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->trans_start = jiffies;
dev_kfree_skb (skb);
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)
+static void atp_interrupt(int irq, void *dev_instance, struct pt_regs * regs)
{
- struct net_device *dev = dev_id;
+ struct net_device *dev = (struct net_device *)dev_instance;
struct net_local *lp;
- int ioaddr, status, boguscount = 20;
+ long ioaddr;
static int num_tx_since_rx = 0;
+ int boguscount = max_interrupt_work;
+ if (dev == NULL) {
+ printk(KERN_ERR "ATP_interrupt(): irq %d for unknown device.\n", irq);
+ return;
+ }
ioaddr = dev->base_addr;
lp = (struct net_local *)dev->priv;
-
+
spin_lock(&lp->lock);
/* Disable additional spurious interrupts. */
@@ -489,14 +605,10 @@
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 (net_debug > 5) printk(KERN_DEBUG "%s: In interrupt ", dev->name);
+ while (--boguscount > 0) {
+ int 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. */
@@ -534,8 +646,7 @@
break;
}
/* Attempt to retransmit. */
- if (net_debug > 6)
- printk("attempting to ReTx");
+ if (net_debug > 6) printk("attempting to ReTx");
write_reg(ioaddr, CMR1, CMR1_ReXmit + CMR1_Xmit);
} else {
/* Finish up the transmit. */
@@ -551,10 +662,10 @@
}
num_tx_since_rx++;
} else if (num_tx_since_rx > 8
- && jiffies - dev->last_rx > 100) {
+ && jiffies > dev->last_rx + HZ) {
if (net_debug > 2)
- printk("%s: Missed packet? No Rx after %d Tx and %ld jiffies"
- " status %02x CMR1 %02x.\n", dev->name,
+ printk(KERN_DEBUG "%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++;
@@ -563,7 +674,7 @@
break;
} else
break;
- }
+ }
/* This following code fixes a rare (and very difficult to track down)
problem where the adapter forgets its ethernet address. */
@@ -571,40 +682,62 @@
int i;
for (i = 0; i < 6; i++)
write_reg_byte(ioaddr, PAR0 + i, dev->dev_addr[i]);
-#ifdef TIMED_CHECKER
- mod_timer(&atp_timer, jiffies+TIMED_CHECKER);
+#if 0 && defined(TIMED_CHECKER)
+ mod_timer(&lp->timer, RUN_AT(TIMED_CHECKER));
#endif
}
/* Tell the adapter that it can go back to using the output line as IRQ. */
- write_reg(ioaddr, CMR2, CMR2_IRQOUT);
+ 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");
-
spin_unlock(&lp->lock);
+
+ if (net_debug > 5) printk("exiting interrupt.\n");
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 ignored)
+static void atp_timed_checker(unsigned long data)
{
+ struct net_device *dev = (struct net_device *)data;
+ long ioaddr = dev->base_addr;
+ struct net_local *lp = (struct net_local *)dev->priv;
+ int tickssofar = jiffies - lp->last_rx_time;
int i;
- struct net_local *lp = (struct net_local *)atp_timed_dev->priv;
- int ioaddr = atp_timed_dev->base_addr;
spin_lock(&lp->lock);
- for (i = 0; i < 6; i++)
- write_reg_byte(ioaddr, PAR0 + i, atp_timed_dev->dev_addr[i]);
+ if (tickssofar > 2*HZ) {
+#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
+ }
spin_unlock(&lp->lock);
- mod_timer(&atp_timer, jiffies+TIMED_CHECKER);
+ lp->timer.expires = RUN_AT(TIMED_CHECKER);
+ add_timer(&lp->timer);
}
#endif
@@ -612,61 +745,57 @@
static void net_rx(struct net_device *dev)
{
struct net_local *lp = (struct net_local *)dev->priv;
- int ioaddr = dev->base_addr;
-#ifdef notdef
- ushort header[4];
-#else
+ long ioaddr = dev->base_addr;
struct rx_header rx_head;
-#endif
/* 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,
+ printk(KERN_DEBUG " 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++;
- /* Ackkk! I don't have any documentation on what the error bits mean!
- The best I can do is slap the device around a bit. */
- if (net_debug > 3) printk("%s: Unknown ATP Rx error %04x.\n",
- dev->name, rx_head.rx_status);
- hardware_init(dev);
+ 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(KERN_DEBUG "%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. */
- int pkt_len = (rx_head.rx_count & 0x7ff) - 4; /* The "-4" is omits the FCS (CRC). */
+ /* Malloc up new buffer. The "-4" omits the FCS (CRC). */
+ int pkt_len = (rx_head.rx_count & 0x7ff) - 4;
struct sk_buff *skb;
-
- skb = dev_alloc_skb(pkt_len);
+
+ skb = dev_alloc_skb(pkt_len + 2);
if (skb == NULL) {
- printk("%s: Memory squeeze, dropping packet.\n", dev->name);
+ printk(KERN_ERR "%s: Memory squeeze, dropping packet.\n",
+ dev->name);
lp->stats.rx_dropped++;
goto done;
}
skb->dev = dev;
-
- read_block(ioaddr, pkt_len, skb_put(skb,pkt_len), dev->if_port);
- if (net_debug > 6) {
- unsigned char *data = skb->data;
- printk(" data %02x%02x%02x %02x%02x%02x %02x%02x%02x"
- "%02x%02x%02x %02x%02x..",
- data[0], data[1], data[2], data[3], data[4], data[5],
- data[6], data[7], data[8], data[9], data[10], data[11],
- data[12], data[13]);
- }
-
- skb->protocol=eth_type_trans(skb,dev);
+ 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);
netif_rx(skb);
lp->stats.rx_packets++;
+ lp->stats.rx_bytes += pkt_len;
}
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)
+static void read_block(long ioaddr, int length, unsigned char *p, int data_mode)
{
if (data_mode <= 3) { /* Mode 0 or 1 */
@@ -682,18 +811,21 @@
else
do *p++ = read_byte_mode6(ioaddr); while (--length > 0);
- outb(EOC+HNib+MAR, ioaddr + PAR_DATA);
+ outb(EOC+HNib+MAR, ioaddr + PAR_DATA);
outb(Ctrl_SelData, ioaddr + PAR_CONTROL);
}
/* The inverse routine to net_open(). */
-static int net_close(struct net_device *dev)
+static int
+net_close(struct net_device *dev)
{
struct net_local *lp = (struct net_local *)dev->priv;
- int ioaddr = dev->base_addr;
+ long ioaddr = dev->base_addr;
netif_stop_queue(dev);
+ del_timer_sync(&lp->timer);
+
/* Flush the Tx and disable Rx here. */
lp->addr_mode = CMR2h_OFF;
write_reg_high(ioaddr, CMR2, CMR2h_OFF);
@@ -702,8 +834,8 @@
outb(0x00, ioaddr + PAR_CONTROL);
free_irq(dev->irq, dev);
- /* Leave the hardware in a reset state. */
- write_reg_high(ioaddr, CMR1, CMR1h_RESET);
+ /* Reset the ethernet hardware and activate the printer pass-through. */
+ write_reg_high(ioaddr, CMR1, CMR1h_RESET | CMR1h_MUX);
MOD_DEC_USE_COUNT;
@@ -712,7 +844,8 @@
/* Get the current statistics. This may be called with the card open or
closed. */
-static struct net_device_stats *net_get_stats(struct net_device *dev)
+static struct net_device_stats *
+net_get_stats(struct net_device *dev)
{
struct net_local *lp = (struct net_local *)dev->priv;
return &lp->stats;
@@ -721,46 +854,101 @@
/*
* Set or clear the multicast filter for this adapter.
*/
-
-static void set_multicast_list(struct net_device *dev)
+
+/* 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 set_rx_mode_8002(struct net_device *dev)
{
struct net_local *lp = (struct net_local *)dev->priv;
- short ioaddr = dev->base_addr;
- int num_addrs=dev->mc_count;
-
- if(dev->flags&(IFF_ALLMULTI|IFF_PROMISC))
- num_addrs=1;
- /*
- * We must make the kernel realise we had to move
- * into promisc mode or we start all out war on
- * the cable. - AC
- */
- if(num_addrs)
- dev->flags|=IFF_PROMISC;
- lp->addr_mode = num_addrs ? CMR2h_PROMISC : CMR2h_Normal;
+ long 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);
}
-/* module stuff */
-static int io;
-static struct net_device atp_dev = { init: atp_init };
-MODULE_AUTHOR("Donald Becker <becker@scyld.com>");
-MODULE_DESCRIPTION("Realtek 8002/8012 Pocket Lan Adapter");
-MODULE_PARM(io, "I/O port of the pocket adapter");
+static void set_rx_mode_8012(struct net_device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ long ioaddr = dev->base_addr;
+ unsigned char new_mode, mc_filter[8]; /* Multicast hash filter */
+ int i;
-static int __init atp_init_module(void) {
- atp_dev.base_addr = io;
+ 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;
- if (register_netdev(&atp_dev) != 0)
- return -EIO;
+ 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(KERN_DEBUG "%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");
+ }
- return 0;
+ write_reg_high(ioaddr, CMR2, lp->addr_mode);
+ write_reg(ioaddr, CMR2, CMR2_IRQOUT); /* Switch back to page 0 */
+}
+
+static int __init atp_init_module(void) {
+ if (debug) /* Emit version even if no cards detected. */
+ printk(KERN_INFO "%s" KERN_INFO "%s", versionA, versionB);
+ return atp_init(NULL);
}
static void __exit atp_cleanup_module(void) {
- unregister_netdev(&atp_dev);
+ struct net_device *next_dev;
+
+ while (root_atp_dev) {
+ next_dev = ((struct net_local *)root_atp_dev->priv)->next_module;
+ unregister_netdev(root_atp_dev);
+ /* No need to release_region(), since we never snarf it. */
+ kfree(root_atp_dev);
+ root_atp_dev = next_dev;
+ }
}
module_init(atp_init_module);
module_exit(atp_cleanup_module);
-
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)