Commit 5f0ccf26 authored by Don Fry's avatar Don Fry Committed by Stephen Hemminger

[PATCH] 2.6.3 pcnet32.c transmit hang fix

The transmit routine will stop interrupting and hang, causing the
tx_timeout routine to attempt to restart the device when the 32-bit
cur_tx counter wraps below dirty_tx.  If the device had called
netif_stop_queue it will never call netif_wake_queue in the interrupt
routine (at least on an IA32 system) due to 32-bit wrap around arithmetic.

On my IA32 system 'dirty_tx > lp->cur_tx - TX_RING_SIZE + 2' would
always evaluate to false when dirty and cur_tx were less than 15,
preventing netif_wake_queue to be called.

By starting dirty_tx and cur_tx at 0xfffffff0 (to reduce test time) I
found that once cur_tx wrapped to zero, that transmitted buffers would
never be unmapped or freed because 'while (dirty_tx < lp->cur_tx) {'
was not true.  cur_tx would keep incrementing (in start_xmit) but dirty_tx
would not (in pcnet32_interrupt), thus leaking skb's and pci map entries.
On PPC machines, the system would quickly run out of pci maps.

Fix tested on PPC and IA32.
parent caddcf6d
...@@ -1082,7 +1082,7 @@ pcnet32_tx_timeout (struct net_device *dev) ...@@ -1082,7 +1082,7 @@ pcnet32_tx_timeout (struct net_device *dev)
pcnet32_restart(dev, 0x0042); pcnet32_restart(dev, 0x0042);
dev->trans_start = jiffies; dev->trans_start = jiffies;
netif_start_queue(dev); netif_wake_queue(dev);
spin_unlock_irqrestore(&lp->lock, flags); spin_unlock_irqrestore(&lp->lock, flags);
} }
...@@ -1108,9 +1108,10 @@ pcnet32_start_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -1108,9 +1108,10 @@ pcnet32_start_xmit(struct sk_buff *skb, struct net_device *dev)
* interrupt when that option is available to us. * interrupt when that option is available to us.
*/ */
status = 0x8300; status = 0x8300;
entry = (lp->cur_tx - lp->dirty_tx) & TX_RING_MOD_MASK;
if ((lp->ltint) && if ((lp->ltint) &&
((lp->cur_tx - lp->dirty_tx == TX_RING_SIZE/2) || ((entry == TX_RING_SIZE/2) ||
(lp->cur_tx - lp->dirty_tx >= TX_RING_SIZE-2))) (entry >= TX_RING_SIZE-2)))
{ {
/* Enable Successful-TxDone interrupt if we have /* Enable Successful-TxDone interrupt if we have
* 1/2 of, or nearly all of, our ring buffer Tx'd * 1/2 of, or nearly all of, our ring buffer Tx'd
...@@ -1125,7 +1126,7 @@ pcnet32_start_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -1125,7 +1126,7 @@ pcnet32_start_xmit(struct sk_buff *skb, struct net_device *dev)
/* Mask to ring buffer boundary. */ /* Mask to ring buffer boundary. */
entry = lp->cur_tx & TX_RING_MOD_MASK; 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. */ with the "ownership" bits last. */
lp->tx_ring[entry].length = le16_to_cpu(-skb->len); lp->tx_ring[entry].length = le16_to_cpu(-skb->len);
...@@ -1147,7 +1148,7 @@ pcnet32_start_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -1147,7 +1148,7 @@ pcnet32_start_xmit(struct sk_buff *skb, struct net_device *dev)
dev->trans_start = jiffies; dev->trans_start = jiffies;
if (lp->tx_ring[(entry+1) & TX_RING_MOD_MASK].base == 0) if (lp->tx_ring[(entry+1) & TX_RING_MOD_MASK].base == 0)
netif_start_queue(dev); netif_wake_queue(dev);
else { else {
lp->tx_full = 1; lp->tx_full = 1;
netif_stop_queue(dev); netif_stop_queue(dev);
...@@ -1194,8 +1195,9 @@ pcnet32_interrupt(int irq, void *dev_id, struct pt_regs * regs) ...@@ -1194,8 +1195,9 @@ pcnet32_interrupt(int irq, void *dev_id, struct pt_regs * regs)
if (csr0 & 0x0200) { /* Tx-done interrupt */ if (csr0 & 0x0200) { /* Tx-done interrupt */
unsigned int dirty_tx = lp->dirty_tx; 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 entry = dirty_tx & TX_RING_MOD_MASK;
int status = (short)le16_to_cpu(lp->tx_ring[entry].status); int status = (short)le16_to_cpu(lp->tx_ring[entry].status);
...@@ -1249,15 +1251,17 @@ pcnet32_interrupt(int irq, void *dev_id, struct pt_regs * regs) ...@@ -1249,15 +1251,17 @@ pcnet32_interrupt(int irq, void *dev_id, struct pt_regs * regs)
dirty_tx++; 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", 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); dev->name, dirty_tx, lp->cur_tx, lp->tx_full);
dirty_tx += TX_RING_SIZE; dirty_tx += TX_RING_SIZE;
delta -= TX_RING_SIZE;
} }
if (lp->tx_full && if (lp->tx_full &&
netif_queue_stopped(dev) && 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. */ /* The ring is no longer full, clear tbusy. */
lp->tx_full = 0; lp->tx_full = 0;
netif_wake_queue (dev); netif_wake_queue (dev);
......
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