[PATCH] and enhancements to pcnet32.c for 2.6.3-rc2

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Attached is a collection of patches for the pcnet32 driver.

The maintainer has not responded to the following three patches.

28 Jan  Chinmay Albal, bug caused by pcnet32_watchdog and pcnet32_interrupt
running simultaneously on different CPUs, causing the wrong register(s) to
be read/written.  I have added locking to other places (ioctl) this was also
happening.
2 Feb   Jon Mason, adding vendor_id to pcnet32_pci_tbl.

I also submitted an enhancement 22 Dec and 19 Jan, to support PCI hot remove.
Donald Becker replied that the patch was insufficient for PCMCIA hot remove,
which is correct.  The fix, confirmed by Greg KH, is sufficient for PCI.
It has been tested on IA32 and PPC64 hardwre.  I haven't been able to find
a PCMCIA/cardbus adapter which uses the pcnet32 chip.


New functionality in this patch:
Jim Lewis added "ethtool -t" loopback test capability.
I have changed the driver to use generic_mii_ioctl, ethtool_ops, and
netif_msg_* for printk's.  

Bugs fixed:
- Several error paths didn't free allocated resources.
- The length for receive buffers was off by 2, both to the hardware and to
pci_[un]map_single.
- In the pcnet32_interrupt routine when the cur_tx 32-bit counter wrapped,
the dirty_tx value was never incremented, causing 79C975 and some other
adapters to drop old skb's and not unmap the buffers.  79C971 and 79C792
adapters would detect a hung transmit and reset.  
- If the driver had done a netif_stop_queue and then the counters wrapped
around (both dirty_tx and cur_tx were less than TX_RING_SIZE), on an IA32
system "dirty_tx > lp->cur_tx - TX_RING_SIZE + 2" would never be true, and
never call netif_wake_queue.

Tested on IA32 and PPC machines.


--- linux-2.6.3-rc2/drivers/net/pcnet32.c	Tue Feb 10 09:15:52 2004
+++ linux-2.6.3-rc2.p/drivers/net/pcnet32.c	Wed Feb 11 13:47:59 2004
@@ -22,8 +22,8 @@
  *************************************************************************/
 
 #define DRV_NAME	"pcnet32"
-#define DRV_VERSION	"1.27b"
-#define DRV_RELDATE	"01.10.2002"
+#define DRV_VERSION	"1.28"
+#define DRV_RELDATE	"02.11.2004"
 #define PFX		DRV_NAME ": "
 
 static const char *version =
@@ -58,6 +58,12 @@
 static struct pci_device_id pcnet32_pci_tbl[] = {
     { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LANCE_HOME, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
     { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LANCE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+    /*
+     * Adapters that were sold with IBM's RS/6000 or pSeries hardware have
+     * the incorrect vendor id.
+     */
+    { PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_AMD_LANCE, PCI_ANY_ID, PCI_ANY_ID, 
+	    PCI_CLASS_NETWORK_ETHERNET << 8, 0xffff00, 0 },
     { 0, }
 };
 
@@ -73,7 +79,7 @@
 
 
 
-static int pcnet32_debug = 1;
+static int pcnet32_debug = 0;
 static int tx_start = 1; /* Mapping -- 0:20, 1:64, 2:128, 3:~220 (depends on chip vers) */
 static int pcnet32vlb;	 /* check for VLB cards ? */
 
@@ -123,6 +129,10 @@
 static int options[MAX_UNITS];
 static int full_duplex[MAX_UNITS];
 
+static const char pcnet32_gstrings_test[][ETH_GSTRING_LEN] = {
+	"Loopback test  (offline)"
+};
+#define PCNET32_TEST_LEN (sizeof(pcnet32_gstrings_test) / ETH_GSTRING_LEN)
 /*
  *				Theory of Operation
  * 
@@ -213,6 +223,13 @@
  *	   clean up and using new mii module
  * v1.27b  Sep 30 2002 Kent Yoder <yoder1@us.ibm.com>
  * 	   Added timer for cable connection state changes.
+ * v1.28   11 Feb 2004 Don Fry <brazilnut@us.ibm.com>,
+ *	   Jon Mason <jonmason@us.ibm.com>, Chinmay Albal <albal@in.ibm.com>,
+ *	   Jim Lewis <jklewis@us.ibm.com>.
+ *	   Now uses ethtool_ops, netif_msg_*, and generic_mii_ioctl.
+ *	   Loopback test added.  Supports PCI (not PCMCIA) hot remove.
+ *	   Fixes "Bus master arbitration failure" and pci_[un]map_single
+ *	   length errors.
  */
 
 
@@ -319,8 +336,9 @@
 	dxsuflo:1,			/* disable transmit stop on uflo */
 	mii:1;				/* mii port available */
     struct net_device	*next;
-    struct mii_if_info mii_if;
+    struct mii_if_info	mii_if;
     struct timer_list	watchdog_timer;
+    u32			msg_enable;	/* debug message level */
 };
 
 static void pcnet32_probe_vlbus(void);
@@ -339,6 +357,11 @@
 static void pcnet32_watchdog(struct net_device *);
 static int mdio_read(struct net_device *dev, int phy_id, int reg_num);
 static void mdio_write(struct net_device *dev, int phy_id, int reg_num, int val);
+static void pcnet32_restart(struct net_device *dev, unsigned int csr0_bits);
+
+static void pcnet32_ethtool_test(struct net_device *dev,
+            struct ethtool_test *eth_test, u64 *data);
+static int pcnet32_loopback_test(struct net_device *dev, uint64_t *data1);
 
 enum pci_flags_bit {
     PCI_USES_IO=1, PCI_USES_MEM=2, PCI_USES_MASTER=4,
@@ -457,6 +480,259 @@
 };
 
 
+static int pcnet32_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct pcnet32_private *lp = dev->priv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&lp->lock, flags);
+	mii_ethtool_gset(&lp->mii_if, cmd);
+	spin_unlock_irqrestore(&lp->lock, flags);
+	return 0;
+}
+
+static int pcnet32_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct pcnet32_private *lp = dev->priv;
+	unsigned long flags;
+	int r;
+
+	spin_lock_irqsave(&lp->lock, flags);
+	r = mii_ethtool_sset(&lp->mii_if, cmd);
+	spin_unlock_irqrestore(&lp->lock, flags);
+	return r;
+}
+
+static void pcnet32_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
+{
+	struct pcnet32_private *lp = dev->priv;
+  
+	strcpy (info->driver, DRV_NAME);
+	strcpy (info->version, DRV_VERSION);
+	if (lp->pci_dev)
+		strcpy (info->bus_info, pci_name(lp->pci_dev));
+	else
+		sprintf(info->bus_info, "VLB 0x%lx", dev->base_addr);
+	info->testinfo_len = PCNET32_TEST_LEN;
+}
+
+static u32 pcnet32_get_link(struct net_device *dev)
+{
+	struct pcnet32_private *lp = dev->priv;
+	unsigned long flags;
+	int r;
+
+	spin_lock_irqsave(&lp->lock, flags);
+	r = mii_link_ok(&lp->mii_if);
+	spin_unlock_irqrestore(&lp->lock, flags);
+	return r;
+}
+
+static u32 pcnet32_get_msglevel(struct net_device *dev)
+{
+	struct pcnet32_private *lp = dev->priv;
+	return lp->msg_enable;
+}
+  
+static void pcnet32_set_msglevel(struct net_device *dev, u32 value)
+{
+	struct pcnet32_private *lp = dev->priv;
+	lp->msg_enable = value;
+}
+  
+static int pcnet32_nway_reset(struct net_device *dev)
+{
+	struct pcnet32_private *lp = dev->priv;
+	unsigned long flags;
+	int r;
+
+	spin_lock_irqsave(&lp->lock, flags);
+	r = mii_nway_restart(&lp->mii_if);
+	spin_unlock_irqrestore(&lp->lock, flags);
+	return r;
+}
+
+static void pcnet32_get_ringparam(struct net_device *dev, struct ethtool_ringparam *ering)
+{
+	struct pcnet32_private *lp = dev->priv;
+
+	ering->tx_max_pending = TX_RING_SIZE - 1;
+	ering->tx_pending = lp->cur_tx - lp->dirty_tx;
+	ering->rx_max_pending = RX_RING_SIZE - 1;
+	ering->rx_pending = lp->cur_rx & RX_RING_MOD_MASK;
+}
+
+static void pcnet32_get_strings(struct net_device *dev, u32 stringset, u8 *data)
+{
+	memcpy(data, pcnet32_gstrings_test, sizeof(pcnet32_gstrings_test));
+}
+
+static int pcnet32_self_test_count(struct net_device *dev)
+{
+	return PCNET32_TEST_LEN;
+}
+
+static void pcnet32_ethtool_test(struct net_device *dev, struct ethtool_test *test, u64 *data)
+{
+    struct pcnet32_private *lp = dev->priv;
+    int rc;
+
+    if (test->flags == ETH_TEST_FL_OFFLINE) {
+	rc = pcnet32_loopback_test(dev, data);
+	if (rc) {
+	    if (netif_msg_hw(lp))
+		printk(KERN_DEBUG "%s: Loopback test failed.\n", dev->name);
+	    test->flags |= ETH_TEST_FL_FAILED;
+	} else if (netif_msg_hw(lp))
+	    printk(KERN_DEBUG "%s: Loopback test passed.\n", dev->name);
+    } else
+	printk(KERN_DEBUG "%s: No tests to run (specify 'Offline' on ethtool).",	    dev->name);
+} /* end pcnet32_ethtool_test */
+
+static int pcnet32_loopback_test(struct net_device *dev, uint64_t *data1)
+{
+    struct pcnet32_private *lp = dev->priv;
+    struct pcnet32_access *a = &lp->a;	// access to registers
+    ulong ioaddr = dev->base_addr;	// card base I/O address
+    struct sk_buff *skb;		// sk buff
+    int x, y, i;			// counters
+    int numbuffs = 4;			// number of TX/RX buffers and descs
+    u16 status = 0x8300;		// TX ring status
+    int rc;				// return code
+    int size;				// size of packets
+    unsigned char *packet;		// source packet data
+    static int data_len = 60;		// length of source packets data field
+    unsigned long flags;
+
+    *data1 = 1;			// status of test, set to fail in case we abort
+    rc = 1;			// default to fail
+
+    spin_lock_irqsave(&lp->lock, flags);
+    lp->a.write_csr(ioaddr, 0, 0x7904);
+
+    del_timer_sync(&lp->watchdog_timer);
+
+    netif_stop_queue(dev);
+
+    pcnet32_restart(dev, 0x0000);	// purge & init rings but don't actually restart
+
+    lp->a.write_csr(ioaddr, 0, 0x0004);	// Set STOP bit
+
+    x = a->read_bcr(ioaddr, 32);	// set internal loopback in BSR32
+    x = x | 0x00000002;
+    a->write_bcr(ioaddr, 32, x);
+
+    /* Initialize Transmit buffers. */
+    size = data_len + 15;
+    for (x=0; x<numbuffs; x++) {
+	if (!(skb = dev_alloc_skb(size))) {	// freed later in pcnet32_purge_tx_ring
+	    printk(KERN_DEBUG "%s: Cannot allocate skb at line: %d!\n",
+		    dev->name, __LINE__);
+	    goto clean_up;
+	} else {
+	    packet = skb->data;
+	    skb_put(skb, size);		// create space for data
+	    lp->tx_skbuff[x] = skb;
+	    lp->tx_ring[x].length = le16_to_cpu(-skb->len);
+	    lp->tx_ring[x].misc = 0x00000000;
+
+	    // put DA and SA into the skb
+	    for (i=0; i<12; i++)
+		*packet++ = 0xff;
+	    // type
+	    *packet++ = 0x08;
+	    *packet++ = 0x06;
+	    // packet number
+	    *packet++ = x;
+	    // fill packet with data
+	    for (y=0; y<data_len; y++)
+		*packet++ = y;
+
+	    lp->tx_dma_addr[x] = pci_map_single(lp->pci_dev, skb->data, skb->len, PCI_DMA_TODEVICE);
+	    lp->tx_ring[x].base = (u32)le32_to_cpu(lp->tx_dma_addr[x]);
+	    wmb(); /* Make sure owner changes after all others are visible */
+	    lp->tx_ring[x].status = le16_to_cpu(status);
+	}
+    }
+
+    lp->a.write_csr(ioaddr, 0, 0x0002);	// Set STRT bit
+    spin_unlock_irqrestore(&lp->lock, flags);
+
+    mdelay(50);				// wait a bit
+
+    spin_lock_irqsave(&lp->lock, flags);
+    lp->a.write_csr(ioaddr, 0, 0x0004);	// Set STOP bit
+
+    if (netif_msg_hw(lp) && netif_msg_pktdata(lp)) {
+	printk(KERN_DEBUG "%s: RX loopback packets:\n", dev->name);
+
+	for (x=0; x<numbuffs; x++) {
+	    printk(KERN_DEBUG "%s: Packet %d:\n", dev->name, x);
+	    skb=lp->rx_skbuff[x];
+	    for (i=0; i<size; i++) {
+		printk("%02x ",*(skb->data+i));
+	    }
+	    printk("\n");
+	}
+    }
+
+    x = 0;
+    rc = 0;
+    while (x<numbuffs && !rc) {
+	skb = lp->rx_skbuff[x];
+	packet = lp->tx_skbuff[x]->data;
+	for (i=0; i<size; i++) {
+	    if (*(skb->data+i) != packet[i]) {
+		if (netif_msg_hw(lp))
+		    printk(KERN_DEBUG "%s: Error in compare! %2x - %02x %02x\n",
+			    dev->name, i, *(skb->data+i), packet[i]);
+		rc = 1;
+		break;
+	    }
+	}
+	x++;
+    }
+    if (!rc) {
+	*data1 = 0;
+    }
+
+clean_up:
+    x = a->read_csr(ioaddr,15) & 0xFFFF;
+    a->write_csr(ioaddr, 15, (x & ~0x0044));	// reset bits 6 and 2
+
+    x = a->read_bcr(ioaddr, 32);		// BCR32
+    x = x & ~0x00000002;
+    a->write_bcr(ioaddr, 32, x);
+
+    pcnet32_restart(dev, 0x0042);		// resume normal operation
+
+    netif_wake_queue(dev);
+
+    mod_timer(&(lp->watchdog_timer), PCNET32_WATCHDOG_TIMEOUT);
+
+    /* Clear interrupts, and set interrupt enable. */
+    lp->a.write_csr (ioaddr, 0, 0x7940);
+    spin_unlock_irqrestore(&lp->lock, flags);
+
+    return(rc);
+} /* end pcnet32_loopback_test  */
+
+static struct ethtool_ops pcnet32_ethtool_ops = {
+	.get_settings		= pcnet32_get_settings,
+	.set_settings		= pcnet32_set_settings,
+	.get_drvinfo		= pcnet32_get_drvinfo,
+	.get_msglevel		= pcnet32_get_msglevel,
+	.set_msglevel		= pcnet32_set_msglevel,
+	.nway_reset		= pcnet32_nway_reset,
+	.get_link		= pcnet32_get_link,
+	.get_ringparam		= pcnet32_get_ringparam,
+	.get_tx_csum		= ethtool_op_get_tx_csum,
+	.get_sg			= ethtool_op_get_sg,
+	.get_tso		= ethtool_op_get_tso,
+	.get_strings		= pcnet32_get_strings,
+	.self_test_count	= pcnet32_self_test_count,
+	.self_test		= pcnet32_ethtool_test,
+};
 
 /* only probes for non-PCI devices, the rest are handled by 
  * pci_register_driver via pcnet32_probe_pci */
@@ -546,10 +822,12 @@
     }
 
     chip_version = a->read_csr(ioaddr, 88) | (a->read_csr(ioaddr,89) << 16);
-    if (pcnet32_debug > 2)
+    if (pcnet32_debug & NETIF_MSG_PROBE)
 	printk(KERN_INFO "  PCnet chip version is %#x.\n", chip_version);
-    if ((chip_version & 0xfff) != 0x003)
+    if ((chip_version & 0xfff) != 0x003) {
+	    printk(KERN_INFO PFX "Unsupported chip version.\n");
 	    goto err_release_region;
+    }
     
     /* initialize variables */
     fdx = mii = fset = dxsuflo = ltint = 0;
@@ -600,7 +878,7 @@
 	media &= ~3;
 	media |= 1;
 #endif
-	if (pcnet32_debug > 2)
+	if (pcnet32_debug & NETIF_MSG_PROBE)
 	    printk(KERN_DEBUG PFX "media reset to %#x.\n",  media);
 	a->write_bcr(ioaddr, 49, media);
 	break;
@@ -631,6 +909,7 @@
     
     dev = alloc_etherdev(0);
     if(!dev) {
+	    printk(KERN_ERR PFX "Memory allocation failed.\n");
 	    ret = -ENOMEM;
 	    goto err_release_region;
     }
@@ -704,6 +983,7 @@
     dev->base_addr = ioaddr;
     /* pci_alloc_consistent returns page-aligned memory, so we do not have to check the alignment */
     if ((lp = pci_alloc_consistent(pdev, sizeof(*lp), &lp_dma_addr)) == NULL) {
+	printk("\n" KERN_ERR PFX "Consistent memory allocation failed.\n");
 	ret = -ENOMEM;
 	goto err_free_netdev;
     }
@@ -720,9 +1000,12 @@
     lp->name = chipname;
     lp->shared_irq = shared;
     lp->mii_if.full_duplex = fdx;
+    lp->mii_if.phy_id_mask = 0x1f;
+    lp->mii_if.reg_num_mask = 0x1f;
     lp->dxsuflo = dxsuflo;
     lp->ltint = ltint;
     lp->mii = mii;
+    lp->msg_enable = pcnet32_debug;
     if ((cards_found >= MAX_UNITS) || (options[cards_found] > sizeof(options_mapping)))
 	lp->options = PCNET32_PORT_ASEL;
     else
@@ -803,6 +1086,7 @@
     dev->get_stats = &pcnet32_get_stats;
     dev->set_multicast_list = &pcnet32_set_multicast_list;
     dev->do_ioctl = &pcnet32_ioctl;
+    dev->ethtool_ops = &pcnet32_ethtool_ops;
     dev->tx_timeout = pcnet32_tx_timeout;
     dev->watchdog_timeo = (5*HZ);
 
@@ -810,8 +1094,13 @@
     if (register_netdev(dev))
 	goto err_free_consistent;
 
-    lp->next = pcnet32_dev;
-    pcnet32_dev = dev;
+    if (pdev)
+	pci_set_drvdata(pdev, dev);
+    else {
+	lp->next = pcnet32_dev;
+	pcnet32_dev = dev;
+    }
+
     printk(KERN_INFO "%s: registered as %s\n",dev->name, lp->name);
     cards_found++;
     return 0;
@@ -833,6 +1122,7 @@
     unsigned long ioaddr = dev->base_addr;
     u16 val;
     int i;
+    int ret;
 
     if (dev->irq == 0 ||
 	request_irq(dev->irq, &pcnet32_interrupt,
@@ -841,8 +1131,10 @@
     }
 
     /* Check for a valid station address */
-    if( !is_valid_ether_addr(dev->dev_addr) )
-	return -EINVAL;
+    if( !is_valid_ether_addr(dev->dev_addr) ) {
+	ret = -EINVAL;
+	goto err_free_irq;
+    }
 
     /* Reset the PCNET32 */
     lp->a.reset (ioaddr);
@@ -850,7 +1142,7 @@
     /* switch pcnet32 to 32bit mode */
     lp->a.write_bcr (ioaddr, 20, 2);
 
-    if (pcnet32_debug > 1)
+    if (netif_msg_ifup(lp))
 	printk(KERN_DEBUG "%s: pcnet32_open() irq %d tx/rx rings %#x/%#x init %#x.\n",
 	       dev->name, dev->irq,
 	       (u32) (lp->dma_addr + offsetof(struct pcnet32_private, tx_ring)),
@@ -916,8 +1208,10 @@
     lp->init_block.mode = le16_to_cpu((lp->options & PCNET32_PORT_PORTSEL) << 7);
     lp->init_block.filter[0] = 0x00000000;
     lp->init_block.filter[1] = 0x00000000;
-    if (pcnet32_init_ring(dev))
-	return -ENOMEM;
+    if (pcnet32_init_ring(dev)) {
+	ret = -ENOMEM;
+	goto err_free_ring;
+    }
     
     /* Re-initialize the PCNET32, and start it when done. */
     lp->a.write_csr (ioaddr, 1, (lp->dma_addr + offsetof(struct pcnet32_private, init_block)) &0xffff);
@@ -944,13 +1238,35 @@
      */
     lp->a.write_csr (ioaddr, 0, 0x0042);
 
-    if (pcnet32_debug > 2)
+    if (netif_msg_ifup(lp))
 	printk(KERN_DEBUG "%s: pcnet32 open after %d ticks, init block %#x csr0 %4.4x.\n",
 	       dev->name, i, (u32) (lp->dma_addr + offsetof(struct pcnet32_private, init_block)),
 	       lp->a.read_csr(ioaddr, 0));
 
 
     return 0;	/* Always succeed */
+
+err_free_ring:
+    /* free any allocated skbuffs */
+    for (i = 0; i < RX_RING_SIZE; i++) {
+	lp->rx_ring[i].status = 0;			    
+	if (lp->rx_skbuff[i]) {
+            pci_unmap_single(lp->pci_dev, lp->rx_dma_addr[i], PKT_BUF_SZ-2,
+		    PCI_DMA_FROMDEVICE);
+	    dev_kfree_skb(lp->rx_skbuff[i]);
+        }
+	lp->rx_skbuff[i] = NULL;
+        lp->rx_dma_addr[i] = 0;
+    }
+    /*
+     * Switch back to 16bit mode to avoid problems with dumb 
+     * DOS packet driver after a warm reboot
+     */
+    lp->a.write_bcr (ioaddr, 20, 4);
+
+err_free_irq:
+    free_irq(dev->irq, dev);
+    return ret;
 }
 
 /*
@@ -1006,15 +1322,17 @@
 	}
 
 	if (lp->rx_dma_addr[i] == 0)
-		lp->rx_dma_addr[i] = pci_map_single(lp->pci_dev, rx_skbuff->tail, rx_skbuff->len, PCI_DMA_FROMDEVICE);
+		lp->rx_dma_addr[i] = pci_map_single(lp->pci_dev, rx_skbuff->tail, PKT_BUF_SZ-2, PCI_DMA_FROMDEVICE);
 	lp->rx_ring[i].base = (u32)le32_to_cpu(lp->rx_dma_addr[i]);
-	lp->rx_ring[i].buf_length = le16_to_cpu(-PKT_BUF_SZ);
+	lp->rx_ring[i].buf_length = le16_to_cpu(2-PKT_BUF_SZ);
+	wmb(); /* Make sure owner changes after all others are visible */
 	lp->rx_ring[i].status = le16_to_cpu(0x8000);
     }
     /* The Tx buffer address is filled in as needed, but we do need to clear
        the upper ownership bit. */
     for (i = 0; i < TX_RING_SIZE; i++) {
 	lp->tx_ring[i].base = 0;
+	wmb(); /* Make sure owner changes after all others are visible */
 	lp->tx_ring[i].status = 0;
         lp->tx_dma_addr[i] = 0;
     }
@@ -1061,7 +1379,7 @@
 	       dev->name, lp->a.read_csr(ioaddr, 0));
 	lp->a.write_csr (ioaddr, 0, 0x0004);
 	lp->stats.tx_errors++;
-	if (pcnet32_debug > 2) {
+	if (netif_msg_tx_err(lp)) {
 	    int i;
 	    printk(KERN_DEBUG " Ring data dump: dirty_tx %d cur_tx %d%s cur_rx %d.",
 	       lp->dirty_tx, lp->cur_tx, lp->tx_full ? " (full)" : "",
@@ -1079,7 +1397,7 @@
 	pcnet32_restart(dev, 0x0042);
 
 	dev->trans_start = jiffies;
-	netif_start_queue(dev);
+	netif_wake_queue(dev);
 
 	spin_unlock_irqrestore(&lp->lock, flags);
 }
@@ -1094,7 +1412,7 @@
     int entry;
     unsigned long flags;
 
-    if (pcnet32_debug > 3) {
+    if (netif_msg_tx_queued(lp)) {
 	printk(KERN_DEBUG "%s: pcnet32_start_xmit() called, csr0 %4.4x.\n",
 	       dev->name, lp->a.read_csr(ioaddr, 0));
     }
@@ -1105,9 +1423,10 @@
      * interrupt when that option is available to us.
      */
     status = 0x8300;
+    entry = (lp->cur_tx - lp->dirty_tx) & TX_RING_MOD_MASK; 
     if ((lp->ltint) &&
-	((lp->cur_tx - lp->dirty_tx == TX_RING_SIZE/2) ||
-	 (lp->cur_tx - lp->dirty_tx >= TX_RING_SIZE-2)))
+	((entry == TX_RING_SIZE/2) ||
+	 (entry >= TX_RING_SIZE-2)))
     {
 	/* Enable Successful-TxDone interrupt if we have
 	 * 1/2 of, or nearly all of, our ring buffer Tx'd
@@ -1122,7 +1441,7 @@
     /* Mask to ring buffer boundary. */
     entry = lp->cur_tx & TX_RING_MOD_MASK;
   
-    /* Caution: the write order is important here, set the base address
+    /* Caution: the write order is important here, set the status
        with the "ownership" bits last. */
 
     lp->tx_ring[entry].length = le16_to_cpu(-skb->len);
@@ -1143,9 +1462,9 @@
 
     dev->trans_start = jiffies;
 
-    if (lp->tx_ring[(entry+1) & TX_RING_MOD_MASK].base == 0)
-	netif_start_queue(dev);
-    else {
+    if (lp->tx_ring[(entry+1) & TX_RING_MOD_MASK].base == 0) {
+	netif_wake_queue(dev);
+    } else {
 	lp->tx_full = 1;
 	netif_stop_queue(dev);
     }
@@ -1182,7 +1501,7 @@
 
 	must_restart = 0;
 
-	if (pcnet32_debug > 5)
+	if (netif_msg_intr(lp))
 	    printk(KERN_DEBUG "%s: interrupt  csr0=%#2.2x new csr=%#2.2x.\n",
 		   dev->name, csr0, lp->a.read_csr (ioaddr, 0));
 
@@ -1191,8 +1510,9 @@
 
 	if (csr0 & 0x0200) {		/* Tx-done interrupt */
 	    unsigned int dirty_tx = lp->dirty_tx;
+	    int delta;
 
-	    while (dirty_tx < lp->cur_tx) {
+	    while (dirty_tx != lp->cur_tx) {
 		int entry = dirty_tx & TX_RING_MOD_MASK;
 		int status = (short)le16_to_cpu(lp->tx_ring[entry].status);
 			
@@ -1246,15 +1566,17 @@
 		dirty_tx++;
 	    }
 
-	    if (lp->cur_tx - dirty_tx >= TX_RING_SIZE) {
+	    delta = (lp->cur_tx - dirty_tx) & (TX_RING_MOD_MASK + TX_RING_SIZE);
+	    if (delta >= TX_RING_SIZE) {
 		printk(KERN_ERR "%s: out-of-sync dirty pointer, %d vs. %d, full=%d.\n",
 			dev->name, dirty_tx, lp->cur_tx, lp->tx_full);
 		dirty_tx += TX_RING_SIZE;
+		delta -= TX_RING_SIZE;
 	    }
 
 	    if (lp->tx_full &&
 		netif_queue_stopped(dev) &&
-		dirty_tx > lp->cur_tx - TX_RING_SIZE + 2) {
+		delta < TX_RING_SIZE - 2) {
 		/* The ring is no longer full, clear tbusy. */
 		lp->tx_full = 0;
 		netif_wake_queue (dev);
@@ -1294,7 +1616,7 @@
     lp->a.write_csr (ioaddr, 0, 0x7940);
     lp->a.write_rap (ioaddr,rap);
     
-    if (pcnet32_debug > 4)
+    if (netif_msg_intr(lp))
 	printk(KERN_DEBUG "%s: exiting interrupt, csr0=%#4.4x.\n",
 	       dev->name, lp->a.read_csr (ioaddr, 0));
 
@@ -1344,13 +1666,13 @@
 		    if ((newskb = dev_alloc_skb (PKT_BUF_SZ))) {
 			skb_reserve (newskb, 2);
 			skb = lp->rx_skbuff[entry];
-			pci_unmap_single(lp->pci_dev, lp->rx_dma_addr[entry], skb->len, PCI_DMA_FROMDEVICE);
+			pci_unmap_single(lp->pci_dev, lp->rx_dma_addr[entry], PKT_BUF_SZ-2, PCI_DMA_FROMDEVICE);
 			skb_put (skb, pkt_len);
 			lp->rx_skbuff[entry] = newskb;
 			newskb->dev = dev;
                         lp->rx_dma_addr[entry] = 
 				pci_map_single(lp->pci_dev, newskb->tail,
-					newskb->len, PCI_DMA_FROMDEVICE);
+					PKT_BUF_SZ-2, PCI_DMA_FROMDEVICE);
 			lp->rx_ring[entry].base = le32_to_cpu(lp->rx_dma_addr[entry]);
 			rx_in_place = 1;
 		    } else
@@ -1379,7 +1701,7 @@
 		    skb_put(skb,pkt_len);	/* Make room */
 		    pci_dma_sync_single(lp->pci_dev,
 		                        lp->rx_dma_addr[entry],
-		                        PKT_BUF_SZ,
+		                        PKT_BUF_SZ-2,
 		                        PCI_DMA_FROMDEVICE);
 		    eth_copy_and_sum(skb,
 				     (unsigned char *)(lp->rx_skbuff[entry]->tail),
@@ -1396,7 +1718,8 @@
 	 * The docs say that the buffer length isn't touched, but Andrew Boyd
 	 * of QNX reports that some revs of the 79C965 clear it.
 	 */
-	lp->rx_ring[entry].buf_length = le16_to_cpu(-PKT_BUF_SZ);
+	lp->rx_ring[entry].buf_length = le16_to_cpu(2-PKT_BUF_SZ);
+	wmb(); /* Make sure owner changes after all others are visible */
 	lp->rx_ring[entry].status |= le16_to_cpu(0x8000);
 	entry = (++lp->cur_rx) & RX_RING_MOD_MASK;
     }
@@ -1417,7 +1740,7 @@
 
     lp->stats.rx_missed_errors = lp->a.read_csr (ioaddr, 112);
 
-    if (pcnet32_debug > 1)
+    if (netif_msg_ifdown(lp))
 	printk(KERN_DEBUG "%s: Shutting down ethercard, status was %2.2x.\n",
 	       dev->name, lp->a.read_csr (ioaddr, 0));
 
@@ -1436,7 +1759,7 @@
     for (i = 0; i < RX_RING_SIZE; i++) {
 	lp->rx_ring[i].status = 0;			    
 	if (lp->rx_skbuff[i]) {
-            pci_unmap_single(lp->pci_dev, lp->rx_dma_addr[i], lp->rx_skbuff[i]->len, PCI_DMA_FROMDEVICE);
+            pci_unmap_single(lp->pci_dev, lp->rx_dma_addr[i], PKT_BUF_SZ-2, PCI_DMA_FROMDEVICE);
 	    dev_kfree_skb(lp->rx_skbuff[i]);
         }
 	lp->rx_skbuff[i] = NULL;
@@ -1571,147 +1894,60 @@
 	lp->a.write_bcr(ioaddr, 33, phyaddr);
 }
 
-static int pcnet32_ethtool_ioctl (struct net_device *dev, void *useraddr)
-{
-	struct pcnet32_private *lp = dev->priv;
-	u32 ethcmd;
-	int phyaddr = 0;
-	int phy_id = 0;
-	unsigned long ioaddr = dev->base_addr;
-
-	if (lp->mii) {
-		phyaddr = lp->a.read_bcr (ioaddr, 33);
-		phy_id = (phyaddr >> 5) & 0x1f;
-		lp->mii_if.phy_id = phy_id;
-	}
-
-	if (copy_from_user (&ethcmd, useraddr, sizeof (ethcmd)))
-		return -EFAULT;
-
-	switch (ethcmd) {
-	case ETHTOOL_GDRVINFO: {
-		struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO };
-		strcpy (info.driver, DRV_NAME);
-		strcpy (info.version, DRV_VERSION);
-		if (lp->pci_dev)
-			strcpy (info.bus_info, pci_name(lp->pci_dev));
-		else
-			sprintf(info.bus_info, "VLB 0x%lx", dev->base_addr);
-		if (copy_to_user (useraddr, &info, sizeof (info)))
-			return -EFAULT;
-		return 0;
-	}
-
-	/* get settings */
-	case ETHTOOL_GSET: {
-		struct ethtool_cmd ecmd = { ETHTOOL_GSET };
-		spin_lock_irq(&lp->lock);
-		mii_ethtool_gset(&lp->mii_if, &ecmd);
-		spin_unlock_irq(&lp->lock);
-		if (copy_to_user(useraddr, &ecmd, sizeof(ecmd)))
-			return -EFAULT;
-		return 0;
-	}
-	/* set settings */
-	case ETHTOOL_SSET: {
-		int r;
-		struct ethtool_cmd ecmd;
-		if (copy_from_user(&ecmd, useraddr, sizeof(ecmd)))
-			return -EFAULT;
-		spin_lock_irq(&lp->lock);
-		r = mii_ethtool_sset(&lp->mii_if, &ecmd);
-		spin_unlock_irq(&lp->lock);
-		return r;
-	}
-	/* restart autonegotiation */
-	case ETHTOOL_NWAY_RST: {
-		return mii_nway_restart(&lp->mii_if);
-	}
-	/* get link status */
-	case ETHTOOL_GLINK: {
-		struct ethtool_value edata = {ETHTOOL_GLINK};
-		edata.data = mii_link_ok(&lp->mii_if);
-		if (copy_to_user(useraddr, &edata, sizeof(edata)))
-			return -EFAULT;
-		return 0;
-	}
-
-	/* get message-level */
-	case ETHTOOL_GMSGLVL: {
-		struct ethtool_value edata = {ETHTOOL_GMSGLVL};
-		edata.data = pcnet32_debug;
-		if (copy_to_user(useraddr, &edata, sizeof(edata)))
-			return -EFAULT;
-		return 0;
-	}
-	/* set message-level */
-	case ETHTOOL_SMSGLVL: {
-		struct ethtool_value edata;
-		if (copy_from_user(&edata, useraddr, sizeof(edata)))
-			return -EFAULT;
-		pcnet32_debug = edata.data;
-		return 0;
-	}
-	default:
-		break;
-	}
-
-	return -EOPNOTSUPP;
-}
-
 static int pcnet32_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
 {
-    unsigned long ioaddr = dev->base_addr;
     struct pcnet32_private *lp = dev->priv;	 
     struct mii_ioctl_data *data = (struct mii_ioctl_data *)&rq->ifr_data;
-    int phyaddr = lp->a.read_bcr (ioaddr, 33);
+    int rc;
+    unsigned long flags;
 
-    if (cmd == SIOCETHTOOL)
-	return pcnet32_ethtool_ioctl(dev, (void *) rq->ifr_data);
+    /* all non-ethtool ioctls (the SIOC[GS]MIIxxx ioctls) */
+    spin_lock_irqsave(&lp->lock, flags);
+    rc = generic_mii_ioctl(&lp->mii_if, data, cmd, NULL);
+    spin_unlock_irqrestore(&lp->lock, flags);
 
-    if (lp->mii) {
-	switch(cmd) {
-	case SIOCGMIIPHY:		/* Get address of MII PHY in use. */
-	    data->phy_id = (phyaddr >> 5) & 0x1f;
-	    /* Fall Through */
-	case SIOCGMIIREG:		/* Read MII PHY register. */
-	    lp->a.write_bcr (ioaddr, 33, ((data->phy_id & 0x1f) << 5) | (data->reg_num & 0x1f));
-	    data->val_out = lp->a.read_bcr (ioaddr, 34);
-	    lp->a.write_bcr (ioaddr, 33, phyaddr);
-	    return 0;
-	case SIOCSMIIREG:		/* Write MII PHY register. */
-	    if (!capable(CAP_NET_ADMIN))
-		return -EPERM;
-	    lp->a.write_bcr (ioaddr, 33, ((data->phy_id & 0x1f) << 5) | (data->reg_num & 0x1f));
-	    lp->a.write_bcr (ioaddr, 34, data->val_in);
-	    lp->a.write_bcr (ioaddr, 33, phyaddr);
-	    return 0;
-	default:
-	    return -EOPNOTSUPP;
-	}
-    }
-    return -EOPNOTSUPP;
+    return rc;
 }
 
 static void pcnet32_watchdog(struct net_device *dev)
 {
     struct pcnet32_private *lp = dev->priv;
+    unsigned long flags;
 
     /* Print the link status if it has changed */
-    if (lp->mii)
+    if (lp->mii)  {
+	spin_lock_irqsave(&lp->lock, flags);
 	mii_check_media (&lp->mii_if, 1, 0);
+	spin_unlock_irqrestore(&lp->lock, flags);
+    }
 
     mod_timer (&(lp->watchdog_timer), PCNET32_WATCHDOG_TIMEOUT);
 }
 
+static void __devexit pcnet32_remove_one(struct pci_dev *pdev)
+{
+    struct net_device *dev = pci_get_drvdata(pdev);
+
+    if (dev) {
+	struct pcnet32_private *lp = dev->priv;
+
+	unregister_netdev(dev);
+	release_region(dev->base_addr, PCNET32_TOTAL_SIZE);
+	pci_free_consistent(lp->pci_dev, sizeof(*lp), lp, lp->dma_addr);
+	free_netdev(dev);
+	pci_set_drvdata(pdev, NULL);
+    }
+}
+
 static struct pci_driver pcnet32_driver = {
     .name	= DRV_NAME,
     .probe	= pcnet32_probe_pci,
+    .remove	= __devexit_p(pcnet32_remove_one),
     .id_table	= pcnet32_pci_tbl,
 };
 
 MODULE_PARM(debug, "i");
-MODULE_PARM_DESC(debug, DRV_NAME " debug level (0-6)");
+MODULE_PARM_DESC(debug, DRV_NAME " debug level");
 MODULE_PARM(max_interrupt_work, "i");
 MODULE_PARM_DESC(max_interrupt_work, DRV_NAME " maximum events handled per interrupt");  
 MODULE_PARM(rx_copybreak, "i");
-- 
Don Fry
brazilnut@us.ibm.com
-
: send the line "unsubscribe linux-net" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux 802.1Q VLAN]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Git]     [Bugtraq]     [Yosemite News and Information]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux PCI]     [Linux Admin]     [Samba]

  Powered by Linux