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

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)