Commit 8998ae21 authored by Neil Horman's avatar Neil Horman Committed by Jeff Garzik

[PATCH] olympic driver: fix kernel oops on lobe fault

It fixes an oops that results when a lobe fault is detected.  The oops
occurs because a lobe fault triggers an interrupt which is handled
in the current version of the driver by effectively shutting down the
card, and freeing its requisite irq.  The former is fine, the latter
is not, as its illegal to free an irq from within an interrupt context.

I've fixed this bug by removing the call to free_irq from the interrupt
handler (specifically the chunk around line 964 fixes that).  While I
was in there I noticed that there were several other conditions in
the interrupt handler that contained the same condition, so I made
the same fix there.  I re-added.

I also modified the contents of olympic_freemem (the chunk around line
898 to correct a misuse of a pointer after it requisite memory has
been free in the case the the adapter is re-initalized after a fault
to prevent that oops.  And then I clean up the interrupt handler to
simply use olympic_freemem from the close routine since the ring buffer
doesn't need to be freed until the driver is closed.

In addition to these changes I added a call to olympic init in
olympic_open and reset the spinlock so the adapter can be reset and
rejoin the ring without needing to rmmod/insmod the module.  Lastly I
cleaned up the wait queue code so that the close routine didn't have
to wait 60 seconds to close the adapter if a fatal fault has closed
the adapter.
Signed-off-by: default avatarNeil Horman <nhorman@redhat.com>
parent eb3d6012
...@@ -221,6 +221,8 @@ static int __devinit olympic_probe(struct pci_dev *pdev, const struct pci_device ...@@ -221,6 +221,8 @@ static int __devinit olympic_probe(struct pci_dev *pdev, const struct pci_device
olympic_priv = dev->priv ; olympic_priv = dev->priv ;
spin_lock_init(&olympic_priv->olympic_lock) ;
init_waitqueue_head(&olympic_priv->srb_wait); init_waitqueue_head(&olympic_priv->srb_wait);
init_waitqueue_head(&olympic_priv->trb_wait); init_waitqueue_head(&olympic_priv->trb_wait);
#if OLYMPIC_DEBUG #if OLYMPIC_DEBUG
...@@ -311,7 +313,6 @@ static int __devinit olympic_init(struct net_device *dev) ...@@ -311,7 +313,6 @@ static int __devinit olympic_init(struct net_device *dev)
} }
} }
spin_lock_init(&olympic_priv->olympic_lock) ;
/* Needed for cardbus */ /* Needed for cardbus */
if(!(readl(olympic_mmio+BCTL) & BCTL_MODE_INDICATOR)) { if(!(readl(olympic_mmio+BCTL) & BCTL_MODE_INDICATOR)) {
...@@ -442,6 +443,8 @@ static int olympic_open(struct net_device *dev) ...@@ -442,6 +443,8 @@ static int olympic_open(struct net_device *dev)
DECLARE_WAITQUEUE(wait,current) ; DECLARE_WAITQUEUE(wait,current) ;
olympic_init(dev);
if(request_irq(dev->irq, &olympic_interrupt, SA_SHIRQ , "olympic", dev)) { if(request_irq(dev->irq, &olympic_interrupt, SA_SHIRQ , "olympic", dev)) {
return -EAGAIN; return -EAGAIN;
} }
...@@ -898,7 +901,10 @@ static void olympic_freemem(struct net_device *dev) ...@@ -898,7 +901,10 @@ static void olympic_freemem(struct net_device *dev)
int i; int i;
for(i=0;i<OLYMPIC_RX_RING_SIZE;i++) { for(i=0;i<OLYMPIC_RX_RING_SIZE;i++) {
if (olympic_priv->rx_ring_skb[olympic_priv->rx_status_last_received] != NULL) {
dev_kfree_skb_irq(olympic_priv->rx_ring_skb[olympic_priv->rx_status_last_received]); dev_kfree_skb_irq(olympic_priv->rx_ring_skb[olympic_priv->rx_status_last_received]);
olympic_priv->rx_ring_skb[olympic_priv->rx_status_last_received] = NULL;
}
if (olympic_priv->olympic_rx_ring[olympic_priv->rx_status_last_received].buffer != 0xdeadbeef) { if (olympic_priv->olympic_rx_ring[olympic_priv->rx_status_last_received].buffer != 0xdeadbeef) {
pci_unmap_single(olympic_priv->pdev, pci_unmap_single(olympic_priv->pdev,
le32_to_cpu(olympic_priv->olympic_rx_ring[olympic_priv->rx_status_last_received].buffer), le32_to_cpu(olympic_priv->olympic_rx_ring[olympic_priv->rx_status_last_received].buffer),
...@@ -944,9 +950,6 @@ static irqreturn_t olympic_interrupt(int irq, void *dev_id, struct pt_regs *regs ...@@ -944,9 +950,6 @@ static irqreturn_t olympic_interrupt(int irq, void *dev_id, struct pt_regs *regs
/* Hotswap gives us this on removal */ /* Hotswap gives us this on removal */
if (sisr == 0xffffffff) { if (sisr == 0xffffffff) {
printk(KERN_WARNING "%s: Hotswap adapter removal.\n",dev->name) ; printk(KERN_WARNING "%s: Hotswap adapter removal.\n",dev->name) ;
olympic_freemem(dev) ;
free_irq(dev->irq, dev) ;
dev->stop = NULL ;
spin_unlock(&olympic_priv->olympic_lock) ; spin_unlock(&olympic_priv->olympic_lock) ;
return IRQ_NONE; return IRQ_NONE;
} }
...@@ -961,9 +964,7 @@ static irqreturn_t olympic_interrupt(int irq, void *dev_id, struct pt_regs *regs ...@@ -961,9 +964,7 @@ static irqreturn_t olympic_interrupt(int irq, void *dev_id, struct pt_regs *regs
printk(KERN_ERR "The adapter must be reset to clear this condition.\n") ; printk(KERN_ERR "The adapter must be reset to clear this condition.\n") ;
printk(KERN_ERR "Please report this error to the driver maintainer and/\n") ; printk(KERN_ERR "Please report this error to the driver maintainer and/\n") ;
printk(KERN_ERR "or the linux-tr mailing list.\n") ; printk(KERN_ERR "or the linux-tr mailing list.\n") ;
olympic_freemem(dev) ; wake_up_interruptible(&olympic_priv->srb_wait);
free_irq(dev->irq, dev) ;
dev->stop = NULL ;
spin_unlock(&olympic_priv->olympic_lock) ; spin_unlock(&olympic_priv->olympic_lock) ;
return IRQ_HANDLED; return IRQ_HANDLED;
} /* SISR_ERR */ } /* SISR_ERR */
...@@ -1006,9 +1007,6 @@ static irqreturn_t olympic_interrupt(int irq, void *dev_id, struct pt_regs *regs ...@@ -1006,9 +1007,6 @@ static irqreturn_t olympic_interrupt(int irq, void *dev_id, struct pt_regs *regs
writel(readl(olympic_mmio+LAPWWC),olympic_mmio+LAPA); writel(readl(olympic_mmio+LAPWWC),olympic_mmio+LAPA);
adapter_check_area = olympic_priv->olympic_lap + ((readl(olympic_mmio+LAPWWC)) & (~0xf800)) ; adapter_check_area = olympic_priv->olympic_lap + ((readl(olympic_mmio+LAPWWC)) & (~0xf800)) ;
printk(KERN_WARNING "%s: Bytes %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",dev->name, readb(adapter_check_area+0), readb(adapter_check_area+1), readb(adapter_check_area+2), readb(adapter_check_area+3), readb(adapter_check_area+4), readb(adapter_check_area+5), readb(adapter_check_area+6), readb(adapter_check_area+7)) ; printk(KERN_WARNING "%s: Bytes %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",dev->name, readb(adapter_check_area+0), readb(adapter_check_area+1), readb(adapter_check_area+2), readb(adapter_check_area+3), readb(adapter_check_area+4), readb(adapter_check_area+5), readb(adapter_check_area+6), readb(adapter_check_area+7)) ;
olympic_freemem(dev) ;
free_irq(dev->irq, dev) ;
dev->stop = NULL ;
spin_unlock(&olympic_priv->olympic_lock) ; spin_unlock(&olympic_priv->olympic_lock) ;
return IRQ_HANDLED; return IRQ_HANDLED;
} /* SISR_ADAPTER_CHECK */ } /* SISR_ADAPTER_CHECK */
...@@ -1094,34 +1092,32 @@ static int olympic_close(struct net_device *dev) ...@@ -1094,34 +1092,32 @@ static int olympic_close(struct net_device *dev)
writeb(0,srb+1); writeb(0,srb+1);
writeb(OLYMPIC_CLEAR_RET_CODE,srb+2); writeb(OLYMPIC_CLEAR_RET_CODE,srb+2);
add_wait_queue(&olympic_priv->srb_wait,&wait) ;
set_current_state(TASK_INTERRUPTIBLE) ;
spin_lock_irqsave(&olympic_priv->olympic_lock,flags); spin_lock_irqsave(&olympic_priv->olympic_lock,flags);
olympic_priv->srb_queued=1; olympic_priv->srb_queued=1;
writel(LISR_SRB_CMD,olympic_mmio+LISR_SUM); writel(LISR_SRB_CMD,olympic_mmio+LISR_SUM);
spin_unlock_irqrestore(&olympic_priv->olympic_lock,flags); spin_unlock_irqrestore(&olympic_priv->olympic_lock,flags);
t = jiffies ; while(olympic_priv->srb_queued) {
add_wait_queue(&olympic_priv->srb_wait,&wait) ; t = schedule_timeout(60*HZ);
set_current_state(TASK_INTERRUPTIBLE) ;
while(olympic_priv->srb_queued) {
schedule() ;
if(signal_pending(current)) { if(signal_pending(current)) {
printk(KERN_WARNING "%s: SRB timed out.\n",dev->name); printk(KERN_WARNING "%s: SRB timed out.\n",dev->name);
printk(KERN_WARNING "SISR=%x MISR=%x\n",readl(olympic_mmio+SISR),readl(olympic_mmio+LISR)); printk(KERN_WARNING "SISR=%x MISR=%x\n",readl(olympic_mmio+SISR),readl(olympic_mmio+LISR));
olympic_priv->srb_queued=0; olympic_priv->srb_queued=0;
break; break;
} }
if ((jiffies-t) > 60*HZ) {
if (t == 0) {
printk(KERN_WARNING "%s: SRB timed out. May not be fatal. \n",dev->name) ; printk(KERN_WARNING "%s: SRB timed out. May not be fatal. \n",dev->name) ;
olympic_priv->srb_queued=0;
break ;
} }
set_current_state(TASK_INTERRUPTIBLE) ; olympic_priv->srb_queued=0;
} }
remove_wait_queue(&olympic_priv->srb_wait,&wait) ; remove_wait_queue(&olympic_priv->srb_wait,&wait) ;
set_current_state(TASK_RUNNING) ;
olympic_priv->rx_status_last_received++; olympic_priv->rx_status_last_received++;
olympic_priv->rx_status_last_received&=OLYMPIC_RX_RING_SIZE-1; olympic_priv->rx_status_last_received&=OLYMPIC_RX_RING_SIZE-1;
...@@ -1513,29 +1509,6 @@ static void olympic_arb_cmd(struct net_device *dev) ...@@ -1513,29 +1509,6 @@ static void olympic_arb_cmd(struct net_device *dev)
writel(readl(olympic_mmio+BCTL)&~(3<<13),olympic_mmio+BCTL); writel(readl(olympic_mmio+BCTL)&~(3<<13),olympic_mmio+BCTL);
netif_stop_queue(dev); netif_stop_queue(dev);
olympic_priv->srb = readw(olympic_priv->olympic_lap + LAPWWO) ; olympic_priv->srb = readw(olympic_priv->olympic_lap + LAPWWO) ;
for(i=0;i<OLYMPIC_RX_RING_SIZE;i++) {
dev_kfree_skb_irq(olympic_priv->rx_ring_skb[olympic_priv->rx_status_last_received]);
if (olympic_priv->olympic_rx_ring[olympic_priv->rx_status_last_received].buffer != 0xdeadbeef) {
pci_unmap_single(olympic_priv->pdev,
le32_to_cpu(olympic_priv->olympic_rx_ring[olympic_priv->rx_status_last_received].buffer),
olympic_priv->pkt_buf_sz, PCI_DMA_FROMDEVICE);
}
olympic_priv->rx_status_last_received++;
olympic_priv->rx_status_last_received&=OLYMPIC_RX_RING_SIZE-1;
}
/* unmap rings */
pci_unmap_single(olympic_priv->pdev, olympic_priv->rx_status_ring_dma_addr,
sizeof(struct olympic_rx_status) * OLYMPIC_RX_RING_SIZE, PCI_DMA_FROMDEVICE);
pci_unmap_single(olympic_priv->pdev, olympic_priv->rx_ring_dma_addr,
sizeof(struct olympic_rx_desc) * OLYMPIC_RX_RING_SIZE, PCI_DMA_TODEVICE);
pci_unmap_single(olympic_priv->pdev, olympic_priv->tx_status_ring_dma_addr,
sizeof(struct olympic_tx_status) * OLYMPIC_TX_RING_SIZE, PCI_DMA_FROMDEVICE);
pci_unmap_single(olympic_priv->pdev, olympic_priv->tx_ring_dma_addr,
sizeof(struct olympic_tx_desc) * OLYMPIC_TX_RING_SIZE, PCI_DMA_TODEVICE);
free_irq(dev->irq,dev);
dev->stop=NULL;
printk(KERN_WARNING "%s: Adapter has been closed \n", dev->name) ; printk(KERN_WARNING "%s: Adapter has been closed \n", dev->name) ;
} /* If serious error */ } /* If serious error */
......
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