Commit 05fd0be9 authored by Jeff Garzik's avatar Jeff Garzik

Merge up to version 1.04 of sundance net driver:

* New Rx polling scheme to prevent congestion
* Tx timeout recovery
* Enhanced ethtool support
* Fix problems with multiport sundance boards
(contributed by Edward Peng @ D-Link)
parent 6f5c8595
...@@ -24,19 +24,27 @@ ...@@ -24,19 +24,27 @@
Version 1.02 (D-Link): Version 1.02 (D-Link):
- Add new board to PCI ID list - Add new board to PCI ID list
- Fix multicast bug - Fix multicast bug
Version 1.03 (D-Link):
- New Rx scheme, reduce Rx congestion
- Option to disable flow control
Version 1.04 (D-Link):
- Tx timeout recovery
- More support for ethtool.
*/ */
#define DRV_NAME "sundance" #define DRV_NAME "sundance"
#define DRV_VERSION "1.02" #define DRV_VERSION "1.04"
#define DRV_RELDATE "17-Jan-2002" #define DRV_RELDATE "19-Aug-2002"
/* The user-configurable values. /* The user-configurable values.
These may be modified when a driver module is loaded.*/ These may be modified when a driver module is loaded.*/
static int debug = 1; /* 1 normal messages, 0 quiet .. 7 verbose. */ static int debug = 1; /* 1 normal messages, 0 quiet .. 7 verbose. */
/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ /* Maximum events (Rx packets, etc.) to handle at each interrupt. */
static int max_interrupt_work = 30; static int max_interrupt_work = 0;
static int mtu; static int mtu;
/* Maximum number of multicast addresses to filter (vs. rx-all-multicast). /* Maximum number of multicast addresses to filter (vs. rx-all-multicast).
Typical is a 64 element hash table based on the Ethernet CRC. */ Typical is a 64 element hash table based on the Ethernet CRC. */
...@@ -47,6 +55,8 @@ static int multicast_filter_limit = 32; ...@@ -47,6 +55,8 @@ static int multicast_filter_limit = 32;
This chip can receive into offset buffers, so the Alpha does not This chip can receive into offset buffers, so the Alpha does not
need a copy-align. */ need a copy-align. */
static int rx_copybreak; static int rx_copybreak;
static int tx_coalesce=1;
static int flowctrl=1;
/* media[] specifies the media type the NIC operates at. /* media[] specifies the media type the NIC operates at.
autosense Autosensing active media. autosense Autosensing active media.
...@@ -70,9 +80,10 @@ static char *media[MAX_UNITS]; ...@@ -70,9 +80,10 @@ static char *media[MAX_UNITS];
bonding and packet priority, and more than 128 requires modifying the bonding and packet priority, and more than 128 requires modifying the
Tx error recovery. Tx error recovery.
Large receive rings merely waste memory. */ Large receive rings merely waste memory. */
#define TX_RING_SIZE 16 #define TX_RING_SIZE 64
#define TX_QUEUE_LEN 10 /* Limit ring entries actually used. */ #define TX_QUEUE_LEN (TX_RING_SIZE - 1) /* Limit ring entries actually used. */
#define RX_RING_SIZE 32 #define RX_RING_SIZE 64
#define RX_BUDGET 32
#define TX_TOTAL_SIZE TX_RING_SIZE*sizeof(struct netdev_desc) #define TX_TOTAL_SIZE TX_RING_SIZE*sizeof(struct netdev_desc)
#define RX_TOTAL_SIZE RX_RING_SIZE*sizeof(struct netdev_desc) #define RX_TOTAL_SIZE RX_RING_SIZE*sizeof(struct netdev_desc)
...@@ -107,13 +118,17 @@ static char *media[MAX_UNITS]; ...@@ -107,13 +118,17 @@ static char *media[MAX_UNITS];
#include <linux/init.h> #include <linux/init.h>
#include <linux/ethtool.h> #include <linux/ethtool.h>
#include <linux/mii.h> #include <linux/mii.h>
#include <linux/crc32.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/processor.h> /* Processor type for cache alignment. */ #include <asm/processor.h> /* Processor type for cache alignment. */
#include <asm/bitops.h> #include <asm/bitops.h>
#include <asm/io.h> #include <asm/io.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#ifndef _LOCAL_CRC32
#include <linux/crc32.h>
#else
#include "crc32.h"
#endif
/* These identify the driver base version and may not be removed. */ /* These identify the driver base version and may not be removed. */
static char version[] __devinitdata = static char version[] __devinitdata =
...@@ -129,10 +144,12 @@ MODULE_PARM(mtu, "i"); ...@@ -129,10 +144,12 @@ MODULE_PARM(mtu, "i");
MODULE_PARM(debug, "i"); MODULE_PARM(debug, "i");
MODULE_PARM(rx_copybreak, "i"); MODULE_PARM(rx_copybreak, "i");
MODULE_PARM(media, "1-" __MODULE_STRING(MAX_UNITS) "s"); MODULE_PARM(media, "1-" __MODULE_STRING(MAX_UNITS) "s");
MODULE_PARM(flowctrl, "i");
MODULE_PARM_DESC(max_interrupt_work, "Sundance Alta maximum events handled per interrupt"); MODULE_PARM_DESC(max_interrupt_work, "Sundance Alta maximum events handled per interrupt");
MODULE_PARM_DESC(mtu, "Sundance Alta MTU (all boards)"); MODULE_PARM_DESC(mtu, "Sundance Alta MTU (all boards)");
MODULE_PARM_DESC(debug, "Sundance Alta debug level (0-5)"); MODULE_PARM_DESC(debug, "Sundance Alta debug level (0-5)");
MODULE_PARM_DESC(rx_copybreak, "Sundance Alta copy breakpoint for copy-only-tiny-frames"); MODULE_PARM_DESC(rx_copybreak, "Sundance Alta copy breakpoint for copy-only-tiny-frames");
MODULE_PARM_DESC(flowctrl, "Sundance Alta flow control [0|1]");
/* /*
Theory of Operation Theory of Operation
...@@ -207,7 +224,6 @@ IVc. Errata ...@@ -207,7 +224,6 @@ IVc. Errata
*/ */
enum pci_id_flags_bits { enum pci_id_flags_bits {
/* Set PCI command register bits before calling probe1(). */ /* Set PCI command register bits before calling probe1(). */
...@@ -290,20 +306,24 @@ static struct pci_id_info pci_id_tbl[] = { ...@@ -290,20 +306,24 @@ static struct pci_id_info pci_id_tbl[] = {
enum alta_offsets { enum alta_offsets {
DMACtrl = 0x00, DMACtrl = 0x00,
TxListPtr = 0x04, TxListPtr = 0x04,
TxDMACtrl = 0x08, TxDMABurstThresh = 0x08,
TxDescPoll = 0x0a, TxDMAUrgentThresh = 0x09,
TxDMAPollPeriod = 0x0a,
RxDMAStatus = 0x0c, RxDMAStatus = 0x0c,
RxListPtr = 0x10, RxListPtr = 0x10,
RxDMACtrl = 0x14, RxDMABurstThresh = 0x14,
RxDescPoll = 0x16, RxDMAUrgentThresh = 0x15,
RxDMAPollPeriod = 0x16,
LEDCtrl = 0x1a, LEDCtrl = 0x1a,
ASICCtrl = 0x30, ASICCtrl = 0x30,
EEData = 0x34, EEData = 0x34,
EECtrl = 0x36, EECtrl = 0x36,
TxThreshold = 0x3c, TxStartThresh = 0x3c,
RxEarlyThresh = 0x3e,
FlashAddr = 0x40, FlashAddr = 0x40,
FlashData = 0x44, FlashData = 0x44,
TxStatus = 0x46, TxStatus = 0x46,
TxFrameId = 0x47,
DownCounter = 0x18, DownCounter = 0x18,
IntrClear = 0x4a, IntrClear = 0x4a,
IntrEnable = 0x4c, IntrEnable = 0x4c,
...@@ -337,6 +357,16 @@ enum alta_offsets { ...@@ -337,6 +357,16 @@ enum alta_offsets {
/* Aliased and bogus values! */ /* Aliased and bogus values! */
RxStatus = 0x0c, RxStatus = 0x0c,
}; };
enum ASICCtrl_HiWord_bit {
GlobalReset = 0x0001,
RxReset = 0x0002,
TxReset = 0x0004,
DMAReset = 0x0008,
FIFOReset = 0x0010,
NetworkReset = 0x0020,
HostReset = 0x0040,
ResetBusy = 0x0400,
};
/* Bits in the interrupt status/mask registers. */ /* Bits in the interrupt status/mask registers. */
enum intr_status_bits { enum intr_status_bits {
...@@ -399,19 +429,20 @@ struct netdev_private { ...@@ -399,19 +429,20 @@ struct netdev_private {
struct timer_list timer; /* Media monitoring timer. */ struct timer_list timer; /* Media monitoring timer. */
/* Frequently used values: keep some adjacent for cache effect. */ /* Frequently used values: keep some adjacent for cache effect. */
spinlock_t lock; spinlock_t lock;
spinlock_t rx_lock; /* Group with Tx control cache line. */
int chip_id, drv_flags; int chip_id, drv_flags;
unsigned int cur_rx, dirty_rx; /* Producer/consumer ring indices */ unsigned int cur_rx, dirty_rx; /* Producer/consumer ring indices */
unsigned int rx_buf_sz; /* Based on MTU+slack. */ unsigned int rx_buf_sz; /* Based on MTU+slack. */
spinlock_t txlock; /* Group with Tx control cache line. */
struct netdev_desc *last_tx; /* Last Tx descriptor used. */ struct netdev_desc *last_tx; /* Last Tx descriptor used. */
unsigned int cur_tx, dirty_tx; unsigned int cur_tx, dirty_tx;
unsigned int tx_full:1; /* The Tx queue is full. */
/* These values are keep track of the transceiver/media in use. */ /* These values are keep track of the transceiver/media in use. */
unsigned int full_duplex:1; /* Full-duplex operation requested. */ unsigned int full_duplex:1; /* Full-duplex operation requested. */
unsigned int medialock:1; /* Do not sense media. */ unsigned int flowctrl:1;
unsigned int default_port:4; /* Last dev->if_port value. */ unsigned int default_port:4; /* Last dev->if_port value. */
unsigned int an_enable:1; unsigned int an_enable:1;
unsigned int speed; unsigned int speed;
struct tasklet_struct rx_tasklet;
int budget;
/* Multicast and receive mode. */ /* Multicast and receive mode. */
spinlock_t mcastlock; /* SMP lock multicast updates. */ spinlock_t mcastlock; /* SMP lock multicast updates. */
u16 mcast_filter[4]; u16 mcast_filter[4];
...@@ -424,6 +455,9 @@ struct netdev_private { ...@@ -424,6 +455,9 @@ struct netdev_private {
/* The station address location in the EEPROM. */ /* The station address location in the EEPROM. */
#define EEPROM_SA_OFFSET 0x10 #define EEPROM_SA_OFFSET 0x10
#define DEFAULT_INTR (IntrRxDMADone | IntrPCIErr | \
IntrDrvRqst | IntrTxDone | StatsMax | \
LinkChange)
static int eeprom_read(long ioaddr, int location); static int eeprom_read(long ioaddr, int location);
static int mdio_read(struct net_device *dev, int phy_id, int location); static int mdio_read(struct net_device *dev, int phy_id, int location);
...@@ -434,9 +468,11 @@ static void netdev_timer(unsigned long data); ...@@ -434,9 +468,11 @@ static void netdev_timer(unsigned long data);
static void tx_timeout(struct net_device *dev); static void tx_timeout(struct net_device *dev);
static void init_ring(struct net_device *dev); static void init_ring(struct net_device *dev);
static int start_tx(struct sk_buff *skb, struct net_device *dev); static int start_tx(struct sk_buff *skb, struct net_device *dev);
static int reset_tx (struct net_device *dev, int irq);
static void intr_handler(int irq, void *dev_instance, struct pt_regs *regs); static void intr_handler(int irq, void *dev_instance, struct pt_regs *regs);
static void rx_poll(unsigned long data);
static void refill_rx (struct net_device *dev);
static void netdev_error(struct net_device *dev, int intr_status); static void netdev_error(struct net_device *dev, int intr_status);
static int netdev_rx(struct net_device *dev);
static void netdev_error(struct net_device *dev, int intr_status); static void netdev_error(struct net_device *dev, int intr_status);
static void set_rx_mode(struct net_device *dev); static void set_rx_mode(struct net_device *dev);
static struct net_device_stats *get_stats(struct net_device *dev); static struct net_device_stats *get_stats(struct net_device *dev);
...@@ -502,6 +538,7 @@ static int __devinit sundance_probe1 (struct pci_dev *pdev, ...@@ -502,6 +538,7 @@ static int __devinit sundance_probe1 (struct pci_dev *pdev,
np->drv_flags = pci_id_tbl[chip_idx].drv_flags; np->drv_flags = pci_id_tbl[chip_idx].drv_flags;
np->pci_dev = pdev; np->pci_dev = pdev;
spin_lock_init(&np->lock); spin_lock_init(&np->lock);
tasklet_init(&np->rx_tasklet, rx_poll, (unsigned long)dev);
ring_space = pci_alloc_consistent(pdev, TX_TOTAL_SIZE, &ring_dma); ring_space = pci_alloc_consistent(pdev, TX_TOTAL_SIZE, &ring_dma);
if (!ring_space) if (!ring_space)
...@@ -582,6 +619,12 @@ static int __devinit sundance_probe1 (struct pci_dev *pdev, ...@@ -582,6 +619,12 @@ static int __devinit sundance_probe1 (struct pci_dev *pdev,
np->an_enable = 1; np->an_enable = 1;
} }
} }
if (tx_coalesce < 1)
tx_coalesce = 1;
else if (tx_coalesce > TX_QUEUE_LEN - 1)
tx_coalesce = TX_QUEUE_LEN - 1;
if (flowctrl == 0)
np->flowctrl = 0;
} }
/* Fibre PHY? */ /* Fibre PHY? */
...@@ -742,7 +785,6 @@ static void mdio_write(struct net_device *dev, int phy_id, int location, int val ...@@ -742,7 +785,6 @@ static void mdio_write(struct net_device *dev, int phy_id, int location, int val
return; return;
} }
static int netdev_open(struct net_device *dev) static int netdev_open(struct net_device *dev)
{ {
struct netdev_private *np = dev->priv; struct netdev_private *np = dev->priv;
...@@ -779,14 +821,10 @@ static int netdev_open(struct net_device *dev) ...@@ -779,14 +821,10 @@ static int netdev_open(struct net_device *dev)
writew(0, ioaddr + IntrEnable); writew(0, ioaddr + IntrEnable);
writew(0, ioaddr + DownCounter); writew(0, ioaddr + DownCounter);
/* Set the chip to poll every N*320nsec. */ /* Set the chip to poll every N*320nsec. */
writeb(100, ioaddr + RxDescPoll); writeb(100, ioaddr + RxDMAPollPeriod);
writeb(127, ioaddr + TxDescPoll); writeb(127, ioaddr + TxDMAPollPeriod);
netif_start_queue(dev); netif_start_queue(dev);
/* Enable interrupts by setting the interrupt mask. */
writew(IntrRxDone | IntrRxDMADone | IntrPCIErr | IntrDrvRqst | IntrTxDone
| StatsMax | LinkChange, ioaddr + IntrEnable);
writew(StatsEnable | RxEnable | TxEnable, ioaddr + MACCtrl1); writew(StatsEnable | RxEnable | TxEnable, ioaddr + MACCtrl1);
if (debug > 2) if (debug > 2)
...@@ -802,6 +840,9 @@ static int netdev_open(struct net_device *dev) ...@@ -802,6 +840,9 @@ static int netdev_open(struct net_device *dev)
np->timer.data = (unsigned long)dev; np->timer.data = (unsigned long)dev;
np->timer.function = &netdev_timer; /* timer handler */ np->timer.function = &netdev_timer; /* timer handler */
add_timer(&np->timer); add_timer(&np->timer);
/* Enable interrupts by setting the interrupt mask. */
writew(DEFAULT_INTR, ioaddr + IntrEnable);
return 0; return 0;
} }
...@@ -855,9 +896,12 @@ static void tx_timeout(struct net_device *dev) ...@@ -855,9 +896,12 @@ static void tx_timeout(struct net_device *dev)
{ {
struct netdev_private *np = dev->priv; struct netdev_private *np = dev->priv;
long ioaddr = dev->base_addr; long ioaddr = dev->base_addr;
long flag;
printk(KERN_WARNING "%s: Transmit timed out, status %2.2x," printk(KERN_WARNING "%s: Transmit timed out, TxStatus %2.2x "
" resetting...\n", dev->name, readb(ioaddr + TxStatus)); "TxFrameId %2.2x,"
" resetting...\n", dev->name, readb(ioaddr + TxStatus),
readb(ioaddr + TxFrameId));
{ {
int i; int i;
...@@ -866,22 +910,24 @@ static void tx_timeout(struct net_device *dev) ...@@ -866,22 +910,24 @@ static void tx_timeout(struct net_device *dev)
printk(" %8.8x", (unsigned int)np->rx_ring[i].status); printk(" %8.8x", (unsigned int)np->rx_ring[i].status);
printk("\n"KERN_DEBUG" Tx ring %p: ", np->tx_ring); printk("\n"KERN_DEBUG" Tx ring %p: ", np->tx_ring);
for (i = 0; i < TX_RING_SIZE; i++) for (i = 0; i < TX_RING_SIZE; i++)
printk(" %4.4x", np->tx_ring[i].status); printk(" %8.8x", np->tx_ring[i].status);
printk("\n"); printk("\n");
} }
spin_lock_irqsave(&np->lock, flag);
reset_tx(dev, 0);
spin_unlock_irqrestore(&np->lock, flag);
/* Perhaps we should reinitialize the hardware here. */ /* Perhaps we should reinitialize the hardware here. */
dev->if_port = 0; dev->if_port = 0;
/* Stop and restart the chip's Tx processes . */ /* Stop and restart the chip's Tx processes . */
/* Trigger an immediate transmit demand. */ /* Trigger an immediate transmit demand. */
writew(IntrRxDone | IntrRxDMADone | IntrPCIErr | IntrDrvRqst | IntrTxDone writew(DEFAULT_INTR, ioaddr + IntrEnable);
| StatsMax | LinkChange, ioaddr + IntrEnable);
dev->trans_start = jiffies; dev->trans_start = jiffies;
np->stats.tx_errors++; np->stats.tx_errors++;
if (!np->tx_full) if (!netif_queue_stopped(dev))
netif_wake_queue(dev); netif_wake_queue(dev);
} }
...@@ -892,7 +938,6 @@ static void init_ring(struct net_device *dev) ...@@ -892,7 +938,6 @@ static void init_ring(struct net_device *dev)
struct netdev_private *np = dev->priv; struct netdev_private *np = dev->priv;
int i; int i;
np->tx_full = 0;
np->cur_rx = np->cur_tx = 0; np->cur_rx = np->cur_tx = 0;
np->dirty_rx = np->dirty_tx = 0; np->dirty_rx = np->dirty_tx = 0;
...@@ -929,15 +974,16 @@ static void init_ring(struct net_device *dev) ...@@ -929,15 +974,16 @@ static void init_ring(struct net_device *dev)
return; return;
} }
static int start_tx(struct sk_buff *skb, struct net_device *dev) static int
start_tx (struct sk_buff *skb, struct net_device *dev)
{ {
struct netdev_private *np = dev->priv; struct netdev_private *np = (struct netdev_private *) dev->priv;
struct netdev_desc *txdesc; struct netdev_desc *txdesc;
unsigned entry; unsigned entry;
long ioaddr = dev->base_addr;
/* Note: Ordering is important here, set the field with the /* Note: Ordering is important here, set the field with the
"ownership" bit last, and only then increment cur_tx. */ "ownership" bit last, and only then increment cur_tx. */
/* Calculate the next Tx descriptor entry. */ /* Calculate the next Tx descriptor entry. */
entry = np->cur_tx % TX_RING_SIZE; entry = np->cur_tx % TX_RING_SIZE;
np->tx_skbuff[entry] = skb; np->tx_skbuff[entry] = skb;
...@@ -945,11 +991,17 @@ static int start_tx(struct sk_buff *skb, struct net_device *dev) ...@@ -945,11 +991,17 @@ static int start_tx(struct sk_buff *skb, struct net_device *dev)
txdesc->next_desc = 0; txdesc->next_desc = 0;
/* Note: disable the interrupt generation here before releasing. */ /* Note: disable the interrupt generation here before releasing. */
txdesc->status = if (entry % tx_coalesce == 0) {
cpu_to_le32((entry<<2) | DescIntrOnDMADone | DescIntrOnTx | DisableAlign); txdesc->status = cpu_to_le32 ((entry << 2) |
txdesc->frag[0].addr = cpu_to_le32(pci_map_single(np->pci_dev, DescIntrOnTx | DisableAlign);
skb->data, skb->len, PCI_DMA_TODEVICE));
txdesc->frag[0].length = cpu_to_le32(skb->len | LastFrag); } else {
txdesc->status = cpu_to_le32 ((entry << 2) | DisableAlign);
}
txdesc->frag[0].addr = cpu_to_le32 (pci_map_single (np->pci_dev, skb->data,
skb->len,
PCI_DMA_TODEVICE));
txdesc->frag[0].length = cpu_to_le32 (skb->len | LastFrag);
if (np->last_tx) if (np->last_tx)
np->last_tx->next_desc = cpu_to_le32(np->tx_ring_dma + np->last_tx->next_desc = cpu_to_le32(np->tx_ring_dma +
entry*sizeof(struct netdev_desc)); entry*sizeof(struct netdev_desc));
...@@ -957,24 +1009,63 @@ static int start_tx(struct sk_buff *skb, struct net_device *dev) ...@@ -957,24 +1009,63 @@ static int start_tx(struct sk_buff *skb, struct net_device *dev)
np->cur_tx++; np->cur_tx++;
/* On some architectures: explicitly flush cache lines here. */ /* On some architectures: explicitly flush cache lines here. */
if (np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 1
if (np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 1) { && !netif_queue_stopped(dev)) {
/* do nothing */ /* do nothing */
} else { } else {
np->tx_full = 1; netif_stop_queue (dev);
netif_stop_queue(dev);
} }
/* Side effect: The read wakes the potentially-idle transmit channel. */ /* Side effect: The read wakes the potentially-idle transmit channel. */
if (readl(dev->base_addr + TxListPtr) == 0) if (readl (dev->base_addr + TxListPtr) == 0)
writel(np->tx_ring_dma + entry*sizeof(*np->tx_ring), writel (np->tx_ring_dma + entry*sizeof(*np->tx_ring),
dev->base_addr + TxListPtr); dev->base_addr + TxListPtr);
dev->trans_start = jiffies; dev->trans_start = jiffies;
if (debug > 4) { if (debug > 4) {
printk(KERN_DEBUG "%s: Transmit frame #%d queued in slot %d.\n", printk (KERN_DEBUG
dev->name, np->cur_tx, entry); "%s: Transmit frame #%d queued in slot %d.\n",
dev->name, np->cur_tx, entry);
} }
if (tx_coalesce > 1)
writel (1000, ioaddr + DownCounter);
return 0;
}
static int
reset_tx (struct net_device *dev, int irq)
{
struct netdev_private *np = (struct netdev_private*) dev->priv;
long ioaddr = dev->base_addr;
int i;
int frame_id;
frame_id = readb(ioaddr + TxFrameId);
writew (TxReset | DMAReset | FIFOReset | NetworkReset,
ioaddr + ASICCtrl + 2);
for (i=50; i > 0; i--) {
if ((readw(ioaddr + ASICCtrl + 2) & ResetBusy) == 0)
break;
mdelay(1);
}
for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) {
int entry = np->dirty_tx % TX_RING_SIZE;
struct sk_buff *skb;
if (!(np->tx_ring[entry].status & 0x00010000))
break;
skb = np->tx_skbuff[entry];
/* Free the original skb. */
pci_unmap_single(np->pci_dev,
np->tx_ring[entry].frag[0].addr,
skb->len, PCI_DMA_TODEVICE);
if (irq)
dev_kfree_skb_irq (np->tx_skbuff[entry]);
else
dev_kfree_skb (np->tx_skbuff[entry]);
np->tx_skbuff[entry] = 0;
}
writel (np->tx_ring_dma + frame_id * sizeof(*np->tx_ring),
dev->base_addr + TxListPtr);
return 0; return 0;
} }
...@@ -989,83 +1080,88 @@ static void intr_handler(int irq, void *dev_instance, struct pt_regs *rgs) ...@@ -989,83 +1080,88 @@ static void intr_handler(int irq, void *dev_instance, struct pt_regs *rgs)
ioaddr = dev->base_addr; ioaddr = dev->base_addr;
np = dev->priv; np = dev->priv;
spin_lock(&np->lock);
do { do {
int intr_status = readw(ioaddr + IntrStatus); int intr_status = readw(ioaddr + IntrStatus);
writew(intr_status & (IntrRxDone | IntrRxDMADone | IntrPCIErr | writew(intr_status, ioaddr + IntrStatus);
IntrDrvRqst | IntrTxDone | IntrTxDMADone | StatsMax |
LinkChange), ioaddr + IntrStatus);
if (debug > 4) if (debug > 4)
printk(KERN_DEBUG "%s: Interrupt, status %4.4x.\n", printk(KERN_DEBUG "%s: Interrupt, status %4.4x.\n",
dev->name, intr_status); dev->name, intr_status);
if (intr_status == 0) if (!(intr_status & DEFAULT_INTR))
break; break;
if (intr_status & (IntrRxDone|IntrRxDMADone)) if (intr_status & (IntrRxDMADone)) {
netdev_rx(dev); writew(DEFAULT_INTR & ~(IntrRxDone|IntrRxDMADone),
ioaddr + IntrEnable);
if (np->budget < 0)
np->budget = RX_BUDGET;
tasklet_schedule(&np->rx_tasklet);
}
if (intr_status & IntrTxDone) { if (intr_status & (IntrTxDone | IntrDrvRqst)) {
int boguscnt = 32; int boguscnt = 32;
int tx_status = readw(ioaddr + TxStatus); int tx_status = readw (ioaddr + TxStatus);
while (tx_status & 0x80) { while (tx_status & 0x80) {
if (debug > 4) if (debug > 4)
printk("%s: Transmit status is %2.2x.\n", printk
dev->name, tx_status); ("%s: Transmit status is %2.2x.\n",
dev->name, tx_status);
if (tx_status & 0x1e) { if (tx_status & 0x1e) {
np->stats.tx_errors++; np->stats.tx_errors++;
if (tx_status & 0x10) np->stats.tx_fifo_errors++; if (tx_status & 0x10)
np->stats.tx_fifo_errors++;
#ifdef ETHER_STATS #ifdef ETHER_STATS
if (tx_status & 0x08) np->stats.collisions16++; if (tx_status & 0x08)
np->stats.collisions16++;
#else #else
if (tx_status & 0x08) np->stats.collisions++; if (tx_status & 0x08)
np->stats.collisions++;
#endif #endif
if (tx_status & 0x04) np->stats.tx_fifo_errors++; if (tx_status & 0x02)
if (tx_status & 0x02) np->stats.tx_window_errors++; np->stats.tx_window_errors++;
/* This reset has not been verified!. */ /* This reset has not been verified!. */
if (tx_status & 0x10) { /* Reset the Tx. */ if (tx_status & 0x10) { /* Reset the Tx. */
writew(0x001c, ioaddr + ASICCtrl + 2); np->stats.tx_fifo_errors++;
#if 0 /* Do we need to reset the Tx pointer here? */ spin_lock(&np->lock);
writel(np->tx_ring_dma reset_tx(dev, 1);
+ np->dirty_tx*sizeof(*np->tx_ring), spin_unlock(&np->lock);
dev->base_addr + TxListPtr);
#endif
} }
if (tx_status & 0x1e) /* Restart the Tx. */ if (tx_status & 0x1e) /* Restart the Tx. */
writew(TxEnable, ioaddr + MACCtrl1); writew (TxEnable,
ioaddr + MACCtrl1);
} }
/* Yup, this is a documentation bug. It cost me *hours*. */ /* Yup, this is a documentation bug. It cost me *hours*. */
writew(0, ioaddr + TxStatus); writew (0, ioaddr + TxStatus);
tx_status = readb(ioaddr + TxStatus); tx_status = readw (ioaddr + TxStatus);
if (--boguscnt < 0) if (--boguscnt < 0)
break; break;
} }
} }
spin_lock(&np->lock);
for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) { for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) {
int entry = np->dirty_tx % TX_RING_SIZE; int entry = np->dirty_tx % TX_RING_SIZE;
struct sk_buff *skb; struct sk_buff *skb;
if (!(np->tx_ring[entry].status & 0x00010000))
if ( ! (np->tx_ring[entry].status & 0x00010000))
break; break;
skb = np->tx_skbuff[entry]; skb = np->tx_skbuff[entry];
/* Free the original skb. */ /* Free the original skb. */
pci_unmap_single(np->pci_dev, pci_unmap_single(np->pci_dev,
np->tx_ring[entry].frag[0].addr, np->tx_ring[entry].frag[0].addr,
skb->len, PCI_DMA_TODEVICE); skb->len, PCI_DMA_TODEVICE);
dev_kfree_skb_irq(skb); dev_kfree_skb_irq (np->tx_skbuff[entry]);
np->tx_skbuff[entry] = 0; np->tx_skbuff[entry] = 0;
} }
if (np->tx_full spin_unlock(&np->lock);
&& np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 4) { if (netif_queue_stopped(dev) &&
np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 4) {
/* The ring is no longer full, clear tbusy. */ /* The ring is no longer full, clear tbusy. */
np->tx_full = 0; netif_wake_queue (dev);
netif_wake_queue(dev);
} }
/* Abnormal error summary/uncommon events handlers. */ /* Abnormal error summary/uncommon events handlers. */
if (intr_status & (IntrDrvRqst | IntrPCIErr | LinkChange | StatsMax)) if (intr_status & (IntrPCIErr | LinkChange | StatsMax))
netdev_error(dev, intr_status); netdev_error(dev, intr_status);
if (--boguscnt < 0) { if (--boguscnt < 0) {
get_stats(dev); get_stats(dev);
...@@ -1073,49 +1169,41 @@ static void intr_handler(int irq, void *dev_instance, struct pt_regs *rgs) ...@@ -1073,49 +1169,41 @@ static void intr_handler(int irq, void *dev_instance, struct pt_regs *rgs)
printk(KERN_WARNING "%s: Too much work at interrupt, " printk(KERN_WARNING "%s: Too much work at interrupt, "
"status=0x%4.4x / 0x%4.4x.\n", "status=0x%4.4x / 0x%4.4x.\n",
dev->name, intr_status, readw(ioaddr + IntrClear)); dev->name, intr_status, readw(ioaddr + IntrClear));
/* Re-enable us in 3.2msec. */
writew(0, ioaddr + IntrEnable);
writew(1000, ioaddr + DownCounter);
writew(IntrDrvRqst, ioaddr + IntrEnable);
break; break;
} }
} while (1); } while (1);
if (debug > 3) if (debug > 3)
printk(KERN_DEBUG "%s: exiting interrupt, status=%#4.4x.\n", printk(KERN_DEBUG "%s: exiting interrupt, status=%#4.4x.\n",
dev->name, readw(ioaddr + IntrStatus)); dev->name, readw(ioaddr + IntrStatus));
if (np->cur_tx - np->dirty_tx > 0 && tx_coalesce > 1)
writel(100, ioaddr + DownCounter);
spin_unlock(&np->lock);
} }
/* This routine is logically part of the interrupt handler, but separated static void rx_poll(unsigned long data)
for clarity and better register allocation. */
static int netdev_rx(struct net_device *dev)
{ {
struct net_device *dev = (struct net_device *)data;
struct netdev_private *np = dev->priv; struct netdev_private *np = dev->priv;
int entry = np->cur_rx % RX_RING_SIZE; int entry = np->cur_rx % RX_RING_SIZE;
int boguscnt = np->dirty_rx + RX_RING_SIZE - np->cur_rx; int boguscnt = np->budget;
long ioaddr = dev->base_addr;
if (debug > 4) { int received = 0;
printk(KERN_DEBUG " In netdev_rx(), entry %d status %4.4x.\n",
entry, np->rx_ring[entry].status);
}
/* If EOP is set on the next entry, it's a new packet. Send it up. */ /* If EOP is set on the next entry, it's a new packet. Send it up. */
while (1) { while (1) {
struct netdev_desc *desc = &(np->rx_ring[entry]); struct netdev_desc *desc = &(np->rx_ring[entry]);
u32 frame_status; u32 frame_status = le32_to_cpu(desc->status);
int pkt_len; int pkt_len;
if (--boguscnt < 0) {
goto not_done;
}
if (!(desc->status & DescOwn)) if (!(desc->status & DescOwn))
break; break;
frame_status = le32_to_cpu(desc->status);
pkt_len = frame_status & 0x1fff; /* Chip omits the CRC. */ pkt_len = frame_status & 0x1fff; /* Chip omits the CRC. */
if (debug > 4) if (debug > 4)
printk(KERN_DEBUG " netdev_rx() status was %8.8x.\n", printk(KERN_DEBUG " netdev_rx() status was %8.8x.\n",
frame_status); frame_status);
if (--boguscnt < 0)
break;
pci_dma_sync_single(np->pci_dev, desc->frag[0].addr, pci_dma_sync_single(np->pci_dev, desc->frag[0].addr,
np->rx_buf_sz, PCI_DMA_FROMDEVICE); np->rx_buf_sz, PCI_DMA_FROMDEVICE);
...@@ -1136,7 +1224,6 @@ static int netdev_rx(struct net_device *dev) ...@@ -1136,7 +1224,6 @@ static int netdev_rx(struct net_device *dev)
} }
} else { } else {
struct sk_buff *skb; struct sk_buff *skb;
#ifndef final_version #ifndef final_version
if (debug > 4) if (debug > 4)
printk(KERN_DEBUG " netdev_rx() normal Rx pkt length %d" printk(KERN_DEBUG " netdev_rx() normal Rx pkt length %d"
...@@ -1164,11 +1251,36 @@ static int netdev_rx(struct net_device *dev) ...@@ -1164,11 +1251,36 @@ static int netdev_rx(struct net_device *dev)
netif_rx(skb); netif_rx(skb);
dev->last_rx = jiffies; dev->last_rx = jiffies;
} }
entry = (++np->cur_rx) % RX_RING_SIZE; entry = (entry + 1) % RX_RING_SIZE;
received++;
} }
np->cur_rx = entry;
refill_rx (dev);
np->budget -= received;
writew(DEFAULT_INTR, ioaddr + IntrEnable);
return;
not_done:
np->cur_rx = entry;
refill_rx (dev);
if (!received)
received = 1;
np->budget -= received;
if (np->budget <= 0)
np->budget = RX_BUDGET;
tasklet_schedule(&np->rx_tasklet);
return;
}
static void refill_rx (struct net_device *dev)
{
struct netdev_private *np = dev->priv;
int entry;
int cnt = 0;
/* Refill the Rx ring buffers. */ /* Refill the Rx ring buffers. */
for (; np->cur_rx - np->dirty_rx > 0; np->dirty_rx++) { for (;(np->cur_rx - np->dirty_rx + RX_RING_SIZE) % RX_RING_SIZE > 0;
np->dirty_rx = (np->dirty_rx + 1) % RX_RING_SIZE) {
struct sk_buff *skb; struct sk_buff *skb;
entry = np->dirty_rx % RX_RING_SIZE; entry = np->dirty_rx % RX_RING_SIZE;
if (np->rx_skbuff[entry] == NULL) { if (np->rx_skbuff[entry] == NULL) {
...@@ -1186,56 +1298,51 @@ static int netdev_rx(struct net_device *dev) ...@@ -1186,56 +1298,51 @@ static int netdev_rx(struct net_device *dev)
np->rx_ring[entry].frag[0].length = np->rx_ring[entry].frag[0].length =
cpu_to_le32(np->rx_buf_sz | LastFrag); cpu_to_le32(np->rx_buf_sz | LastFrag);
np->rx_ring[entry].status = 0; np->rx_ring[entry].status = 0;
cnt++;
} }
return;
/* No need to restart Rx engine, it will poll. */
return 0;
} }
static void netdev_error(struct net_device *dev, int intr_status) static void netdev_error(struct net_device *dev, int intr_status)
{ {
long ioaddr = dev->base_addr; long ioaddr = dev->base_addr;
struct netdev_private *np = dev->priv; struct netdev_private *np = dev->priv;
u16 mii_ctl, mii_advertise, mii_lpa; u16 mii_ctl, mii_advertise, mii_lpa;
int speed; int speed;
if (intr_status & IntrDrvRqst) {
/* Stop the down counter and turn interrupts back on. */
if (debug > 1)
printk("%s: Turning interrupts back on.\n", dev->name);
writew(0, ioaddr + IntrEnable);
writew(0, ioaddr + DownCounter);
writew(IntrRxDone | IntrRxDMADone | IntrPCIErr | IntrDrvRqst |
IntrTxDone | StatsMax | LinkChange, ioaddr + IntrEnable);
/* Ack buggy InRequest */
writew (IntrDrvRqst, ioaddr + IntrStatus);
}
if (intr_status & LinkChange) { if (intr_status & LinkChange) {
if (np->an_enable) { if (np->an_enable) {
mii_advertise = mdio_read (dev, np->phys[0], MII_ADVERTISE); mii_advertise = mdio_read (dev, np->phys[0], MII_ADVERTISE);
mii_lpa= mdio_read (dev, np->phys[0], MII_LPA); mii_lpa= mdio_read (dev, np->phys[0], MII_LPA);
mii_advertise &= mii_lpa; mii_advertise &= mii_lpa;
printk (KERN_INFO "%s: Link changed: ", dev->name); printk (KERN_INFO "%s: Link changed: ", dev->name);
if (mii_advertise & ADVERTISE_100FULL) if (mii_advertise & ADVERTISE_100FULL) {
np->speed = 100;
printk ("100Mbps, full duplex\n"); printk ("100Mbps, full duplex\n");
else if (mii_advertise & ADVERTISE_100HALF) } else if (mii_advertise & ADVERTISE_100HALF) {
np->speed = 100;
printk ("100Mbps, half duplex\n"); printk ("100Mbps, half duplex\n");
else if (mii_advertise & ADVERTISE_10FULL) } else if (mii_advertise & ADVERTISE_10FULL) {
np->speed = 10;
printk ("10Mbps, full duplex\n"); printk ("10Mbps, full duplex\n");
else if (mii_advertise & ADVERTISE_10HALF) } else if (mii_advertise & ADVERTISE_10HALF) {
np->speed = 10;
printk ("10Mbps, half duplex\n"); printk ("10Mbps, half duplex\n");
else } else
printk ("\n"); printk ("\n");
} else { } else {
mii_ctl = mdio_read (dev, np->phys[0], MII_BMCR); mii_ctl = mdio_read (dev, np->phys[0], MII_BMCR);
speed = (mii_ctl & BMCR_SPEED100) ? 100 : 10; speed = (mii_ctl & BMCR_SPEED100) ? 100 : 10;
np->speed = speed;
printk (KERN_INFO "%s: Link changed: %dMbps ,", printk (KERN_INFO "%s: Link changed: %dMbps ,",
dev->name, speed); dev->name, speed);
printk ("%s duplex.\n", (mii_ctl & BMCR_FULLDPLX) ? printk ("%s duplex.\n", (mii_ctl & BMCR_FULLDPLX) ?
"full" : "half"); "full" : "half");
} }
check_duplex (dev); check_duplex (dev);
if (np->flowctrl == 0)
writew(readw(ioaddr + MACCtrl0) & ~EnbFlowCtrl,
ioaddr + MACCtrl0);
} }
if (intr_status & StatsMax) { if (intr_status & StatsMax) {
get_stats(dev); get_stats(dev);
...@@ -1319,24 +1426,136 @@ static int netdev_ethtool_ioctl(struct net_device *dev, void *useraddr) ...@@ -1319,24 +1426,136 @@ static int netdev_ethtool_ioctl(struct net_device *dev, void *useraddr)
{ {
struct netdev_private *np = dev->priv; struct netdev_private *np = dev->priv;
u32 ethcmd; u32 ethcmd;
long ioaddr = dev->base_addr;
if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd))) if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
return -EFAULT; return -EFAULT;
switch (ethcmd) { switch (ethcmd) {
case ETHTOOL_GDRVINFO: { case ETHTOOL_GDRVINFO: {
struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO}; struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO};
strcpy(info.driver, DRV_NAME); strcpy(info.driver, DRV_NAME);
strcpy(info.version, DRV_VERSION); strcpy(info.version, DRV_VERSION);
strcpy(info.bus_info, np->pci_dev->slot_name); strcpy(info.bus_info, np->pci_dev->slot_name);
if (copy_to_user(useraddr, &info, sizeof(info))) memset(&info.fw_version, 0, sizeof(info.fw_version));
if (copy_to_user(useraddr, &info, sizeof(info)))
return -EFAULT;
return 0;
}
case ETHTOOL_GSET: {
struct ethtool_cmd cmd = { ETHTOOL_GSET };
if (readl (ioaddr + ASICCtrl) & 0x80) {
/* fiber device */
cmd.supported = SUPPORTED_Autoneg |
SUPPORTED_FIBRE;
cmd.advertising= ADVERTISED_Autoneg |
ADVERTISED_FIBRE;
cmd.port = PORT_FIBRE;
cmd.transceiver = XCVR_INTERNAL;
} else {
/* copper device */
cmd.supported = SUPPORTED_10baseT_Half |
SUPPORTED_10baseT_Full |
SUPPORTED_100baseT_Half |
SUPPORTED_100baseT_Full |
SUPPORTED_Autoneg |
SUPPORTED_MII;
cmd.advertising = ADVERTISED_10baseT_Half |
ADVERTISED_10baseT_Full |
ADVERTISED_100baseT_Half |
ADVERTISED_100baseT_Full |
ADVERTISED_Autoneg |
ADVERTISED_MII;
cmd.port = PORT_MII;
cmd.transceiver = XCVR_INTERNAL;
}
if (readb(ioaddr + MIICtrl) & 0x80) {
cmd.speed = np->speed;
cmd.duplex = np->full_duplex ?
DUPLEX_FULL : DUPLEX_HALF;
} else {
cmd.speed = -1;
cmd.duplex = -1;
}
if ( np->an_enable)
cmd.autoneg = AUTONEG_ENABLE;
else
cmd.autoneg = AUTONEG_DISABLE;
cmd.phy_address = np->phys[0];
if (copy_to_user(useraddr, &cmd,
sizeof(cmd)))
return -EFAULT;
return 0;
}
case ETHTOOL_SSET: {
struct ethtool_cmd cmd;
if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
return -EFAULT;
netif_carrier_off(dev);
if (cmd.autoneg == AUTONEG_ENABLE) {
if (np->an_enable)
return 0;
else {
np->an_enable = 1;
/* Reset PHY */
mdio_write (dev, np->phys[0], MII_BMCR,
BMCR_RESET);
mdelay (300);
/* Start auto negotiation */
mdio_write (dev, np->phys[0], MII_BMCR,
BMCR_ANENABLE|BMCR_ANRESTART);
return 0;
}
} else {
/* Reset PHY */
mdio_write (dev, np->phys[0], MII_BMCR,
BMCR_RESET);
mdelay (300);
np->an_enable = 0;
switch(cmd.speed + cmd.duplex){
case SPEED_10 + DUPLEX_HALF:
np->speed = 10;
np->full_duplex = 0;
break;
case SPEED_10 + DUPLEX_FULL:
np->speed = 10;
np->full_duplex = 1;
break;
case SPEED_100 + DUPLEX_HALF:
np->speed = 100;
np->full_duplex = 0;
break;
case SPEED_100 + DUPLEX_FULL:
np->speed = 100;
np->full_duplex = 1;
break;
default:
return -EINVAL;
}
mdio_write (dev, np->phys[0], MII_BMCR,
((np->speed == 100) ? BMCR_SPEED100 : 0) |
((np->full_duplex) ? BMCR_FULLDPLX : 0) );
}
return 0;
}
#ifdef ETHTOOL_GLINK
case ETHTOOL_GLINK:{
struct ethtool_value link = { ETHTOOL_GLINK };
link.data = readb(ioaddr + MIICtrl) & 0x80;
if (copy_to_user(useraddr, &link, sizeof(link)))
return -EFAULT; return -EFAULT;
return 0; return 0;
} }
#endif
default:
return -EOPNOTSUPP;
} }
return -EOPNOTSUPP;
} }
static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
...@@ -1460,10 +1679,10 @@ static void __devexit sundance_remove1 (struct pci_dev *pdev) ...@@ -1460,10 +1679,10 @@ static void __devexit sundance_remove1 (struct pci_dev *pdev)
} }
static struct pci_driver sundance_driver = { static struct pci_driver sundance_driver = {
.name = DRV_NAME, name: DRV_NAME,
.id_table = sundance_pci_tbl, id_table: sundance_pci_tbl,
.probe = sundance_probe1, probe: sundance_probe1,
.remove = __devexit_p(sundance_remove1), remove: __devexit_p(sundance_remove1),
}; };
static int __init sundance_init(void) static int __init sundance_init(void)
...@@ -1482,3 +1701,5 @@ static void __exit sundance_exit(void) ...@@ -1482,3 +1701,5 @@ static void __exit sundance_exit(void)
module_init(sundance_init); module_init(sundance_init);
module_exit(sundance_exit); module_exit(sundance_exit);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment