patch-2.3.99-pre7 linux/drivers/net/via-rhine.c

Next file: linux/drivers/net/wan/comx.c
Previous file: linux/drivers/net/tulip/tulip_core.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.99-pre6/linux/drivers/net/via-rhine.c linux/drivers/net/via-rhine.c
@@ -1,22 +1,27 @@
 /* via-rhine.c: A Linux Ethernet device driver for VIA Rhine family chips. */
 /*
-	Written 1998-1999 by Donald Becker.
+	Written 1998-2000 by Donald Becker.
 
-	This software may be used and distributed according to the terms
-	of the GNU Public License (GPL), incorporated herein by reference.
-	Drivers derived from this code also fall under the GPL and must retain
-	this authorship and copyright notice.
+	This 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.
 
 	This driver is designed for the VIA VT86c100A Rhine-II PCI Fast Ethernet
 	controller.  It also works with the older 3043 Rhine-I chip.
 
-	The author may be reached as becker@cesdis.edu, or
-	Donald Becker
-	312 Severn Ave. #W302
+	The author may be reached as becker@scyld.com, or C/O
+	Scyld Computing Corporation
+	410 Severn Ave., Suite 210
 	Annapolis MD 21403
 
-	Support and updates available at
-	http://cesdis.gsfc.nasa.gov/linux/drivers/via-rhine.html
+
+	This driver contains some changes from the original Donald Becker
+	version. He may or may not be interested in bug reports on this
+	code. You can find his versions at:
+	http://www.scyld.com/network/via-rhine.html
 
 
 	Linux kernel version history:
@@ -41,10 +46,15 @@
 	LK1.1.4:
 	- Urban Widmark: fix gcc 2.95.2 problem and
 	                 remove writel's to fixed address 0x7c
+
+	LK1.1.5:
+	- Urban Widmark: mdio locking, bounce buffer changes
+	                 merges from Beckers 1.05 version
+	                 added netif_running_on/off support
 */
 
-/* A few user-configurable values.   These may be modified when a driver
-   module is loaded. */
+/* A few user-configurable values.
+   These may be modified when a driver module is loaded. */
 
 static int debug = 1;			/* 1 normal messages, 0 quiet .. 7 verbose. */
 static int max_interrupt_work = 20;
@@ -74,7 +84,8 @@
    Making the Tx ring too large decreases the effectiveness of channel
    bonding and packet priority.
    There are no ill effects from too-large receive rings. */
-#define TX_RING_SIZE	8
+#define TX_RING_SIZE	16
+#define TX_QUEUE_LEN	10				/* Limit ring entries actually used.  */
 #define RX_RING_SIZE	16
 
 
@@ -89,7 +100,7 @@
 #if !defined(__OPTIMIZE__)  ||  !defined(__KERNEL__)
 #warning  You must compile this file with the correct options!
 #warning  See the last lines of the source file.
-#error  See the last lines of the source file for the proper compile-command.
+#error  You must compile this driver with "-O".
 #endif
 
 #include <linux/module.h>
@@ -109,10 +120,11 @@
 #include <asm/bitops.h>
 #include <asm/io.h>
 
-static const char *versionA __devinitdata =
-"via-rhine.c:v1.03a-LK1.1.4  3/28/2000  Written by Donald Becker\n";
-static const char *versionB __devinitdata =
-"  http://cesdis.gsfc.nasa.gov/linux/drivers/via-rhine.html\n";
+/* These identify the driver base version and may not be removed. */
+static char version1[] __devinitdata =
+"via-rhine.c:v1.05-LK1.1.5  5/2/2000  Written by Donald Becker\n";
+static char version2[] __devinitdata =
+"  http://www.scyld.com/network/via-rhine.html\n";
 
 
 
@@ -137,9 +149,7 @@
 #define writel outl
 #endif
 
-#define RUN_AT(x) (jiffies + (x))
-
-MODULE_AUTHOR("Donald Becker <becker@cesdis.gsfc.nasa.gov>");
+MODULE_AUTHOR("Donald Becker <becker@scyld.com>");
 MODULE_DESCRIPTION("VIA Rhine PCI Fast Ethernet driver");
 MODULE_PARM(max_interrupt_work, "i");
 MODULE_PARM(debug, "i");
@@ -262,7 +272,7 @@
 };
 
 
-enum chip_capability_flags {CanHaveMII=1, };
+enum chip_capability_flags {CanHaveMII=1, HasESIPhy=2 };
 
 #if defined(VIA_USE_MEMORY)
 #define RHINE_IOTYPE (PCI_USES_MEM | PCI_USES_MASTER | PCI_ADDR1)
@@ -309,6 +319,19 @@
 	IntrNormalSummary=0x0003, IntrAbnormalSummary=0xC260,
 };
 
+/* MII interface, status flags.
+   Not to be confused with the MIIStatus register ... */
+enum mii_status_bits {
+	MIICap100T4			= 0x8000,
+	MIICap10100HdFd		= 0x7800,
+	MIIPreambleSupr		= 0x0040,
+	MIIAutoNegCompleted	= 0x0020,
+	MIIRemoteFault		= 0x0010,
+	MIICapAutoNeg		= 0x0008,
+	MIILink				= 0x0004,
+	MIIJabber			= 0x0002,
+	MIIExtended			= 0x0001
+};
 
 /* The Rx and Tx buffer descriptors. */
 struct rx_desc {
@@ -355,7 +378,11 @@
 	/* The saved address of a sent-in-place packet/buffer, for later free(). */
 	struct sk_buff *tx_skbuff[TX_RING_SIZE];
 	dma_addr_t tx_skbuff_dma[TX_RING_SIZE];
-	unsigned char *tx_buf[TX_RING_SIZE];	/* Tx bounce buffers */
+
+	/* Tx bounce buffers */
+	unsigned char *tx_buf[TX_RING_SIZE];
+	unsigned char *tx_bufs;
+	dma_addr_t tx_bufs_dma;
 
 	struct pci_dev *pdev;
 	struct net_device_stats stats;
@@ -363,25 +390,23 @@
 	spinlock_t lock;
 
 	/* Frequently used values: keep some adjacent for cache effect. */
-	int chip_id;
+	int chip_id, drv_flags;
 	struct rx_desc *rx_head_desc;
 	unsigned int cur_rx, dirty_rx;		/* Producer/consumer ring indices */
 	unsigned int cur_tx, dirty_tx;
 	unsigned int rx_buf_sz;				/* Based on MTU+slack. */
 	u16 chip_cmd;						/* Current setting for ChipCmd */
-	unsigned int tx_full:1;				/* The Tx queue is full. */
 
 	/* These values are keep track of the transceiver/media in use. */
 	unsigned int full_duplex:1;			/* Full-duplex operation requested. */
 	unsigned int duplex_lock:1;
-	unsigned int medialock:1;			/* Do not sense media. */
 	unsigned int default_port:4;		/* Last dev->if_port value. */
 	u8 tx_thresh, rx_thresh;
 
 	/* MII transceiver section. */
-	int mii_cnt;						/* MII device addresses. */
 	u16 advertising;					/* NWay media advertisement */
 	unsigned char phys[2];				/* MII device addresses. */
+	u16 mii_status;						/* last read MII status */
 };
 
 static int  mdio_read(struct net_device *dev, int phy_id, int location);
@@ -400,6 +425,7 @@
 static struct net_device_stats *via_rhine_get_stats(struct net_device *dev);
 static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
 static int  via_rhine_close(struct net_device *dev);
+static inline void clear_tally_counters(long ioaddr);
 
 
 static int __devinit via_rhine_init_one (struct pci_dev *pdev,
@@ -420,8 +446,8 @@
 	
 	/* print version once and once only */
 	if (! did_version++) {
-		printk (KERN_INFO "%s", versionA);
-		printk (KERN_INFO "%s", versionB);
+		printk (KERN_INFO "%s", version1);
+		printk (KERN_INFO "%s", version2);
 	}
 	
 	card_idx++;
@@ -454,11 +480,8 @@
 
 	ioaddr = pci_resource_start (pdev, pci_flags & PCI_ADDR0 ? 0 : 1);
 
-	if (pci_enable_device (pdev)) {
-		printk (KERN_ERR "unable to init PCI device (card #%d)\n",
-			card_idx);
+	if (pci_enable_device (pdev))
 		goto err_out_free_dma;
-	}
 	
 	if (pci_flags & PCI_USES_MASTER)
 		pci_set_master (pdev);
@@ -500,8 +523,8 @@
 	printk(KERN_INFO "%s: %s at 0x%lx, ",
 		   dev->name, via_rhine_chip_info[chip_id].name, ioaddr);
 
-	/* Ideally we would be read the EEPROM but access may be locked. */
-	for (i = 0; i <6; i++)
+	/* Ideally we would read the EEPROM but access may be locked. */
+	for (i = 0; i < 6; i++)
 		dev->dev_addr[i] = readb(ioaddr + StationAddr + i);
 	for (i = 0; i < 5; i++)
 			printk("%2.2x:", dev->dev_addr[i]);
@@ -516,6 +539,7 @@
 	np = dev->priv;
 	spin_lock_init (&np->lock);
 	np->chip_id = chip_id;
+	np->drv_flags = via_rhine_chip_info[chip_id].drv_flags;
 	np->pdev = pdev;
 	np->rx_ring = ring;
 	np->tx_ring = ring + RX_RING_SIZE * sizeof(struct rx_desc);
@@ -530,8 +554,6 @@
 		if (option & 0x200)
 			np->full_duplex = 1;
 		np->default_port = option & 15;
-		if (np->default_port)
-			np->medialock = 1;
 	}
 	if (card_idx < MAX_UNITS  &&  full_duplex[card_idx] > 0)
 		np->full_duplex = 1;
@@ -551,7 +573,7 @@
 	
 	pdev->driver_data = dev;
 
-	if (via_rhine_chip_info[chip_id].drv_flags & CanHaveMII) {
+	if (np->drv_flags & CanHaveMII) {
 		int phy, phy_idx = 0;
 		np->phys[0] = 1;		/* Standard for this chip. */
 		for (phy = 1; phy < 32 && phy_idx < 4; phy++) {
@@ -565,7 +587,6 @@
 					   mdio_read(dev, phy, 5));
 			}
 		}
-		np->mii_cnt = phy_idx;
 	}
 
 	return 0;
@@ -630,7 +651,6 @@
 	writeb(regnum, ioaddr + MIIRegAddr);
 	writew(value, ioaddr + MIIData);
 	writeb(0x20, ioaddr + MIICmd);			/* Trigger write. */
-	return;
 }
 
 
@@ -654,6 +674,14 @@
 		printk(KERN_DEBUG "%s: via_rhine_open() irq %d.\n",
 			   dev->name, dev->irq);
 
+	np->tx_bufs = pci_alloc_consistent(np->pdev, PKT_BUF_SZ * TX_RING_SIZE,
+									   &np->tx_bufs_dma);
+	if (np->tx_bufs == NULL) {
+		free_irq(dev->irq, dev);
+		MOD_DEC_USE_COUNT;
+		return -ENOMEM;
+	}
+
 	via_rhine_init_ring(dev);
 
 	writel(np->rx_ring_dma, ioaddr + RxRingPtr);
@@ -689,6 +717,12 @@
 
 	via_rhine_check_duplex(dev);
 
+	/* The LED outputs of various MII xcvrs should be configured.  */
+	/* For NS or Mison phys, turn on bit 1 in register 0x17 */
+	/* For ESI phys, turn on bit 7 in register 0x17. */
+	mdio_write(dev, np->phys[0], 0x17, mdio_read(dev, np->phys[0], 0x17) |
+			   (np->drv_flags & HasESIPhy) ? 0x0080 : 0x0001);
+
 	if (debug > 2)
 		printk(KERN_DEBUG "%s: Done via_rhine_open(), status %4.4x "
 			   "MII status: %4.4x.\n",
@@ -697,7 +731,7 @@
 
 	/* Set the timer to check for link beat. */
 	init_timer(&np->timer);
-	np->timer.expires = RUN_AT(1);
+	np->timer.expires = jiffies + 2;
 	np->timer.data = (unsigned long)dev;
 	np->timer.function = &via_rhine_timer;				/* timer handler */
 	add_timer(&np->timer);
@@ -737,15 +771,30 @@
 	struct netdev_private *np = (struct netdev_private *)dev->priv;
 	long ioaddr = dev->base_addr;
 	int next_tick = 10*HZ;
+	int mii_status;
 
 	if (debug > 3) {
 		printk(KERN_DEBUG "%s: VIA Rhine monitor tick, status %4.4x.\n",
 			   dev->name, readw(ioaddr + IntrStatus));
 	}
 
+	spin_lock_irq (&np->lock);
+
 	via_rhine_check_duplex(dev);
 
-	np->timer.expires = RUN_AT(next_tick);
+	/* make IFF_RUNNING follow the MII status bit "Link established" */
+	mii_status = mdio_read(dev, np->phys[0], 1);
+	if ( (mii_status & MIILink) != (np->mii_status & MIILink) ) {
+		if (mii_status & MIILink)
+			netif_carrier_on(dev);
+		else
+			netif_carrier_off(dev);
+	}
+	np->mii_status = mii_status;
+
+	spin_unlock_irq (&np->lock);
+
+	np->timer.expires = jiffies + next_tick;
 	add_timer(&np->timer);
 }
 
@@ -755,6 +804,11 @@
 	struct netdev_private *np = (struct netdev_private *) dev->priv;
 	long ioaddr = dev->base_addr;
 
+	/* Lock to protect mdio_read and access to stats. A friendly
+       advice to the implementor of the XXXs in this function is to be
+       sure not to spin too long (whatever that means :) */
+	spin_lock_irq (&np->lock);
+
 	printk (KERN_WARNING "%s: Transmit timed out, status %4.4x, PHY status "
 		"%4.4x, resetting...\n",
 		dev->name, readw (ioaddr + IntrStatus),
@@ -771,6 +825,8 @@
 
 	dev->trans_start = jiffies;
 	np->stats.tx_errors++;
+
+	spin_unlock_irq (&np->lock);
 }
 
 
@@ -797,7 +853,7 @@
 	/* Mark the last entry as wrapping the ring. */
 	np->rx_ring[i-1].next_desc = cpu_to_le32(np->rx_ring_dma);
 
-	/* Fill in the Rx buffers. */
+	/* Fill in the Rx buffers.  Handle allocation failure gracefully. */
 	for (i = 0; i < RX_RING_SIZE; i++) {
 		struct sk_buff *skb = dev_alloc_skb(np->rx_buf_sz);
 		np->rx_skbuff[i] = skb;
@@ -821,7 +877,7 @@
 		np->tx_ring[i].desc_length = cpu_to_le32(0x00e08000);
 		next += sizeof(struct tx_desc);
 		np->tx_ring[i].next_desc = cpu_to_le32(next);
-		np->tx_buf[i] = kmalloc(PKT_BUF_SZ, GFP_KERNEL);
+		np->tx_buf[i] = &np->tx_bufs[i * PKT_BUF_SZ];
 	}
 	np->tx_ring[i-1].next_desc = cpu_to_le32(np->tx_ring_dma);
 
@@ -832,13 +888,12 @@
 {
 	struct netdev_private *np = (struct netdev_private *)dev->priv;
 	unsigned entry;
-	unsigned long flags;
 
 	/* Caution: the write order is important here, set the field
 	   with the "ownership" bits last. */
 
 	/* lock eth irq */
-	spin_lock_irqsave (&np->lock, flags);
+	spin_lock_irq (&np->lock);
 
 	/* Calculate the next Tx descriptor entry. */
 	entry = np->cur_tx % TX_RING_SIZE;
@@ -846,21 +901,15 @@
 	np->tx_skbuff[entry] = skb;
 
 	if ((long)skb->data & 3) {			/* Must use alignment buffer. */
-		if (np->tx_buf[entry] == NULL &&
-			(np->tx_buf[entry] = kmalloc(PKT_BUF_SZ, GFP_KERNEL)) == NULL) {
-			spin_unlock_irqrestore (&np->lock, flags);
-			return 1;
-		}
 		memcpy(np->tx_buf[entry], skb->data, skb->len);
-		np->tx_skbuff_dma[entry] =
-			pci_map_single(np->pdev, np->tx_buf[entry], skb->len,
-						   PCI_DMA_TODEVICE);
+		np->tx_skbuff_dma[entry] = 0;
+		np->tx_ring[entry].addr = cpu_to_le32(np->tx_bufs_dma +
+										  (np->tx_buf[entry] - np->tx_bufs));
 	} else {
 		np->tx_skbuff_dma[entry] =
-			pci_map_single(np->pdev, skb->data, skb->len,
-						   PCI_DMA_TODEVICE);
+			pci_map_single(np->pdev, skb->data, skb->len, PCI_DMA_TODEVICE);
+		np->tx_ring[entry].addr = cpu_to_le32(np->tx_skbuff_dma[entry]);
 	}
-	np->tx_ring[entry].addr = cpu_to_le32(np->tx_skbuff_dma[entry]);
 
 	np->tx_ring[entry].desc_length = 
 		cpu_to_le32(0x00E08000 | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN));
@@ -873,12 +922,12 @@
 	/* Wake the potentially-idle transmit channel. */
 	writew(CmdTxDemand | np->chip_cmd, dev->base_addr + ChipCmd);
 
-	if (np->cur_tx == np->dirty_tx + TX_RING_SIZE)
+	if (np->cur_tx == np->dirty_tx + TX_QUEUE_LEN)
 		netif_stop_queue(dev);
 
 	dev->trans_start = jiffies;
 
-	spin_unlock_irqrestore (&np->lock, flags);
+	spin_unlock_irq (&np->lock);
 
 	if (debug > 4) {
 		printk(KERN_DEBUG "%s: Transmit frame #%d queued in slot %d.\n",
@@ -892,8 +941,9 @@
 static void via_rhine_interrupt(int irq, void *dev_instance, struct pt_regs *rgs)
 {
 	struct net_device *dev = (struct net_device *)dev_instance;
-	long ioaddr, boguscnt = max_interrupt_work;
+	long ioaddr;
 	u32 intr_status;
+	int boguscnt = max_interrupt_work;
 
 	ioaddr = dev->base_addr;
 	
@@ -940,7 +990,7 @@
 
 	spin_lock (&np->lock);
 
-	/* if tx_full is set, they're all dirty, not clean */
+	/* find and cleanup dirty tx descriptors */
 	while (np->dirty_tx != np->cur_tx) {
 		txstatus = le32_to_cpu(np->tx_ring[entry].tx_status);
 		if (txstatus & DescOwn)
@@ -961,18 +1011,20 @@
 			/* Transmitter restarted in 'abnormal' handler. */
 		} else {
 			np->stats.collisions += (txstatus >> 3) & 15;
-			np->stats.tx_bytes += le32_to_cpu(np->tx_ring[entry].desc_length) & 0x7ff;
+			np->stats.tx_bytes += np->tx_skbuff[entry]->len;
 			np->stats.tx_packets++;
 		}
 		/* Free the original skb. */
-		pci_unmap_single(np->pdev,
-						 le32_to_cpu(np->tx_ring[entry].addr),
-						 np->tx_skbuff[entry]->len, PCI_DMA_TODEVICE);
+		if (np->tx_skbuff_dma[entry]) {
+			pci_unmap_single(np->pdev,
+							 np->tx_skbuff_dma[entry],
+							 np->tx_skbuff[entry]->len, PCI_DMA_TODEVICE);
+		}
 		dev_kfree_skb_irq(np->tx_skbuff[entry]);
 		np->tx_skbuff[entry] = NULL;
 		entry = (++np->dirty_tx) % TX_RING_SIZE;
 	}
-	if ((np->cur_tx - np->dirty_tx) <= TX_RING_SIZE/2)
+	if ((np->cur_tx - np->dirty_tx) < TX_QUEUE_LEN - 4)
 		netif_wake_queue (dev);
 
 	spin_unlock (&np->lock);
@@ -1019,12 +1071,17 @@
 				if (desc_status & 0x0030) np->stats.rx_length_errors++;
 				if (desc_status & 0x0048) np->stats.rx_fifo_errors++;
 				if (desc_status & 0x0004) np->stats.rx_frame_errors++;
-				if (desc_status & 0x0002) np->stats.rx_crc_errors++;
+				if (desc_status & 0x0002) {
+					/* this can also be updated outside the interrupt handler */
+					spin_lock (&np->lock);
+					np->stats.rx_crc_errors++;
+					spin_unlock (&np->lock);
+				}
 			}
 		} else {
 			struct sk_buff *skb;
 			/* Length should omit the CRC */
-			u16 pkt_len = data_size - 4;
+			int pkt_len = data_size - 4;
 
 			/* Check if the packet is long enough to accept without copying
 			   to a minimally-sized skbuff. */
@@ -1034,7 +1091,11 @@
 				skb_reserve(skb, 2);	/* 16 byte align the IP header */
 				pci_dma_sync_single(np->pdev, np->rx_skbuff_dma[entry],
 						    np->rx_buf_sz, PCI_DMA_FROMDEVICE);
-#if ! defined(__alpha__) || USE_IP_COPYSUM		/* Avoid misaligned on Alpha */
+
+				/* *_IP_COPYSUM isn't defined anywhere and eth_copy_and_sum
+				   is memcpy for all archs so this is kind of pointless right
+				   now ... or? */
+#if HAS_IP_COPYSUM                     /* Call copy + cksum if available. */
 				eth_copy_and_sum(skb, np->rx_skbuff[entry]->tail, pkt_len, 0);
 				skb_put(skb, pkt_len);
 #else
@@ -1090,6 +1151,8 @@
 	struct netdev_private *np = (struct netdev_private *)dev->priv;
 	long ioaddr = dev->base_addr;
 
+	spin_lock (&np->lock);
+
 	if (intr_status & (IntrMIIChange | IntrLinkChange)) {
 		if (readb(ioaddr + MIIStatus) & 0x02)
 			/* Link failed, restart autonegotiation. */
@@ -1105,6 +1168,7 @@
 	if (intr_status & IntrStatsMax) {
 		np->stats.rx_crc_errors	+= readw(ioaddr + RxCRCErrs);
 		np->stats.rx_missed_errors	+= readw(ioaddr + RxMissed);
+		clear_tally_counters(ioaddr);
 	}
 	if (intr_status & IntrTxAbort) {
 		/* Stats counted in Tx-done handler, just restart Tx. */
@@ -1124,22 +1188,37 @@
 		/* Recovery for other fault sources not known. */
 		writew(CmdTxDemand | np->chip_cmd, dev->base_addr + ChipCmd);
 	}
+
+	spin_unlock (&np->lock);
 }
 
 static struct net_device_stats *via_rhine_get_stats(struct net_device *dev)
 {
 	struct netdev_private *np = (struct netdev_private *)dev->priv;
 	long ioaddr = dev->base_addr;
+	unsigned long flags;
 
-	/* Nominally we should lock this segment of code for SMP, although
-	   the vulnerability window is very small and statistics are
-	   non-critical. */
+	spin_lock_irqsave(&np->lock, flags);
 	np->stats.rx_crc_errors	+= readw(ioaddr + RxCRCErrs);
 	np->stats.rx_missed_errors	+= readw(ioaddr + RxMissed);
+	clear_tally_counters(ioaddr);
+	spin_unlock_irqrestore(&np->lock, flags);
 
 	return &np->stats;
 }
 
+/* Clears the "tally counters" for CRC errors and missed frames(?).
+   It has been reported that some chips need a write of 0 to clear
+   these, for others the counters are set to 1 when written to and
+   instead cleared when read. So we clear them both ways ... */
+static inline void clear_tally_counters(const long ioaddr)
+{
+	writel(0, ioaddr + RxMissed);
+	readw(ioaddr + RxCRCErrs);
+	readw(ioaddr + RxMissed);
+}
+
+
 /* The big-endian AUTODIN II ethernet CRC calculation.
    N.B. Do not use for bulk data, use a table-based routine instead.
    This is common code and should be moved to net/core/crc.c */
@@ -1193,23 +1272,34 @@
 
 static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
 {
+	struct netdev_private *np = (struct netdev_private *)dev->priv;
 	u16 *data = (u16 *)&rq->ifr_data;
+	unsigned long flags;
+	int retval;
+
+	spin_lock_irqsave(&np->lock, flags);
+	retval = 0;
 
 	switch(cmd) {
 	case SIOCDEVPRIVATE:		/* Get the address of the PHY in use. */
-		data[0] = ((struct netdev_private *)dev->priv)->phys[0] & 0x1f;
+		data[0] = np->phys[0] & 0x1f;
 		/* Fall Through */
 	case SIOCDEVPRIVATE+1:		/* Read the specified MII register. */
 		data[3] = mdio_read(dev, data[0] & 0x1f, data[1] & 0x1f);
-		return 0;
+		break;
 	case SIOCDEVPRIVATE+2:		/* Write the specified MII register */
-		if (!capable(CAP_NET_ADMIN))
-			return -EPERM;
+		if (!capable(CAP_NET_ADMIN)) {
+			retval = -EPERM;
+			break;
+		}
 		mdio_write(dev, data[0] & 0x1f, data[1] & 0x1f, data[2]);
-		return 0;
+		break;
 	default:
-		return -EOPNOTSUPP;
+		retval = -EOPNOTSUPP;
 	}
+
+	spin_unlock_irqrestore(&np->lock, flags);
+	return retval;
 }
 
 static int via_rhine_close(struct net_device *dev)
@@ -1217,6 +1307,9 @@
 	long ioaddr = dev->base_addr;
 	struct netdev_private *np = (struct netdev_private *)dev->priv;
 	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&np->lock, flags);
 
 	netif_stop_queue(dev);
 
@@ -1232,6 +1325,11 @@
 
 	del_timer(&np->timer);
 
+	spin_unlock_irqrestore(&np->lock, flags);
+
+	/* Make sure there is no irq-handler running on a different CPU. */
+	synchronize_irq();
+
 	free_irq(dev->irq, dev);
 
 	/* Free all the skbuffs in the Rx queue. */
@@ -1247,11 +1345,24 @@
 		np->rx_skbuff[i] = 0;
 	}
 
+	/* Free all the skbuffs in the Tx queue, and also any bounce buffers. */
 	for (i = 0; i < TX_RING_SIZE; i++) {
-		if (np->tx_skbuff[i])
+		np->tx_ring[i].tx_status = 0;
+		np->tx_ring[i].desc_length = cpu_to_le32(0x00e08000);
+		np->tx_ring[i].addr = cpu_to_le32(0xBADF00D0); /* An invalid address. */
+		if (np->tx_skbuff[i]) {
+			if (np->tx_skbuff_dma[i]) {
+				pci_unmap_single(np->pdev,
+								 np->tx_skbuff_dma[i],
+								 np->tx_skbuff[i]->len, PCI_DMA_TODEVICE);
+			}
 			dev_kfree_skb(np->tx_skbuff[i]);
+		}
 		np->tx_skbuff[i] = 0;
+		np->tx_buf[i] = 0;
 	}
+	pci_free_consistent(np->pdev, PKT_BUF_SZ * TX_RING_SIZE,
+						np->tx_bufs, np->tx_bufs_dma);
 
 	MOD_DEC_USE_COUNT;
 
@@ -1294,15 +1405,7 @@
 
 static int __init via_rhine_init (void)
 {
-	int rc;
-	
-	MOD_INC_USE_COUNT;
-	
-	rc = pci_module_init (&via_rhine_driver);
-	
-	MOD_DEC_USE_COUNT;
-	
-	return rc;
+	return pci_module_init (&via_rhine_driver);
 }
 
 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)