I'll send the attached patch to Alan soon, please test it.
Main changes:
* powerpc support added. Tested with ppc 603e.
* memory leak in _close fixed.
* initial power management support
* ethtool support merged
* SMP synchronization updated.
* improved link detection. This change could cause problems, but it's
required for proper switching between nway and pre-nway link partners,
at least with my PHY.
Tested with -ac11, patch also applies against 2.4.4 and 2.4.4-ac17.
--
Manfred
--- 2.4/drivers/net/winbond-840.c Fri May 25 22:36:10 2001
+++ build-2.4/drivers/net/winbond-840.c Fri May 25 23:23:07 2001
@@ -32,12 +32,22 @@
synchronize tx_q_bytes
software reset in tx_timeout
Copyright (C) 2000 Manfred Spraul
+ * further cleanups
+ power management.
+ support for big endian descriptors
+ ethtool ioctl merged from gkernel.
+ Copyright (c) 2001 Manfred Spraul
TODO:
- * according to the documentation, the chip supports big endian
- descriptors. Remove cpu_to_le32, enable BE descriptors.
+ * enable pci_power_off
+ * Wake-On-LAN
*/
+#define DRV_NAME "winbond-840"
+#define DRV_VERSION "1.01-b"
+#define DRV_RELDATE "5/15/2000"
+
+
/* Automatically extracted configuration info:
probe-func: winbond840_probe
config-in: tristate 'Winbond W89c840 Ethernet support' CONFIG_WINBOND_840
@@ -79,10 +89,13 @@
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 16
+#define TX_RING_SIZE 16
#define TX_QUEUE_LEN 10 /* Limit ring entries actually used. */
+#define TX_QUEUE_LEN_RESTART 5
#define RX_RING_SIZE 32
+#define TX_BUFLIMIT (1024-128)
+
/* The presumed FIFO size for working around the Tx-FIFO-overflow bug.
To avoid overflowing we don't queue again until we have room for a
full-size packet.
@@ -90,7 +103,6 @@
#define TX_FIFO_SIZE (2048)
#define TX_BUG_FIFO_LIMIT (TX_FIFO_SIZE-1514-16)
-#define TX_BUFLIMIT (1024-128)
/* Operational parameters that usually are not changed. */
/* Time in jiffies before concluding the transmitter is hung. */
@@ -122,13 +134,17 @@
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/delay.h>
+#include <linux/rtnetlink.h>
+#include <linux/ethtool.h>
+#include <asm/uaccess.h>
#include <asm/processor.h> /* Processor type for cache alignment. */
#include <asm/bitops.h>
#include <asm/io.h>
+#include <asm/irq.h>
/* These identify the driver base version and may not be removed. */
static char version[] __devinitdata =
-KERN_INFO "winbond-840.c:v1.01 (2.4 port) 5/15/2000 Donald Becker
<[EMAIL PROTECTED]>\n"
+KERN_INFO DRV_NAME ".c:v" DRV_VERSION DRV_RELDATE " Donald Becker
+<[EMAIL PROTECTED]>\n"
KERN_INFO " http://www.scyld.com/network/drivers.html\n";
MODULE_AUTHOR("Donald Becker <[EMAIL PROTECTED]>");
@@ -183,6 +199,8 @@
correctly detect a full FIFO, and queuing more than 2048 bytes may result in
silent data corruption.
+Test with 'ping -s 10000' on a fast computer.
+
*/
@@ -198,7 +216,7 @@
PCI_ADDR_64BITS=0x100, PCI_NO_ACPI_WAKE=0x200, PCI_NO_MIN_LATENCY=0x400,
};
enum chip_capability_flags {
- CanHaveMII=1, HasBrokenTx=2, AlwaysFDX=4, FDXOnNoMII=8,};
+ CanHaveMII=1, AlwaysFDX=2, FDXOnNoMII=4};
#ifdef USE_IO_OPS
#define W840_FLAGS (PCI_USES_IO | PCI_ADDR0 | PCI_USES_MASTER)
#else
@@ -226,11 +244,11 @@
static struct pci_id_info pci_id_tbl[] = {
{"Winbond W89c840", /* Sometime a Level-One switch card. */
{ 0x08401050, 0xffffffff, 0x81530000, 0xffff0000 },
- W840_FLAGS, 128, CanHaveMII | HasBrokenTx | FDXOnNoMII},
+ W840_FLAGS, 128, CanHaveMII | FDXOnNoMII},
{"Winbond W89c840", { 0x08401050, 0xffffffff, },
- W840_FLAGS, 128, CanHaveMII | HasBrokenTx},
+ W840_FLAGS, 128, CanHaveMII },
{"Compex RL100-ATX", { 0x201111F6, 0xffffffff,},
- W840_FLAGS, 128, CanHaveMII | HasBrokenTx},
+ W840_FLAGS, 128, CanHaveMII },
{0,}, /* 0 terminated list. */
};
@@ -302,7 +320,7 @@
struct w840_tx_desc {
s32 status;
s32 length;
- u32 buffer1, buffer2; /* We use only buffer 1. */
+ u32 buffer1, buffer2;
};
/* Bits in network_desc.status */
@@ -335,30 +353,31 @@
unsigned int cur_rx, dirty_rx; /* Producer/consumer ring indices */
unsigned int rx_buf_sz; /* Based on MTU+slack. */
unsigned int cur_tx, dirty_tx;
- int tx_q_bytes;
- unsigned int tx_full:1; /* The Tx queue is full. */
+ unsigned int tx_q_bytes;
+ unsigned int tx_full; /* 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. */
+ int dev_suspended;
/* MII transceiver section. */
int mii_cnt; /* MII device
addresses. */
u16 advertising; /* NWay media
advertisement */
unsigned char phys[MII_CNT]; /* MII device addresses, but only the
first is used */
+ u32 mii;
};
static int eeprom_read(long ioaddr, int location);
static int mdio_read(struct net_device *dev, int phy_id, int location);
static void mdio_write(struct net_device *dev, int phy_id, int location, int value);
static int netdev_open(struct net_device *dev);
-static void check_duplex(struct net_device *dev);
+static int update_link(struct net_device *dev);
static void netdev_timer(unsigned long data);
static void init_rxtx_rings(struct net_device *dev);
static void free_rxtx_rings(struct netdev_private *np);
static void init_registers(struct net_device *dev);
static void tx_timeout(struct net_device *dev);
-static int alloc_ring(struct net_device *dev);
+static int alloc_ringdesc(struct net_device *dev);
+static void free_ringdesc(struct netdev_private *np);
static int start_tx(struct sk_buff *skb, struct net_device *dev);
static void intr_handler(int irq, void *dev_instance, struct pt_regs *regs);
static void netdev_error(struct net_device *dev, int intr_status);
@@ -366,7 +385,7 @@
static inline unsigned ether_crc(int length, unsigned char *data);
static void set_rx_mode(struct net_device *dev);
static struct net_device_stats *get_stats(struct net_device *dev);
-static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
static int netdev_close(struct net_device *dev);
@@ -399,7 +418,7 @@
return -ENOMEM;
SET_MODULE_OWNER(dev);
- if (pci_request_regions(pdev, "winbond-840"))
+ if (pci_request_regions(pdev, DRV_NAME))
goto err_out_netdev;
#ifdef USE_IO_OPS
@@ -411,7 +430,6 @@
goto err_out_free_res;
#endif
- /* Warning: broken for big-endian machines. */
for (i = 0; i < 3; i++)
((u16 *)dev->dev_addr)[i] = le16_to_cpu(eeprom_read(ioaddr, i));
@@ -427,6 +445,7 @@
np->chip_id = chip_idx;
np->drv_flags = pci_id_tbl[chip_idx].drv_flags;
spin_lock_init(&np->lock);
+ np->dev_suspended = 0;
pci_set_drvdata(pdev, dev);
@@ -437,9 +456,9 @@
if (option > 0) {
if (option & 0x200)
np->full_duplex = 1;
- np->default_port = option & 15;
- if (np->default_port)
- np->medialock = 1;
+ if (option & 15)
+ printk(KERN_INFO "%s: ignoring user supplied media type %d",
+ dev->name, option & 15);
}
if (find_cnt < MAX_UNITS && full_duplex[find_cnt] > 0)
np->full_duplex = 1;
@@ -453,7 +472,7 @@
dev->stop = &netdev_close;
dev->get_stats = &get_stats;
dev->set_multicast_list = &set_rx_mode;
- dev->do_ioctl = &mii_ioctl;
+ dev->do_ioctl = &netdev_ioctl;
dev->tx_timeout = &tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;
@@ -474,9 +493,11 @@
if (mii_status != 0xffff && mii_status != 0x0000) {
np->phys[phy_idx++] = phy;
np->advertising = mdio_read(dev, phy, 4);
- printk(KERN_INFO "%s: MII PHY found at address %d,
status "
+ np->mii = (mdio_read(dev, phy, 2) << 16)+
+ mdio_read(dev, phy, 3);
+ printk(KERN_INFO "%s: MII PHY %8.8xh found at address
+%d, status "
"0x%4.4x advertising %4.4x.\n",
- dev->name, phy, mii_status,
np->advertising);
+ dev->name, np->mii, phy, mii_status,
+np->advertising);
}
}
np->mii_cnt = phy_idx;
@@ -667,7 +688,7 @@
printk(KERN_DEBUG "%s: w89c840_open() irq %d.\n",
dev->name, dev->irq);
- if((i=alloc_ring(dev)))
+ if((i=alloc_ringdesc(dev)))
return i;
init_registers(dev);
@@ -678,7 +699,7 @@
/* Set the timer to check for link beat. */
init_timer(&np->timer);
- np->timer.expires = jiffies + 3*HZ;
+ np->timer.expires = jiffies + 1*HZ;
np->timer.data = (unsigned long)dev;
np->timer.function = &netdev_timer; /* timer
handler */
add_timer(&np->timer);
@@ -686,25 +707,107 @@
return 0;
}
-static void check_duplex(struct net_device *dev)
+#define MII_DAVICOM_DM9101 0x0181b800
+
+static int update_link(struct net_device *dev)
{
struct netdev_private *np = dev->priv;
- int mii_reg5 = mdio_read(dev, np->phys[0], 5);
- int negotiated = mii_reg5 & np->advertising;
- int duplex;
+ int duplex, fasteth, result, mii_reg;
- if (np->duplex_lock || mii_reg5 == 0xffff)
- return;
- duplex = (negotiated & 0x0100) || (negotiated & 0x01C0) == 0x0040;
- if (np->full_duplex != duplex) {
- np->full_duplex = duplex;
+ /* BSMR */
+ mii_reg = mdio_read(dev, np->phys[0], 1);
+
+ if (mii_reg == 0xffff)
+ return np->csr6;
+ /* reread: the link status bit is sticky */
+ mii_reg = mdio_read(dev, np->phys[0], 1);
+ if (!(mii_reg & 0x4)) {
+ if (netif_carrier_ok(dev)) {
+ if (debug)
+ printk(KERN_INFO "%s: MII #%d reports no link.
+Disabling watchdog.\n",
+ dev->name, np->phys[0]);
+ netif_carrier_off(dev);
+ }
+ return np->csr6;
+ }
+ if (!netif_carrier_ok(dev)) {
if (debug)
- printk(KERN_INFO "%s: Setting %s-duplex based on MII #%d "
- "negotiated capability %4.4x.\n", dev->name,
- duplex ? "full" : "half", np->phys[0], negotiated);
- np->csr6 &= ~0x200;
- np->csr6 |= duplex ? 0x200 : 0;
+ printk(KERN_INFO "%s: MII #%d link is back. Enabling
+watchdog.\n",
+ dev->name, np->phys[0]);
+ netif_carrier_on(dev);
}
+
+ if ((np->mii & ~0xf) == MII_DAVICOM_DM9101) {
+ /* If the link partner doesn't support autonegotiation
+ * the MII detects it's abilities with the "parallel detection".
+ * Some MIIs update the LPA register to the result of the parallel
+ * detection, some don't.
+ * The Davicom PHY [at least 0181b800] doesn't.
+ * Instead bit 9 and 13 of the BMCR are updated to the result
+ * of the negotiation..
+ */
+ mii_reg = mdio_read(dev, np->phys[0], 0);
+ duplex = mii_reg & 0x100;
+ fasteth = mii_reg & 0x2000;
+ } else {
+ int negotiated;
+ mii_reg = mdio_read(dev, np->phys[0], 5);
+ negotiated = mii_reg & np->advertising;
+
+ duplex = (negotiated & 0x0100) || ((negotiated & 0x02C0) == 0x0040);
+ fasteth = negotiated & 0x380;
+ }
+ duplex |= np->duplex_lock;
+ /* remove fastether and fullduplex */
+ result = np->csr6 & ~0x20000200;
+ if (duplex)
+ result |= 0x200;
+ if (fasteth)
+ result |= 0x20000000;
+ if (result != np->csr6 && debug)
+ printk(KERN_INFO "%s: Setting %dMBit-%s-duplex based on MII#%d\n",
+ dev->name, fasteth ? 100 : 10,
+ duplex ? "full" : "half", np->phys[0]);
+ return result;
+}
+
+#define RXTX_TIMEOUT 2000
+static inline void update_csr6(struct net_device *dev, int new)
+{
+ struct netdev_private *np = dev->priv;
+ long ioaddr = dev->base_addr;
+ int limit = RXTX_TIMEOUT;
+
+ if (new==np->csr6)
+ return;
+ /* stop both Tx and Rx processes */
+ writel(np->csr6 & ~0x2002, ioaddr + NetworkConfig);
+ /* wait until they have really stopped */
+ for (;;) {
+ int csr5 = readl(ioaddr + IntrStatus);
+ int t;
+
+ t = (csr5 >> 17) & 0x07;
+ if (t==0||t==1) {
+ /* rx stopped */
+ t = (csr5 >> 20) & 0x07;
+ if (t==0||t==1)
+ break;
+ }
+
+ limit--;
+ if(!limit) {
+ printk(KERN_INFO "%s: couldn't stop rxtx, IntrStatus %xh.\n",
+ dev->name, csr5);
+ break;
+ }
+ udelay(1);
+ }
+ np->csr6 = new;
+ /* and restart them with the new configuration */
+ writel(np->csr6, ioaddr + NetworkConfig);
+ if (new & 0x200)
+ np->full_duplex = 1;
}
static void netdev_timer(unsigned long data)
@@ -712,8 +815,6 @@
struct net_device *dev = (struct net_device *)data;
struct netdev_private *np = dev->priv;
long ioaddr = dev->base_addr;
- int next_tick = 10*HZ;
- int old_csr6 = np->csr6;
if (debug > 2)
printk(KERN_DEBUG "%s: Media selection timer tick, status %8.8x "
@@ -721,13 +822,9 @@
dev->name, (int)readl(ioaddr + IntrStatus),
(int)readl(ioaddr + NetworkConfig));
spin_lock_irq(&np->lock);
- check_duplex(dev);
- if (np->csr6 != old_csr6) {
- writel(np->csr6 & ~0x0002, ioaddr + NetworkConfig);
- writel(np->csr6 | 0x2002, ioaddr + NetworkConfig);
- }
+ update_csr6(dev, update_link(dev));
spin_unlock_irq(&np->lock);
- np->timer.expires = jiffies + next_tick;
+ np->timer.expires = jiffies + 10*HZ;
add_timer(&np->timer);
}
@@ -741,12 +838,12 @@
/* Initial all Rx descriptors. */
for (i = 0; i < RX_RING_SIZE; i++) {
- np->rx_ring[i].length = cpu_to_le32(np->rx_buf_sz);
+ np->rx_ring[i].length = np->rx_buf_sz;
np->rx_ring[i].status = 0;
np->rx_skbuff[i] = 0;
}
/* Mark the last entry as wrapping the ring. */
- np->rx_ring[i-1].length |= cpu_to_le32(DescEndRing);
+ np->rx_ring[i-1].length |= DescEndRing;
/* Fill in the Rx buffers. Handle allocation failure gracefully. */
for (i = 0; i < RX_RING_SIZE; i++) {
@@ -758,8 +855,8 @@
np->rx_addr[i] = pci_map_single(np->pci_dev,skb->tail,
skb->len,PCI_DMA_FROMDEVICE);
- np->rx_ring[i].buffer1 = cpu_to_le32(np->rx_addr[i]);
- np->rx_ring[i].status = cpu_to_le32(DescOwn);
+ np->rx_ring[i].buffer1 = np->rx_addr[i];
+ np->rx_ring[i].status = DescOwn;
}
np->cur_rx = 0;
@@ -816,6 +913,14 @@
writeb(dev->dev_addr[i], ioaddr + StationAddr + i);
/* Initialize other registers. */
+#ifdef __BIG_ENDIAN
+ i = (1<<20); /* Big-endian descriptors */
+#else
+ i = 0;
+#endif
+ i |= (0x04<<2); /* skip length 4 u32 */
+ i |= 0x02; /* give Rx priority */
+
/* Configure the PCI bus bursts and FIFO thresholds.
486: Set 8 longword cache alignment, 8 longword burst.
586: Set 16 longword cache alignment, no burst limit.
@@ -823,43 +928,46 @@
0000 <not allowed> 0000 align to cache 0800 8
longwords
4000 8 longwords 0100 1 longword 1000 16
longwords
8000 16 longwords 0200 2 longwords 2000 32
longwords
- C000 32 longwords 0400 4 longwords
- Wait the specified 50 PCI cycles after a reset by initializing
- Tx and Rx queues and the address filter list. */
-#if defined(__powerpc__) /* Big-endian */
- writel(0x00100080 | 0xE010, ioaddr + PCIBusCfg);
-#elif defined(__alpha__)
- writel(0xE010, ioaddr + PCIBusCfg);
-#elif defined(__i386__)
-#if defined(MODULE)
- writel(0xE010, ioaddr + PCIBusCfg);
+ C000 32 longwords 0400 4 longwords */
+
+#if 1
+ /* certain burst/cache alignment settings cause
+ * data corruptions.
+ */
+ i |= 0x1000;
#else
+#if defined (__i386__) && !defined(MODULE)
/* When not a module we can work around broken '486 PCI boards. */
-#define x86 boot_cpu_data.x86
- writel((x86 <= 4 ? 0x4810 : 0xE010), ioaddr + PCIBusCfg);
- if (x86 <= 4)
+ if (boot_cpu_data.x86 <= 4) {
+ i |= 0x4800;
printk(KERN_INFO "%s: This is a 386/486 PCI system, setting cache "
- "alignment to %x.\n", dev->name,
- (x86 <= 4 ? 0x4810 : 0xE010));
-#endif
+ "alignment to 8 longwords.\n", dev->name);
+ } else {
+ i |= 0xE000;
+ }
+#elif defined(__powerpc__) || defined(__i386__) || defined(__alpha) ||
+defined(__ia64__)
+ i |= 0xE000;
#else
- writel(0xE010, ioaddr + PCIBusCfg);
-#warning Processor architecture undefined!
+#warning Processor architecture undefined
+ i |= 0x4800;
#endif
+#endif
+ writel(i, ioaddr + PCIBusCfg);
- if (dev->if_port == 0)
- dev->if_port = np->default_port;
-
- /* Fast Ethernet; 128 byte Tx threshold;
+ /* 128 byte Tx threshold;
Transmit on; Receive on; */
- np->csr6 = 0x20022002;
- check_duplex(dev);
- set_rx_mode(dev);
- writel(0, ioaddr + RxStartDemand);
+ np->csr6 = 0x00002002;
+ np->csr6 = update_link(dev);
/* Clear and Enable interrupts by setting the interrupt mask. */
+ /* But defer interrupt processing until everything is set up */
+ disable_irq(dev->irq);
writel(0x1A0F5, ioaddr + IntrStatus);
writel(0x1A0F5, ioaddr + IntrEnable);
+ set_rx_mode(dev);
+
+ enable_irq(dev->irq);
+ writel(0, ioaddr + RxStartDemand);
}
@@ -883,35 +991,40 @@
printk("\n");
}
printk(KERN_DEBUG "Tx cur %d Tx dirty %d Tx Full %d, q bytes %d.\n",
- np->cur_tx, np->dirty_tx, np->tx_full,np->tx_q_bytes);
+ np->cur_tx, np->dirty_tx, np->tx_full, np->tx_q_bytes);
printk(KERN_DEBUG "Tx Descriptor addr %xh.\n",readl(ioaddr+0x4C));
#endif
- spin_lock_irq(&np->lock);
/*
* Under high load dirty_tx and the internal tx descriptor pointer
* come out of sync, thus perform a software reset and reinitialize
* everything.
*/
+ disable_irq(dev->irq);
+ del_timer_sync(&np->timer);
+ spin_lock(&np->lock);
writel(1, dev->base_addr+PCIBusCfg);
+ readl(dev->base_addr+PCIBusCfg);
udelay(1);
free_rxtx_rings(np);
init_rxtx_rings(dev);
init_registers(dev);
- set_rx_mode(dev);
- spin_unlock_irq(&np->lock);
+ spin_unlock(&np->lock);
+ enable_irq(dev->irq);
netif_wake_queue(dev);
dev->trans_start = jiffies;
np->stats.tx_errors++;
- return;
+
+ np->timer.expires = jiffies + 1*HZ;
+ add_timer(&np->timer);
}
/* Initialize the Rx and Tx rings, along with various 'dev' bits. */
-static int alloc_ring(struct net_device *dev)
+static int alloc_ringdesc(struct net_device *dev)
{
struct netdev_private *np = dev->priv;
@@ -927,12 +1040,19 @@
return 0;
}
+static void free_ringdesc(struct netdev_private *np)
+{
+ pci_free_consistent(np->pci_dev,
+ sizeof(struct w840_rx_desc)*RX_RING_SIZE +
+ sizeof(struct w840_tx_desc)*TX_RING_SIZE,
+ np->rx_ring, np->ring_dma_addr);
+
+}
static int start_tx(struct sk_buff *skb, struct net_device *dev)
{
struct netdev_private *np = dev->priv;
unsigned entry;
- int len1, len2;
/* Caution: the write order is important here, set the field
with the "ownership" bits last. */
@@ -940,48 +1060,49 @@
/* Calculate the next Tx descriptor entry. */
entry = np->cur_tx % TX_RING_SIZE;
- np->tx_skbuff[entry] = skb;
np->tx_addr[entry] = pci_map_single(np->pci_dev,
skb->data,skb->len, PCI_DMA_TODEVICE);
- np->tx_ring[entry].buffer1 = cpu_to_le32(np->tx_addr[entry]);
- len2 = 0;
- len1 = skb->len;
- if(len1 > TX_BUFLIMIT) {
- len1 = TX_BUFLIMIT;
- len2 = skb->len-len1;
- np->tx_ring[entry].buffer2 =
cpu_to_le32(np->tx_addr[entry]+TX_BUFLIMIT);
- }
- np->tx_ring[entry].length = cpu_to_le32(DescWholePkt | (len2 << 11) | len1);
- if (entry >= TX_RING_SIZE-1) /* Wrap ring */
- np->tx_ring[entry].length |= cpu_to_le32(DescIntr | DescEndRing);
- np->cur_tx++;
+ np->tx_skbuff[entry] = skb;
+
+ np->tx_ring[entry].buffer1 = np->tx_addr[entry];
+ if (skb->len < TX_BUFLIMIT) {
+ np->tx_ring[entry].length = DescWholePkt | skb->len;
+ } else {
+ int len = skb->len - TX_BUFLIMIT;
+
+ np->tx_ring[entry].buffer2 = np->tx_addr[entry]+TX_BUFLIMIT;
+ np->tx_ring[entry].length = DescWholePkt | (len << 11) | TX_BUFLIMIT;
+ }
+ if(entry == TX_RING_SIZE-1)
+ np->tx_ring[entry].length |= DescEndRing;
- /* The spinlock protects against 2 races:
- * - tx_q_bytes is updated by this function and intr_handler
- * - our hardware is extremely fast and finishes the packet between
- * our check for "queue full" and netif_stop_queue.
- * Thus setting DescOwn and netif_stop_queue must be atomic.
+ /* Now acquire the irq spinlock.
+ * The difficult race is the the ordering between
+ * increasing np->cur_tx and setting DescOwn:
+ * - if np->cur_tx is increased first the interrupt
+ * handler could consider the packet as transmitted
+ * since DescOwn is cleared.
+ * - If DescOwn is set first the NIC could report the
+ * packet as sent, but the interrupt handler would ignore it
+ * since the np->cur_tx was not yet increased.
*/
spin_lock_irq(&np->lock);
+ np->cur_tx++;
wmb(); /* flush length, buffer1, buffer2 */
- np->tx_ring[entry].status = cpu_to_le32(DescOwn);
+ np->tx_ring[entry].status = DescOwn;
wmb(); /* flush status and kick the hardware */
writel(0, dev->base_addr + TxStartDemand);
-
np->tx_q_bytes += skb->len;
- /* Work around horrible bug in the chip by marking the queue as full
- when we do not have FIFO room for a maximum sized packet. */
- if (np->cur_tx - np->dirty_tx > TX_QUEUE_LEN)
- np->tx_full = 1;
- else if ((np->drv_flags & HasBrokenTx)
- && np->tx_q_bytes > TX_BUG_FIFO_LIMIT)
- np->tx_full = 1;
- if (np->tx_full)
+ if (np->cur_tx - np->dirty_tx > TX_QUEUE_LEN ||
+ np->tx_q_bytes > TX_BUG_FIFO_LIMIT) {
netif_stop_queue(dev);
+ wmb();
+ np->tx_full = 1;
+ }
+ spin_unlock_irq(&np->lock);
dev->trans_start = jiffies;
- spin_unlock_irq(&np->lock);
if (debug > 4) {
printk(KERN_DEBUG "%s: Transmit frame #%d queued in slot %d.\n",
@@ -990,6 +1111,62 @@
return 0;
}
+static void netdev_tx_done(struct net_device *dev)
+{
+ struct netdev_private *np = dev->priv;
+ for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) {
+ int entry = np->dirty_tx % TX_RING_SIZE;
+ int tx_status = np->tx_ring[entry].status;
+
+ if (tx_status < 0)
+ break;
+ if (tx_status & 0x8000) { /* There was an error, log it. */
+#ifndef final_version
+ if (debug > 1)
+ printk(KERN_DEBUG "%s: Transmit error, Tx status
+%8.8x.\n",
+ dev->name, tx_status);
+#endif
+ np->stats.tx_errors++;
+ if (tx_status & 0x0104) np->stats.tx_aborted_errors++;
+ if (tx_status & 0x0C80) np->stats.tx_carrier_errors++;
+ if (tx_status & 0x0200) np->stats.tx_window_errors++;
+ if (tx_status & 0x0002) np->stats.tx_fifo_errors++;
+ if ((tx_status & 0x0080) && np->full_duplex == 0)
+ np->stats.tx_heartbeat_errors++;
+#ifdef ETHER_STATS
+ if (tx_status & 0x0100) np->stats.collisions16++;
+#endif
+ } else {
+#ifdef ETHER_STATS
+ if (tx_status & 0x0001) np->stats.tx_deferred++;
+#endif
+#ifndef final_version
+ if (debug > 3)
+ printk(KERN_DEBUG "%s: Transmit slot %d ok, Tx status
+%8.8x.\n",
+ dev->name, entry, tx_status);
+#endif
+ np->stats.tx_bytes += np->tx_skbuff[entry]->len;
+ np->stats.collisions += (tx_status >> 3) & 15;
+ np->stats.tx_packets++;
+ }
+ /* Free the original skb. */
+ pci_unmap_single(np->pci_dev,np->tx_addr[entry],
+ np->tx_skbuff[entry]->len,
+ PCI_DMA_TODEVICE);
+ np->tx_q_bytes -= np->tx_skbuff[entry]->len;
+ dev_kfree_skb_irq(np->tx_skbuff[entry]);
+ np->tx_skbuff[entry] = 0;
+ }
+ if (np->tx_full &&
+ np->cur_tx - np->dirty_tx < TX_QUEUE_LEN_RESTART &&
+ np->tx_q_bytes < TX_BUG_FIFO_LIMIT) {
+ /* The ring is no longer full, clear tbusy. */
+ np->tx_full = 0;
+ wmb();
+ netif_wake_queue(dev);
+ }
+}
+
/* The interrupt handler does all of the Rx thread work and cleans up
after the Tx thread. */
static void intr_handler(int irq, void *dev_instance, struct pt_regs *rgs)
@@ -999,8 +1176,8 @@
long ioaddr = dev->base_addr;
int work_limit = max_interrupt_work;
- spin_lock(&np->lock);
-
+ if (np->dev_suspended)
+ return;
do {
u32 intr_status = readl(ioaddr + IntrStatus);
@@ -1016,51 +1193,14 @@
if (intr_status & (IntrRxDone | RxNoBuf))
netdev_rx(dev);
+ if (intr_status & RxNoBuf)
+ writel(0, ioaddr + RxStartDemand);
- for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) {
- int entry = np->dirty_tx % TX_RING_SIZE;
- int tx_status = le32_to_cpu(np->tx_ring[entry].status);
-
- if (tx_status < 0)
- break;
- if (tx_status & 0x8000) { /* There was an error,
log it. */
-#ifndef final_version
- if (debug > 1)
- printk(KERN_DEBUG "%s: Transmit error, Tx
status %8.8x.\n",
- dev->name, tx_status);
-#endif
- np->stats.tx_errors++;
- if (tx_status & 0x0104) np->stats.tx_aborted_errors++;
- if (tx_status & 0x0C80) np->stats.tx_carrier_errors++;
- if (tx_status & 0x0200) np->stats.tx_window_errors++;
- if (tx_status & 0x0002) np->stats.tx_fifo_errors++;
- if ((tx_status & 0x0080) && np->full_duplex == 0)
- np->stats.tx_heartbeat_errors++;
-#ifdef ETHER_STATS
- if (tx_status & 0x0100) np->stats.collisions16++;
-#endif
- } else {
-#ifdef ETHER_STATS
- if (tx_status & 0x0001) np->stats.tx_deferred++;
-#endif
- np->stats.tx_bytes += np->tx_skbuff[entry]->len;
- np->stats.collisions += (tx_status >> 3) & 15;
- np->stats.tx_packets++;
- }
- /* Free the original skb. */
- pci_unmap_single(np->pci_dev,np->tx_addr[entry],
- np->tx_skbuff[entry]->len,
- PCI_DMA_TODEVICE);
- np->tx_q_bytes -= np->tx_skbuff[entry]->len;
- dev_kfree_skb_irq(np->tx_skbuff[entry]);
- np->tx_skbuff[entry] = 0;
- }
- if (np->tx_full &&
- np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 4
- && np->tx_q_bytes < TX_BUG_FIFO_LIMIT) {
- /* The ring is no longer full, clear tbusy. */
- np->tx_full = 0;
- netif_wake_queue(dev);
+ if (intr_status & (TxIdle | IntrTxDone) &&
+ np->cur_tx != np->dirty_tx) {
+ spin_lock(&np->lock);
+ netdev_tx_done(dev);
+ spin_unlock(&np->lock);
}
/* Abnormal error summary/uncommon events handlers. */
@@ -1073,8 +1213,12 @@
"status=0x%4.4x.\n", dev->name, intr_status);
/* Set the timer to re-enable the other interrupts after
10*82usec ticks. */
- writel(AbnormalIntr | TimerInt, ioaddr + IntrEnable);
- writel(10, ioaddr + GPTimer);
+ spin_lock(&np->lock);
+ if (!np->dev_suspended) {
+ writel(AbnormalIntr | TimerInt, ioaddr + IntrEnable);
+ writel(10, ioaddr + GPTimer);
+ }
+ spin_unlock(&np->lock);
break;
}
} while (1);
@@ -1082,8 +1226,6 @@
if (debug > 3)
printk(KERN_DEBUG "%s: exiting interrupt, status=%#4.4x.\n",
dev->name, (int)readl(ioaddr + IntrStatus));
-
- spin_unlock(&np->lock);
}
/* This routine is logically part of the interrupt handler, but separated
@@ -1102,7 +1244,7 @@
/* If EOP is set on the next entry, it's a new packet. Send it up. */
while (--work_limit >= 0) {
struct w840_rx_desc *desc = np->rx_head_desc;
- s32 status = le32_to_cpu(desc->status);
+ s32 status = desc->status;
if (debug > 4)
printk(KERN_DEBUG " netdev_rx() status was %8.8x.\n",
@@ -1198,10 +1340,10 @@
np->rx_addr[entry] = pci_map_single(np->pci_dev,
skb->tail,
skb->len, PCI_DMA_FROMDEVICE);
- np->rx_ring[entry].buffer1 = cpu_to_le32(np->rx_addr[entry]);
+ np->rx_ring[entry].buffer1 = np->rx_addr[entry];
}
wmb();
- np->rx_ring[entry].status = cpu_to_le32(DescOwn);
+ np->rx_ring[entry].status = DescOwn;
}
return 0;
@@ -1218,31 +1360,36 @@
if (intr_status == 0xffffffff)
return;
if (intr_status & TxFIFOUnderflow) {
+ int new;
/* Bump up the Tx threshold */
+ spin_lock(&np->lock);
#if 0
/* This causes lots of dropped packets,
* and under high load even tx_timeouts
*/
- np->csr6 += 0x4000;
+ new = np->csr6 + 0x4000;
#else
- int cur = (np->csr6 >> 14)&0x7f;
- if (cur < 64)
- cur *= 2;
+ new = (np->csr6 >> 14)&0x7f;
+ if (new < 64)
+ new *= 2;
else
- cur = 0; /* load full packet before starting */
- np->csr6 &= ~(0x7F << 14);
- np->csr6 |= cur<<14;
+ new = 0; /* load full packet before starting */
+ new = (np->csr6 & ~(0x7F << 14)) | (new<<14);
#endif
- printk(KERN_DEBUG "%s: Tx underflow, increasing threshold to %8.8x.\n",
- dev->name, np->csr6);
- writel(np->csr6, ioaddr + NetworkConfig);
+ printk(KERN_DEBUG "%s: Tx underflow, new csr6 %8.8x.\n",
+ dev->name, new);
+ update_csr6(dev, new);
+ spin_unlock(&np->lock);
}
if (intr_status & IntrRxDied) { /* Missed a Rx frame. */
np->stats.rx_errors++;
}
if (intr_status & TimerInt) {
/* Re-enable other interrupts. */
- writel(0x1A0F5, ioaddr + IntrEnable);
+ spin_lock(&np->lock);
+ if (!np->dev_suspended)
+ writel(0x1A0F5, ioaddr + IntrEnable);
+ spin_unlock(&np->lock);
}
np->stats.rx_missed_errors += readl(ioaddr + RxMissed) & 0xffff;
writel(0, ioaddr + RxStartDemand);
@@ -1307,26 +1454,57 @@
}
writel(mc_filter[0], ioaddr + MulticastFilter0);
writel(mc_filter[1], ioaddr + MulticastFilter1);
- np->csr6 &= ~0x00F8;
- np->csr6 |= rx_mode;
- writel(np->csr6, ioaddr + NetworkConfig);
+ spin_lock_irq(&np->lock);
+ update_csr6(dev, (np->csr6 & ~0x00F8) | rx_mode);
+ spin_unlock_irq(&np->lock);
+}
+
+static int netdev_ethtool_ioctl(struct net_device *dev, void *useraddr)
+{
+ struct netdev_private *np = dev->priv;
+ u32 ethcmd;
+
+ if (copy_from_user(ðcmd, 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);
+ strcpy(info.bus_info, np->pci_dev->slot_name);
+ if (copy_to_user(useraddr, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+
+ }
+
+ return -EOPNOTSUPP;
}
-static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
+ struct netdev_private *np = dev->priv;
u16 *data = (u16 *)&rq->ifr_data;
switch(cmd) {
+ case SIOCETHTOOL:
+ return netdev_ethtool_ioctl(dev, (void *) rq->ifr_data);
case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */
data[0] = ((struct netdev_private *)dev->priv)->phys[0] & 0x1f;
/* Fall Through */
case SIOCDEVPRIVATE+1: /* Read the specified MII register. */
+ spin_lock_irq(&np->lock);
data[3] = mdio_read(dev, data[0] & 0x1f, data[1] & 0x1f);
+ spin_unlock_irq(&np->lock);
return 0;
case SIOCDEVPRIVATE+2: /* Write the specified MII register */
if (!capable(CAP_NET_ADMIN))
return -EPERM;
+ spin_lock_irq(&np->lock);
mdio_write(dev, data[0] & 0x1f, data[1] & 0x1f, data[2]);
+ spin_unlock_irq(&np->lock);
return 0;
default:
return -EOPNOTSUPP;
@@ -1337,7 +1515,6 @@
{
long ioaddr = dev->base_addr;
struct netdev_private *np = dev->priv;
- int i;
netif_stop_queue(dev);
@@ -1348,18 +1525,29 @@
printk(KERN_DEBUG "%s: Queue pointers were Tx %d / %d, Rx %d / %d.\n",
dev->name, np->cur_tx, np->dirty_tx, np->cur_rx,
np->dirty_rx);
}
+ /* Stop the chip's Tx and Rx processes. */
+ spin_lock_irq(&np->lock);
+ update_csr6(dev, np->csr6 & ~0x20FA);
- /* Disable interrupts by clearing the interrupt mask. */
+ /* and now disable the interrupt handler handler
+ * dev_suspended is required to prevent a TimerInt
+ * from reenabling the interrupts.
+ * The rntl_lock prevents concurrent _suspend calls.
+ */
+ np->dev_suspended = 1;
writel(0x0000, ioaddr + IntrEnable);
+ spin_unlock_irq(&np->lock);
- /* Stop the chip's Tx and Rx processes. */
- writel(np->csr6 &= ~0x20FA, ioaddr + NetworkConfig);
+ free_irq(dev->irq, dev);
+ wmb();
+ np->dev_suspended = 0;
if (readl(ioaddr + NetworkConfig) != 0xffffffff)
np->stats.rx_missed_errors += readl(ioaddr + RxMissed) & 0xffff;
#ifdef __i386__
if (debug > 2) {
+ int i;
printk("\n"KERN_DEBUG" Tx ring at %8.8x:\n",
(int)np->tx_ring);
for (i = 0; i < TX_RING_SIZE; i++)
@@ -1376,11 +1564,10 @@
}
#endif /* __i386__ debugging only */
- free_irq(dev->irq, dev);
-
del_timer_sync(&np->timer);
free_rxtx_rings(np);
+ free_ringdesc(np);
return 0;
}
@@ -1402,11 +1589,70 @@
pci_set_drvdata(pdev, NULL);
}
+static void w840_suspend (struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata (pdev);
+ struct netdev_private *np = dev->priv;
+ long ioaddr = dev->base_addr;
+
+ netif_device_detach(dev);
+ /* no more calls to tx_timeout, hard_start_xmit, set_rx_mode */
+ rtnl_lock();
+ rtnl_unlock();
+ /* noone within ->open */
+ if (netif_running (dev)) {
+ del_timer_sync(&np->timer);
+ /* no more link beat timer calls */
+ spin_lock_irq(&np->lock);
+ update_csr6(dev, np->csr6 & ~0x2002);
+ /* Tx and Rx processes stopped */
+
+ np->dev_suspended = 1;
+ writel(0, ioaddr + IntrEnable);
+ /* all irq events disabled. */
+ spin_unlock_irq(&np->lock);
+
+ synchronize_irq();
+
+ /* Update the error counts. */
+ np->stats.rx_missed_errors += readl(ioaddr + RxMissed) & 0xffff;
+
+ /* pci_power_off(pdev, -1); */
+
+ free_rxtx_rings(np);
+ }
+}
+
+
+static void w840_resume (struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata (pdev);
+ struct netdev_private *np = dev->priv;
+
+ if (netif_running (dev)) {
+ pci_enable_device(pdev);
+ /* pci_power_on(pdev); */
+ np->dev_suspended = 0;
+
+ writel(1, dev->base_addr+PCIBusCfg);
+ readl(dev->base_addr+PCIBusCfg);
+ udelay(1);
+ init_rxtx_rings(dev);
+ init_registers(dev);
+
+ np->timer.expires = jiffies + 1*HZ;
+ add_timer(&np->timer);
+ }
+ netif_device_attach(dev);
+}
+
static struct pci_driver w840_driver = {
- name: "winbond-840",
+ name: DRV_NAME,
id_table: w840_pci_tbl,
probe: w840_probe1,
remove: w840_remove1,
+ suspend: w840_suspend,
+ resume: w840_resume,
};
static int __init w840_init(void)