[e100 netdev-2.6 1/2] fix slab corruption

From: Feldman, Scott <hidden>
Date: 2004-01-13 19:28:38

* Addresses two problems, both resulting in slab corruption: 1)
  driver indicating skb while HW is still DMA'ing (ouch!), 2)
  driver not stopping receiver activity before downing i/f.
  Fix is 1) wait for RNR (receiver-no-resources) interrupt
  before restarting receiver, 2) reseting HW to stop receiver
  before stopping i/f.

----------
--- net-drivers-2.5-exp/drivers/net/e100.c.orig	2004-01-09 16:41:42.000000000 -0800
+++ net-drivers-2.5-exp/drivers/net/e100.c	2004-01-09 16:44:59.000000000 -0800
@@ -143,7 +143,6 @@
 #include <linux/delay.h>
 #include <linux/init.h>
 #include <linux/pci.h>
-#include <linux/list.h>
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/mii.h>
@@ -273,11 +272,13 @@
 };
 
 enum scb_stat_ack {
+	stat_ack_not_ours    = 0x00,
 	stat_ack_sw_gen      = 0x04,
 	stat_ack_rnr         = 0x10,
 	stat_ack_cu_idle     = 0x20,
 	stat_ack_frame_rx    = 0x40,
 	stat_ack_cu_cmd_done = 0x80,
+	stat_ack_not_present = 0xFF,
 	stat_ack_rx = (stat_ack_sw_gen | stat_ack_rnr | stat_ack_frame_rx),
 	stat_ack_tx = (stat_ack_cu_idle | stat_ack_cu_cmd_done),
 };
@@ -367,11 +368,10 @@
 	u16 size;
 };
 
-struct rx_list {
-	struct list_head list;
+struct rx {
+	struct rx *next, *prev;
 	struct sk_buff *skb;
 	dma_addr_t dma_addr;
-	unsigned int length;
 };
 
 #if defined(__BIG_ENDIAN_BITFIELD)
@@ -492,11 +492,13 @@
 	u32 msg_enable				____cacheline_aligned;
 	struct net_device *netdev;
 	struct pci_dev *pdev;
-	
-	struct list_head rx_list_head		____cacheline_aligned;
-	struct rx_list *rx_list;
+
+	struct rx *rxs				____cacheline_aligned;
+	struct rx *rx_to_use;
+	struct rx *rx_to_clean;
 	struct rfd blank_rfd;
-	
+	int ru_running;
+
 	spinlock_t cb_lock			____cacheline_aligned;
 	spinlock_t cmd_lock;
 	struct csr *csr;
@@ -1350,57 +1352,46 @@
 
 static inline void e100_start_receiver(struct nic *nic)
 {
-	/* (Re)start RU if suspended or idle and RFA is fully allocated */
-	struct rx_list *curr =
-		list_entry(nic->rx_list_head.next, struct rx_list, list);
-	if(curr->skb) {
-		u8 status = readb(&nic->csr->scb.status);
-		if(unlikely((status & rus_mask) != rus_ready))
-			e100_exec_cmd(nic, ruc_start, curr->dma_addr);
+	/* (Re)start RU if suspended or idle and RFA is non-NULL */
+	if(!nic->ru_running && nic->rx_to_clean->skb) {
+		e100_exec_cmd(nic, ruc_start, nic->rx_to_clean->dma_addr);
+		nic->ru_running = 1;
 	}
 }
 
-static inline int e100_rx_alloc_skb(struct nic *nic, struct rx_list *curr)
+#define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)
+static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)
 {
 	unsigned int rx_offset = 2; /* u32 align protocol headers */
 
-	curr->dma_addr = 0;
-	curr->length = sizeof(struct rfd) + VLAN_ETH_FRAME_LEN;
-
-	if(!(curr->skb = dev_alloc_skb(curr->length + rx_offset)))
+	if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + rx_offset)))
 		return -ENOMEM;
 
-	skb_reserve(curr->skb, rx_offset);
-	curr->skb->dev = nic->netdev;
-	curr->dma_addr = pci_map_single(nic->pdev, curr->skb->data,
-		curr->length, PCI_DMA_FROMDEVICE);
-
-	return 0;
-}
-
-static inline void e100_rx_rfa_add_tail(struct nic *nic, struct rx_list *curr)
-{
-	memcpy(curr->skb->data, &nic->blank_rfd, sizeof(struct rfd));
-	pci_dma_sync_single(nic->pdev, curr->dma_addr, 
-		sizeof(struct rfd), PCI_DMA_TODEVICE);
+	/* Align, init, and map the RFA. */
+	rx->skb->dev = nic->netdev;
+	skb_reserve(rx->skb, rx_offset);
+	memcpy(rx->skb->data, &nic->blank_rfd, sizeof(struct rfd));
+	rx->dma_addr = pci_map_single(nic->pdev, rx->skb->data,
+		RFD_BUF_LEN, PCI_DMA_FROMDEVICE);
 
-	if(likely(curr->list.prev != &nic->rx_list_head)) {
-		struct rx_list *prev = (struct rx_list *)curr->list.prev;
-		if(likely(prev->skb != NULL)) {
-			struct rfd *prev_rfd = (struct rfd *)prev->skb->data;
-			put_unaligned(cpu_to_le32(curr->dma_addr),
-				(u32 *)&prev_rfd->link);
-			prev_rfd->command = 0;
-			pci_dma_sync_single(nic->pdev, prev->dma_addr, 
-				sizeof(struct rfd), PCI_DMA_TODEVICE);
-		}
+	/* Link the RFD to end of RFA by linking previous RFD to
+	 * this one, and clearing EL bit of previous.  */
+	if(rx->prev->skb) {
+		struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->data;
+		put_unaligned(cpu_to_le32(rx->dma_addr),
+			(u32 *)&prev_rfd->link);
+		prev_rfd->command &= ~cpu_to_le16(cb_el);
+		pci_dma_sync_single(nic->pdev, rx->prev->dma_addr,
+			sizeof(struct rfd), PCI_DMA_TODEVICE);
 	}
+
+	return 0;
 }
 
-static inline int e100_rx_indicate(struct nic *nic, struct rx_list *curr,
+static inline int e100_rx_indicate(struct nic *nic, struct rx *rx,
 	unsigned int *work_done, unsigned int work_to_do)
 {
-	struct sk_buff *skb = curr->skb;
+	struct sk_buff *skb = rx->skb;
 	struct rfd *rfd = (struct rfd *)skb->data;
 	u16 rfd_status, actual_size;
 
@@ -1408,7 +1399,7 @@
 		return -EAGAIN;
 
 	/* Need to sync before taking a peek at cb_complete bit */
-	pci_dma_sync_single(nic->pdev, curr->dma_addr,
+	pci_dma_sync_single(nic->pdev, rx->dma_addr,
 		sizeof(struct rfd), PCI_DMA_FROMDEVICE);
 	rfd_status = le16_to_cpu(rfd->status);
 
@@ -1420,15 +1411,15 @@
 
 	/* Get actual data size */
 	actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;
-	if(unlikely(actual_size > curr->length - sizeof(struct rfd)))
-		actual_size = curr->length - sizeof(struct rfd);
+	if(unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))
+		actual_size = RFD_BUF_LEN - sizeof(struct rfd);
 
 	/* Get data */
-	pci_dma_sync_single(nic->pdev, curr->dma_addr,
+	pci_dma_sync_single(nic->pdev, rx->dma_addr,
 		sizeof(struct rfd) + actual_size,
 		PCI_DMA_FROMDEVICE);
-	pci_unmap_single(nic->pdev, curr->dma_addr,
-		curr->length, PCI_DMA_FROMDEVICE);
+	pci_unmap_single(nic->pdev, rx->dma_addr,
+		RFD_BUF_LEN, PCI_DMA_FROMDEVICE);
 
 	/* Pull off the RFD and put the actual data (minus eth hdr) */
 	skb_reserve(skb, sizeof(struct rfd));
@@ -1452,37 +1443,26 @@
 			(*work_done)++;
 	}
 
-	curr->length = 0;
-	curr->dma_addr = 0;
-	curr->skb = NULL;
-	
+	rx->skb = NULL;
+
 	return 0;
 }
 
 static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,
 	unsigned int work_to_do)
 {
-	struct list_head *list, *tmp;
-	struct rx_list *curr;
+	struct rx *rx;
 
 	/* Indicate newly arrived packets */
-	list_for_each(list, &nic->rx_list_head) {
-		curr = list_entry(list, struct rx_list, list);
-		if(likely(curr->skb != NULL))
-			if(e100_rx_indicate(nic, curr, work_done, work_to_do))
-				break;
+	for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {
+		if(e100_rx_indicate(nic, rx, work_done, work_to_do))
+			break; /* No more to clean */
 	}
 
 	/* Alloc new skbs to refill list */
-	list_for_each_safe(list, tmp, &nic->rx_list_head) {
-		curr = list_entry(list, struct rx_list, list);
-		if(unlikely(curr->skb != NULL))
-			break; /* List is full, done */
-		if(unlikely(e100_rx_alloc_skb(nic, curr)))
+	for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {
+		if(unlikely(e100_rx_alloc_skb(nic, rx)))
 			break; /* Better luck next time (see watchdog) */
-		list_del(&curr->list);
-		list_add_tail(&curr->list, &nic->rx_list_head);
-		e100_rx_rfa_add_tail(nic, curr);
 	}
 
 	e100_start_receiver(nic);
@@ -1490,44 +1470,47 @@
 
 static void e100_rx_clean_list(struct nic *nic)
 {
-	struct list_head *list;
-	
-	if(!nic->rx_list)
-		return;
+	struct rx *rx;
+	unsigned int i, count = nic->params.rfds.count;
 
-	list_for_each(list, &nic->rx_list_head) {
-		struct rx_list *curr = list_entry(list, 
-			struct rx_list, list);
-		if(curr->skb) {
-			pci_unmap_single(nic->pdev, curr->dma_addr,
-				curr->length, PCI_DMA_FROMDEVICE);
-			dev_kfree_skb(curr->skb);
+	if(nic->rxs) {
+		for(rx = nic->rxs, i = 0; i < count; rx++, i++) {
+			if(rx->skb) {
+				pci_unmap_single(nic->pdev, rx->dma_addr,
+					RFD_BUF_LEN, PCI_DMA_FROMDEVICE);
+				dev_kfree_skb(rx->skb);
+			}
 		}
+		kfree(nic->rxs);
+		nic->rxs = NULL;
 	}
 
-	kfree(nic->rx_list);
-	nic->rx_list = NULL;
+	nic->rx_to_use = nic->rx_to_clean = NULL;
+	nic->ru_running = 0;
 }
 
 static int e100_rx_alloc_list(struct nic *nic)
 {
-	struct rx_list *curr;
+	struct rx *rx;
 	unsigned int i, count = nic->params.rfds.count;
-	
-	INIT_LIST_HEAD(&nic->rx_list_head);
 
-	if(!(nic->rx_list = kmalloc(sizeof(struct rx_list)*count, GFP_ATOMIC)))
+	nic->rx_to_use = nic->rx_to_clean = NULL;
+
+	if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))
 		return -ENOMEM;
+	memset(nic->rxs, 0, sizeof(struct rx) * count);
 
-	for(curr = nic->rx_list, i = 0; i < count; curr++, i++) {
-		if(e100_rx_alloc_skb(nic, curr)) {
+	for(rx = nic->rxs, i = 0; i < count; rx++, i++) {
+		rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;
+		rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;
+		if(e100_rx_alloc_skb(nic, rx)) {
 			e100_rx_clean_list(nic);
 			return -ENOMEM;
 		}
-		list_add_tail(&curr->list, &nic->rx_list_head);
-		e100_rx_rfa_add_tail(nic, curr);
 	}
-	
+
+	nic->rx_to_use = nic->rx_to_clean = nic->rxs;
+
 	return 0;
 }
 
@@ -1539,13 +1522,16 @@
 
 	DPRINTK(INTR, DEBUG, "stat_ack = 0x%02X\n", stat_ack);
 
-	if(stat_ack == 0x00 ||	/* Not our interrupt */
-	   stat_ack == 0xFF)	/* Hardware is ejected (cardbus, hotswap)  */
+	if(stat_ack == stat_ack_not_ours ||	/* Not our interrupt */
+	   stat_ack == stat_ack_not_present)	/* Hardware is ejected */
 		return IRQ_NONE;
 
-	/* Ack interrupts */
+	/* Ack interrupt(s) */
 	writeb(stat_ack, &nic->csr->scb.stat_ack);
-	e100_write_flush(nic);
+
+	/* We hit Receive No Resource (RNR); restart RU after cleaning */
+	if(stat_ack & stat_ack_rnr)
+		nic->ru_running = 0;
 
 #ifdef CONFIG_E100_NAPI
 	e100_disable_irq(nic);
@@ -1664,7 +1650,7 @@
 
 static void e100_down(struct nic *nic)
 {
-	e100_disable_irq(nic);
+	e100_hw_reset(nic);
 	free_irq(nic->pdev->irq, nic->netdev);
 	del_timer_sync(&nic->watchdog);
 	netif_carrier_off(nic->netdev);
@@ -1687,7 +1673,6 @@
 {
 	int err;
 	struct sk_buff *skb;
-	struct rx_list *rx;
 
 	/* Use driver resources to perform internal MAC or PHY
 	 * loopback test.  A single packet is prepared and transmitted
@@ -1724,8 +1709,8 @@
 	set_current_state(TASK_UNINTERRUPTIBLE);
 	schedule_timeout(HZ / 100 + 1);
 
-	rx = list_entry(nic->rx_list_head.next, struct rx_list, list);
-	if(memcmp(rx->skb->data + sizeof(struct rfd), skb->data, ETH_DATA_LEN))
+	if(memcmp(nic->rx_to_clean->skb->data + sizeof(struct rfd),
+	   skb->data, ETH_DATA_LEN))
        		err = -EAGAIN;
 
 err_loopback_none:
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help