patch-2.2.0-pre1 linux/drivers/net/8390.c

Next file: linux/drivers/net/8390.h
Previous file: linux/drivers/net/3c523.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.132/linux/drivers/net/8390.c linux/drivers/net/8390.c
@@ -37,7 +37,7 @@
   			  68K Macintosh. Support >16bit I/O spaces
   Paul Gortmaker	: add kmod support for auto-loading of the 8390
 			  module by all drivers that require it.
-
+  Alan Cox		: Spinlocking work, added 'BUG_83C690'
 
   Sources:
   The National Semiconductor LAN Databook, and the 3Com 3c503 databook.
@@ -72,6 +72,8 @@
 #define NS8390_CORE
 #include "8390.h"
 
+#define BUG_83C690
+
 /* These are the operational function interfaces to board-specific
    routines.
 	void reset_8390(struct device *dev)
@@ -110,6 +112,34 @@
 static void NS8390_trigger_send(struct device *dev, unsigned int length,
 								int start_page);
 static void set_multicast_list(struct device *dev);
+static void do_set_multicast_list(struct device *dev);
+
+/*
+ *	SMP and the 8390 setup.
+ *
+ *	The 8390 isnt exactly designed to be multithreaded on RX/TX. There is
+ *	a page register that controls bank and packet buffer access. We guard
+ *	this with ei_local->page_lock. Nobody should assume or set the page other
+ *	than zero when the lock is not held. Lock holders must restore page 0
+ *	before unlocking. Even pure readers must take the lock to protect in 
+ *	page 0.
+ *
+ *	To make life difficult the chip can also be very slow. We therefore can't
+ *	just use spinlocks. For the longer lockups we disable the irq the device
+ *	sits on and hold the lock. We must hold the lock because there is a dual
+ *	processor case other than interrupts (get stats/set multicast list in
+ *	parallel with each other and transmit).
+ *
+ *	Note: in theory we can just disable the irq on the card _but_ there is
+ *	a latency on SMP irq delivery. So we can easily go "disable irq" "sync irqs"
+ *	enter lock, take the queued irq. So we waddle instead of flying.
+ *
+ *	Finally by special arrangement for the purpose of being generally 
+ *	annoying the transmit function is called bh atomic. That places
+ *	restrictions on the user context callers as disable_irq won't save
+ *	them.
+ */
+ 
 
 
 /* Open/initialize the board.  This routine goes all-out, setting everything
@@ -118,6 +148,7 @@
    */
 int ei_open(struct device *dev)
 {
+	unsigned long flags;
 	struct ei_device *ei_local = (struct ei_device *) dev->priv;
 
 	/* This can't happen unless somebody forgot to call ethdev_init(). */
@@ -127,7 +158,14 @@
 		return -ENXIO;
 	}
     
+	/*
+	 *	Grab the page lock so we own the register set, then call
+	 *	the init function.
+	 */
+      
+      	spin_lock_irqsave(&ei_local->page_lock, flags);
 	NS8390_init(dev, 1);
+      	spin_unlock_irqrestore(&ei_local->page_lock, flags);
 	dev->start = 1;
 	ei_local->irqlock = 0;
 	return 0;
@@ -136,7 +174,16 @@
 /* Opposite of above. Only used when "ifconfig <devname> down" is done. */
 int ei_close(struct device *dev)
 {
+	struct ei_device *ei_local = (struct ei_device *) dev->priv;
+	unsigned long flags;
+
+	/*
+	 *	Hold the page lock during close
+	 */
+	 	
+      	spin_lock_irqsave(&ei_local->page_lock, flags);
 	NS8390_init(dev, 0);
+      	spin_unlock_irqrestore(&ei_local->page_lock, flags);
 	dev->start = 0;
 	return 0;
 }
@@ -146,24 +193,39 @@
 	int e8390_base = dev->base_addr;
 	struct ei_device *ei_local = (struct ei_device *) dev->priv;
 	int length, send_length, output_page;
+	unsigned long flags;
 
 	/*
 	 *  We normally shouldn't be called if dev->tbusy is set, but the
 	 *  existing code does anyway. If it has been too long since the
-	 *  last Tx, we assume the board has died and kick it.
+	 *  last Tx, we assume the board has died and kick it. We are
+	 *  bh_atomic here.
 	 */
  
 	if (dev->tbusy) 
 	{	/* Do timeouts, just like the 8003 driver. */
-		int txsr = inb(e8390_base+EN0_TSR), isr;
+		int txsr;
+		int isr;
 		int tickssofar = jiffies - dev->trans_start;
+
+		/*
+		 *	Need the page lock. Now see what went wrong. This bit is
+		 *	fast.
+		 */
+		 		
+		spin_lock_irqsave(&ei_local->page_lock, flags);
+		txsr = inb(e8390_base+EN0_TSR);
 		if (tickssofar < TX_TIMEOUT ||	(tickssofar < (TX_TIMEOUT+5) && ! (txsr & ENTSR_PTX))) 
+		{
+			spin_unlock_irqrestore(&ei_local->page_lock, flags);
 			return 1;
+		}
 
 		ei_local->stat.tx_errors++;
 		isr = inb(e8390_base+EN0_ISR);
 		if (dev->start == 0) 
 		{
+			spin_unlock_irqrestore(&ei_local->page_lock, flags);
 			printk(KERN_WARNING "%s: xmit on stopped card\n", dev->name);
 			return 1;
 		}
@@ -184,22 +246,54 @@
 			ei_local->interface_num ^= 1;   /* Try a different xcvr.  */
 		}
 
+		/*
+		 *	Play shuffle the locks, a reset on some chips takes a few
+		 *	mS. We very rarely hit this point.
+		 */
+		 
+		spin_unlock_irqrestore(&ei_local->page_lock, flags);
+
+		/* Ugly but a reset can be slow, yet must be protected */
+		
+		disable_irq(dev->irq);
+		synchronize_irq();
+		spin_lock(&ei_local->page_lock);
+		
 		/* Try to restart the card.  Perhaps the user has fixed something. */
 		ei_reset_8390(dev);
 		NS8390_init(dev, 1);
+		
+		spin_unlock(&ei_local->page_lock);
+		enable_irq(dev->irq);
 		dev->trans_start = jiffies;
 	}
     
 	length = skb->len;
 
-	/* Mask interrupts from the ethercard. */
+	/* Mask interrupts from the ethercard. 
+	   SMP: We have to grab the lock here otherwise the IRQ handler
+	   on another CPU can flip window and race the IRQ mask set. We end
+	   up trashing the mcast filter not disabling irqs if we dont lock */
+	   
+	spin_lock_irqsave(&ei_local->page_lock, flags);
 	outb_p(0x00, e8390_base + EN0_IMR);
+	spin_unlock_irqrestore(&ei_local->page_lock, flags);
+	
+	
+	/*
+	 *	Slow phase with lock held.
+	 */
+	 
 	disable_irq(dev->irq);
 	synchronize_irq();
+	
+	spin_lock(&ei_local->page_lock);
+	
 	if (dev->interrupt) 
 	{
 		printk(KERN_WARNING "%s: Tx request while isr active.\n",dev->name);
 		outb_p(ENISR_ALL, e8390_base + EN0_IMR);
+		spin_unlock(&ei_local->page_lock);
 		enable_irq(dev->irq);
 		ei_local->stat.tx_errors++;
 		dev_kfree_skb(skb);
@@ -243,6 +337,7 @@
 		ei_local->irqlock = 0;
 		dev->tbusy = 1;
 		outb_p(ENISR_ALL, e8390_base + EN0_IMR);
+		spin_unlock(&ei_local->page_lock);
 		enable_irq(dev->irq);
 		ei_local->stat.tx_errors++;
 		return 1;
@@ -294,6 +389,8 @@
 	/* Turn 8390 interrupts back on. */
 	ei_local->irqlock = 0;
 	outb_p(ENISR_ALL, e8390_base + EN0_IMR);
+	
+	spin_unlock(&ei_local->page_lock);
 	enable_irq(dev->irq);
 
 	dev_kfree_skb (skb);
@@ -320,6 +417,13 @@
     
 	e8390_base = dev->base_addr;
 	ei_local = (struct ei_device *) dev->priv;
+
+	/*
+	 *	Protect the irq test too.
+	 */
+	 
+	spin_lock(&ei_local->page_lock);
+
 	if (dev->interrupt || ei_local->irqlock) 
 	{
 #if 1 /* This might just be an interrupt for a PCI device sharing this line */
@@ -330,15 +434,17 @@
 			   dev->name, inb_p(e8390_base + EN0_ISR),
 			   inb_p(e8390_base + EN0_IMR));
 #endif
+		spin_unlock(&ei_local->page_lock);
 		return;
 	}
     
+	
 	dev->interrupt = 1;
     
 	/* Change to page 0 and read the intr status reg. */
 	outb_p(E8390_NODMA+E8390_PAGE0, e8390_base + E8390_CMD);
 	if (ei_debug > 3)
-		printk("%s: interrupt(isr=%#2.2x).\n", dev->name,
+		printk(KERN_DEBUG "%s: interrupt(isr=%#2.2x).\n", dev->name,
 			   inb_p(e8390_base + EN0_ISR));
     
 	/* !!Assumption!! -- we stay in page 0.	 Don't break this. */
@@ -386,15 +492,16 @@
 		outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, e8390_base + E8390_CMD);
 		if (nr_serviced >= MAX_SERVICE) 
 		{
-			printk("%s: Too much work at interrupt, status %#2.2x\n",
+			printk(KERN_WARNING "%s: Too much work at interrupt, status %#2.2x\n",
 				   dev->name, interrupts);
 			outb_p(ENISR_ALL, e8390_base + EN0_ISR); /* Ack. most intrs. */
 		} else {
-			printk("%s: unknown interrupt %#2x\n", dev->name, interrupts);
+			printk(KERN_WARNING "%s: unknown interrupt %#2x\n", dev->name, interrupts);
 			outb_p(0xff, e8390_base + EN0_ISR); /* Ack. all intrs. */
 		}
 	}
 	dev->interrupt = 0;
+	spin_unlock(&ei_local->page_lock);
 	return;
 }
 
@@ -405,6 +512,8 @@
  * letting the failed packet sit and collect dust in the Tx buffer. This
  * is a much better solution as it avoids kernel based Tx timeouts, and
  * an unnecessary card reset.
+ *
+ * Called with lock held
  */
 
 static void ei_tx_err(struct device *dev)
@@ -443,7 +552,7 @@
 }
 
 /* We have finished a transmit: check for errors and then trigger the next
-   packet to be sent. */
+   packet to be sent. Called with lock held */
 
 static void ei_tx_intr(struct device *dev)
 {
@@ -532,7 +641,8 @@
 	mark_bh (NET_BH);
 }
 
-/* We have a good packet(s), get it/them out of the buffers. */
+/* We have a good packet(s), get it/them out of the buffers. 
+   Called with lock held */
 
 static void ei_receive(struct device *dev)
 {
@@ -659,6 +769,8 @@
  * the updated datasheets, or "the NIC may act in an unpredictable manner."
  * This includes causing "the NIC to defer indefinitely when it is stopped
  * on a busy network."  Ugh.
+ * Called with lock held. Don't call this with the interrupts off or your
+ * computer will hate you - it takes 10mS or so. 
  */
 
 static void ei_rx_overrun(struct device *dev)
@@ -726,19 +838,26 @@
     		outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START + E8390_TRANS, e8390_base + E8390_CMD);
 }
 
+/*
+ *	Collect the stats. This is called unlocked and from several contexts.
+ */
+ 
 static struct net_device_stats *get_stats(struct device *dev)
 {
 	int ioaddr = dev->base_addr;
 	struct ei_device *ei_local = (struct ei_device *) dev->priv;
+	unsigned long flags;
     
 	/* If the card is stopped, just return the present stats. */
 	if (dev->start == 0) 
 		return &ei_local->stat;
 
+	spin_lock_irqsave(&ei_local->page_lock,flags);
 	/* Read the counter registers, assuming we are in page 0. */
 	ei_local->stat.rx_frame_errors += inb_p(ioaddr + EN0_COUNTER0);
 	ei_local->stat.rx_crc_errors   += inb_p(ioaddr + EN0_COUNTER1);
 	ei_local->stat.rx_missed_errors+= inb_p(ioaddr + EN0_COUNTER2);
+	spin_unlock_irqrestore(&ei_local->page_lock, flags);
     
 	return &ei_local->stat;
 }
@@ -794,14 +913,14 @@
 }
 
 /*
- *	Set or clear the multicast filter for this adaptor.
+ *	Set or clear the multicast filter for this adaptor. May be called
+ *	from a BH in 2.1.x. Must be called with lock held. 
  */
  
-static void set_multicast_list(struct device *dev)
+static void do_set_multicast_list(struct device *dev)
 {
 	int e8390_base = dev->base_addr;
 	int i;
-	unsigned long flags;
 	struct ei_device *ei_local = (struct ei_device*)dev->priv;
 
 	if (!(dev->flags&(IFF_PROMISC|IFF_ALLMULTI))) 
@@ -825,21 +944,19 @@
 	 * them as r/w so this is a bug.  The SMC 83C790 (SMC Ultra and
 	 * Ultra32 EISA) appears to have this bug fixed.
 	 */
+	 
 	if (dev->start)
 		outb_p(E8390_RXCONFIG, e8390_base + EN0_RXCR);
-	save_flags(flags);
-	cli();
 	outb_p(E8390_NODMA + E8390_PAGE1, e8390_base + E8390_CMD);
 	for(i = 0; i < 8; i++) 
 	{
 		outb_p(ei_local->mcfilter[i], e8390_base + EN1_MULT_SHIFT(i));
-#ifdef NOT_83C690
+#ifndef BUG_83C690
 		if(inb_p(e8390_base + EN1_MULT_SHIFT(i))!=ei_local->mcfilter[i])
 			printk(KERN_ERR "Multicast filter read/write mismap %d\n",i);
 #endif
 	}
 	outb_p(E8390_NODMA + E8390_PAGE0, e8390_base + E8390_CMD);
-	restore_flags(flags);
 
   	if(dev->flags&IFF_PROMISC)
   		outb_p(E8390_RXCONFIG | 0x18, e8390_base + EN0_RXCR);
@@ -847,12 +964,29 @@
   		outb_p(E8390_RXCONFIG | 0x08, e8390_base + EN0_RXCR);
   	else
   		outb_p(E8390_RXCONFIG, e8390_base + EN0_RXCR);
-}
+ }
+
+/*
+ *	Called without lock held. This is invoked from user context and may
+ *	be parallel to just about everything else. Its also fairly quick and
+ *	not called too often. Must protect against both bh and irq users
+ */
+ 
+static void set_multicast_list(struct device *dev)
+{
+	unsigned long flags;
+	struct ei_device *ei_local = (struct ei_device*)dev->priv;
+	
+	spin_lock_irqsave(&ei_local->page_lock, flags);
+	do_set_multicast_list(dev);
+	spin_unlock_irqrestore(&ei_local->page_lock, flags);
+}	
 
 /*
  * Initialize the rest of the 8390 device structure.  Do NOT __initfunc
  * this, as it is used by 8390 based modular drivers too.
  */
+
 int ethdev_init(struct device *dev)
 {
 	if (ei_debug > 1)
@@ -867,6 +1001,7 @@
 			return -ENOMEM;
 		memset(dev->priv, 0, sizeof(struct ei_device));
 		ei_local = (struct ei_device *)dev->priv;
+		spin_lock_init(&ei_local->page_lock);
 	}
     
 	dev->hard_start_xmit = &ei_start_xmit;
@@ -883,13 +1018,16 @@
 /* This page of functions should be 8390 generic */
 /* Follow National Semi's recommendations for initializing the "NIC". */
 
+/*
+ *	Must be called with lock held.
+ */
+
 void NS8390_init(struct device *dev, int startp)
 {
 	int e8390_base = dev->base_addr;
 	struct ei_device *ei_local = (struct ei_device *) dev->priv;
 	int i;
 	int endcfg = ei_local->word16 ? (0x48 | ENDCFG_WTS) : 0x48;
-	unsigned long flags;
     
 	if(sizeof(struct e8390_pkt_hdr)!=4)
     		panic("8390.c: header struct mispacked\n");    
@@ -914,8 +1052,7 @@
 	outb_p(0x00,  e8390_base + EN0_IMR);
     
 	/* Copy the station address into the DS8390 registers. */
-	save_flags(flags);
-	cli();
+
 	outb_p(E8390_NODMA + E8390_PAGE1 + E8390_STOP, e8390_base+E8390_CMD); /* 0x61 */
 	for(i = 0; i < 6; i++) 
 	{
@@ -926,7 +1063,7 @@
 
 	outb_p(ei_local->rx_start_page, e8390_base + EN1_CURPAG);
 	outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD);
-	restore_flags(flags);
+
 	dev->tbusy = 0;
 	dev->interrupt = 0;
 	ei_local->tx1 = ei_local->tx2 = 0;
@@ -940,18 +1077,20 @@
 		outb_p(E8390_TXCONFIG, e8390_base + EN0_TXCR); /* xmit on. */
 		/* 3c503 TechMan says rxconfig only after the NIC is started. */
 		outb_p(E8390_RXCONFIG, e8390_base + EN0_RXCR); /* rx on,  */
-		set_multicast_list(dev);	/* (re)load the mcast table */
+		do_set_multicast_list(dev);	/* (re)load the mcast table */
 	}
 	return;
 }
 
-/* Trigger a transmit start, assuming the length is valid. */
+/* Trigger a transmit start, assuming the length is valid. 
+   Always called with the page lock held */
+   
 static void NS8390_trigger_send(struct device *dev, unsigned int length,
 								int start_page)
 {
 	int e8390_base = dev->base_addr;
-	struct ei_device *ei_local = (struct ei_device *) dev->priv;
-
+ 	struct ei_device *ei_local = (struct ei_device *) dev->priv;
+   
 	outb_p(E8390_NODMA+E8390_PAGE0, e8390_base+E8390_CMD);
     
 	if (inb_p(e8390_base) & E8390_TRANS) 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov