Patch tulip.c [2.2.9]

Keith Owens kaos@ocs.com.au
Sun Jun 6 12:10:24 1999


While testing a Xircom PCMCIA RBEM56G card (21143 based), I hit
problems with the tulip driver.  The patch below describes the problem
and my fix.  Anybody else having problems with tulips might want to try
this patch, see if fixes your problems.  I'm no tulip expert so this
patch could be a pile of crud but it definitely fixed my RBEM56G
problems.  If this works, I'll send it to the tulip maintainer.

If you load tulip as a module, add "strict_csr6=1" to the options for
tulip.  If tulip is builtin, you will have to edit tulip.c by hand to
set strict_csr6 to 1, I did not want to change boot code just for this.

ps.  I have the Xircom Realport RBEM56G working with 10 Mb, 100 Mb and
56K modem :)!  But please don't ask "how do I get my Xircom RBEM56
working?", a bigger patch including more Xircom code is almost ready
for release.

--- linux-2.2.9/drivers/net/tulip.c	Fri Feb  5 09:47:48 1999
+++ linux/drivers/net/tulip.c	Mon Jun  7 00:38:00 1999
@@ -15,6 +15,8 @@
 
 	Support and updates available at
 	http://cesdis.gsfc.nasa.gov/linux/drivers/tulip.html
+
+	Tweaks to CSR6 by Keith Owens <kaos@ocs.com.au>
 */
 
 #define SMP_CHECK
@@ -431,6 +433,106 @@
 static void set_rx_mode(struct device *dev, int num_addrs, void *addrs);
 #endif
 
+/* The 21143 documentation (27807401.pdf from www.intel.com) is a bit vague
+   about when CSR6[13] (Start/Stop transmitter) and CSR6[1] (Start/Stop
+   receiver) can be issued.  The Xircom RBEM56 documentation (103-0548-001B.pdf
+   from www.xircom.com) is more explicit and says that these bits should only be
+   set when the transmitter (receiver) is in the stopped state.  Bitter
+   experience has shown that this is necessary for the Xircom.  If you start the
+   RBEM56 when it is already running, it gets confused about the state of the
+   transmit and receive rings and frames are lost.
+
+   Most lost frames are not a problem, TCP or application retransmission will
+   take care of it.  However if the lost frame is a setup packet then the MAC
+   filters are not setup correctly and there is no automatic recovery.  Symptoms
+   include no response to ARP and packets being ignored on receive.  Mail on
+   linux-net indicates that this *might* be a problem for other tulip cards.
+
+   27807401.pdf, table 3-76 says that certain bits can only be changed when the
+   transmitter and/or receiver are stopped.  103-0548-001B.pdf contains similar
+   restrictions.  I (kaos) have not seen these bits cause problems but, since a
+   workaround is required for bits 13 and 1 on the Xircom, I also enforce the
+   documented rules for all CSR6 bits.
+
+   All output to CSR6 is done with outl_CSR6.  If the "strict_csr6" flag is zero
+   then this is equivalent to a normal outl.  When "strict_csr6" is nonzero and
+   the new value for CSR6 will change any of the restricted bits then outl_CSR6
+   stops the transmitter and receiver first.  Only after CSR5 says that activity
+   has stopped does outl_CSR6 send the new value.  Users of RBEM56 (and probably
+   all Xircom Cardbus cards based on tulip chips) should include
+
+     module "tulip_cb" opts "strict_csr6=1"
+
+   in /etc/pcmcia/config.opts.
+
+   Stopping both transmitter and receiver might be overkill.  Most of the
+   restricted bits say both must be stopped, a few bits can be changed with one
+   of transmitter or receiver still running.  However the Intel and Xircom docs
+   disagree, for example Intel says both must be stopped to change CSR6[22],
+   Xircom says just the transmitter must be stopped.  To be safe, always stop
+   both when strict_csr6 is set.  In any case, the restricted bits are rarely
+   changed.
+
+   Alas, even checking for change of restricted bits is not quite enough for
+   RBEM56.  It can get wedged setting CSR6[13] or CSR6[1], even when they are
+   already set.  When trying to outl_CSR6 with those bits set, always stop the
+   transmitter and receiver, even if these bits are already set.
+
+   Messy.  Keith Owens <kaos@ocs.com.au>.
+ */
+
+static int strict_csr6 = 0;  /* set to 1 for problem builtin cards */
+
+static void outl_CSR6(u32 newcsr6, long ioaddr)
+{
+    const static restricted_bits = ( (1 << 22)
+				   | (1 << 21)
+				   | (1 << 17)
+				   | (1 << 16)
+				   | (1 << 15)
+				   | (1 << 14)
+				   | (1 << 13)
+				   | (1 << 12)
+				   | (1 << 11)
+				   | (1 << 10)
+				   | (1 << 9)
+				   | (1 << 8)
+				   | (1 << 5)
+				   | (1 << 1)
+				   );
+    int csr5, csr5_22_20, csr5_19_17, currcsr6, attempts = 200;
+    if (!strict_csr6) {
+	outl(newcsr6, ioaddr + CSR6);
+	return;
+    }
+    currcsr6 = inl(ioaddr + CSR6);
+    if ((newcsr6 & restricted_bits) == (currcsr6 & restricted_bits & ~0x2002)) {
+	outl(newcsr6, ioaddr + CSR6);	/* safe */
+	return;
+    }
+    /* make sure the transmitter and receiver are stopped first */
+    currcsr6 &= ~0x2002;
+    while (1) {
+	outl(currcsr6, ioaddr + CSR6);
+	csr5 = inl(ioaddr + CSR5);
+	if (csr5 == 0xffffffff)
+	    break;  /* cannot read csr5, card removed? */
+	csr5_22_20 = csr5 & 0x700000;
+	csr5_19_17 = csr5 & 0x0e0000;
+	if ((csr5_22_20 == 0 || csr5_22_20 == 0x600000) &
+	    (csr5_19_17 == 0 || csr5_19_17 == 0x80000 || csr5_19_17 == 0xc0000))
+	    break;  /* both are stopped or suspended */
+	if (!--attempts) {
+	    printk(KERN_INFO
+		"tulip.c: outl_CSR6 too many attempts, csr5=0x%08x\n", csr5);
+	    outl(newcsr6, ioaddr + CSR6);  /* unsafe but do it anyway */
+	    return;
+	}
+    }
+    /* now it is safe to change csr6 */
+    outl(newcsr6, ioaddr + CSR6);
+}
+
 
 
 /* A list of all installed Tulip devices, for removing the driver module. */
@@ -588,7 +690,7 @@
 		   dev->name, tulip_tbl[chip_id].chip_name, ioaddr);
 
 	/* Stop the chip's Tx and Rx processes. */
-	outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6);
+	outl_CSR6(inl(ioaddr + CSR6) & ~0x2002, ioaddr);
 	/* Clear the missed-packet counter. */
 	(volatile int)inl(ioaddr + CSR8);
 
@@ -790,7 +892,7 @@
 		outl(0x00000000, ioaddr + CSR13);
 		outl(0xFFFFFFFF, ioaddr + CSR14);
 		outl(0x00000008, ioaddr + CSR15); /* Listen on AUI also. */
-		outl(inl(ioaddr + CSR6) | 0x0200, ioaddr + CSR6);
+		outl_CSR6(inl(ioaddr + CSR6) | 0x0200, ioaddr);
 		outl(0x0000EF05, ioaddr + CSR13);
 		break;
 	case DC21040:
@@ -802,7 +904,7 @@
 			outl(tp->mtable->csr12dir | 0x100, ioaddr + CSR12);
 		break;
 	case DC21142:
-		outl(0x82420200, ioaddr + CSR6);
+		outl_CSR6(0x82420200, ioaddr);
 		outl(0x0001, ioaddr + CSR13);
 		outl(0x0003FFFF, ioaddr + CSR14);
 		outl(0x0008, ioaddr + CSR15);
@@ -811,14 +913,14 @@
 		break;
 	case LC82C168:
 		if ( ! tp->mii_cnt) {
-			outl(0x00420000, ioaddr + CSR6);
+			outl_CSR6(0x00420000, ioaddr);
 			outl(0x30, ioaddr + CSR12);
 			outl(0x0001F078, ioaddr + 0xB8);
 			outl(0x0201F078, ioaddr + 0xB8); /* Turn on autonegotiation. */
 		}
 		break;
 	case MX98713: case MX98715: case MX98725:
-		outl(0x00000000, ioaddr + CSR6);
+		outl_CSR6(0x00000000, ioaddr);
 		outl(0x000711C0, ioaddr + CSR14); /* Turn on NWay. */
 		outl(0x00000001, ioaddr + CSR13);
 		break;
@@ -1209,7 +1311,7 @@
 
 	/* On some chip revs we must set the MII/SYM port before the reset!? */
 	if (tp->mii_cnt  ||  (tp->mtable  &&  tp->mtable->has_mii))
-		outl(0x00040000, ioaddr + CSR6);
+		outl_CSR6(0x00040000, ioaddr);
 
 	/* Reset the chip, holding bit 0 set at least 50 PCI cycles. */
 	outl(0x00000001, ioaddr + CSR0);
@@ -1348,8 +1450,8 @@
 		select_media(dev, 1);
 
 	/* Start the chip's Tx to process setup frame. */
-	outl(tp->csr6, ioaddr + CSR6);
-	outl(tp->csr6 | 0x2000, ioaddr + CSR6);
+	outl_CSR6(tp->csr6, ioaddr);
+	outl_CSR6(tp->csr6 | 0x2000, ioaddr);
 
 	dev->tbusy = 0;
 	tp->interrupt = 0;
@@ -1358,7 +1460,7 @@
 	/* Enable interrupts by setting the interrupt mask. */
 	outl(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR5);
 	outl(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR7);
-	outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+	outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 	outl(0, ioaddr + CSR2);		/* Rx poll demand */
 
 	if (tulip_debug > 2) {
@@ -1689,8 +1791,8 @@
 					   medianame[tp->mtable->mleaf[tp->cur_index].media]);
 			select_media(dev, 0);
 			/* Restart the transmit process. */
-			outl(tp->csr6 | 0x0002, ioaddr + CSR6);
-			outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+			outl_CSR6(tp->csr6 | 0x0002, ioaddr);
+			outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 			next_tick = (24*HZ)/10;
 			break;
 		}
@@ -1729,8 +1831,8 @@
 						tp->csr6 |= 0x0200;
 					else
 						tp->csr6 &= ~0x0200;
-					outl(tp->csr6 | 0x0002, ioaddr + CSR6);
-					outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+					outl_CSR6(tp->csr6 | 0x0002, ioaddr);
+					outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 					if (tulip_debug > 0) /* Gurppp, should be >1 */
 						printk(KERN_INFO "%s: Setting %s-duplex based on MII"
 							   " Xcvr #%d parter capability of %4.4x.\n",
@@ -1771,7 +1873,7 @@
 	if (dev->if_port == 3) {
 		if (csr12 & 2) {		/* No 100mbps link beat, revert to 10mbps. */
 			new_csr6 = 0x82420200;
-			outl(new_csr6, ioaddr + CSR6);
+			outl_CSR6(new_csr6, ioaddr);
 			outl(0x0000, ioaddr + CSR13);
 			outl(0x0003FFFF, ioaddr + CSR14);
 			outl(0x0008, ioaddr + CSR15);
@@ -1813,8 +1915,8 @@
 			tp->csr6 &= 0x00D5;
 			tp->csr6 |= new_csr6;
 			outl(0x0301, ioaddr + CSR12);
-			outl(tp->csr6 | 0x0002, ioaddr + CSR6);
-			outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+			outl_CSR6(tp->csr6 | 0x0002, ioaddr);
+			outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 		}
 	}
 	tp->timer.expires = RUN_AT(next_tick);
@@ -1834,7 +1936,7 @@
 	if ((csr12 & 0x7000) == 0x5000) {
 		if (csr12 & 0x01800000) {
 			/* Switch to 100mbps mode. */
-			outl(tp->csr6 | 0x0002, ioaddr + CSR6);
+			outl_CSR6(tp->csr6 | 0x0002, ioaddr);
 			if (csr12 & 0x01000000) {
 				dev->if_port = 5;
 				tp->csr6 = 0x83860200;
@@ -1842,7 +1944,7 @@
 				dev->if_port = 3;
 				tp->csr6 = 0x83860000;
 			}
-			outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+			outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 		} /* Else 10baseT-FD is handled automatically. */
 	} else if (dev->if_port == 3) {
 		if (!(csr12 & 2))
@@ -1865,8 +1967,8 @@
 		tp->csr6 = 0x83860000;
 		outl(0x0003FF7F, ioaddr + CSR14);
 		outl(0x0301, ioaddr + CSR12);
-		outl(tp->csr6 | 0x0002, ioaddr + CSR6);
-		outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+		outl_CSR6(tp->csr6 | 0x0002, ioaddr);
+		outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 	}
 }
 	
@@ -1961,8 +2063,8 @@
 	}
 	if (tp->csr6 != new_csr6) {
 		tp->csr6 = new_csr6;
-		outl(tp->csr6 | 0x0002, ioaddr + CSR6);	/* Restart Tx */
-		outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+		outl_CSR6(tp->csr6 | 0x0002, ioaddr);	/* Restart Tx */
+		outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 		dev->trans_start = jiffies;
 		if (tulip_debug > 0) /* Gurppp, should be >1 */
 			printk(KERN_INFO "%s: Changing PNIC configuration to %s-duplex, "
@@ -2016,7 +2118,7 @@
   } else if (tp->chip_id == DC21140 || tp->chip_id == DC21142
 			 || tp->chip_id == MX98713) {
 	  /* Stop the transmit process. */
-	  outl(tp->csr6 | 0x0002, ioaddr + CSR6);
+	  outl_CSR6(tp->csr6 | 0x0002, ioaddr);
 	  printk(KERN_WARNING "%s: 21140 transmit timed out, status %8.8x, "
 			 "SIA %8.8x %8.8x %8.8x %8.8x, resetting...\n",
 			 dev->name, inl(ioaddr + CSR5), inl(ioaddr + CSR12),
@@ -2030,7 +2132,7 @@
 		  printk(KERN_WARNING "%s: transmit timed out, switching to %s media.\n",
 				 dev->name, dev->if_port ? "100baseTx" : "10baseT");
 	  }
-	  outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+	  outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 	  tp->stats.tx_errors++;
 	  dev->trans_start = jiffies;
 	  return;
@@ -2051,8 +2153,8 @@
   /* Perhaps we should reinitialize the hardware here. */
   dev->if_port = 0;
   /* Stop and restart the chip's Tx processes . */
-  outl(tp->csr6 | 0x0002, ioaddr + CSR6);
-  outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+  outl_CSR6(tp->csr6 | 0x0002, ioaddr);
+  outl_CSR6(tp->csr6 | 0x2002, ioaddr);
   /* Trigger an immediate transmit demand. */
   outl(0, ioaddr + CSR1);
 
@@ -2286,8 +2388,8 @@
 					printk(KERN_WARNING "%s: The transmitter stopped!"
 						   "  CSR5 is %x, CSR6 %x.\n",
 						   dev->name, csr5, inl(ioaddr + CSR6));
-				outl(tp->csr6 | 0x0002, ioaddr + CSR6);
-				outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+				outl_CSR6(tp->csr6 | 0x0002, ioaddr);
+				outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 			}
 		}
 
@@ -2300,8 +2402,8 @@
 				else
 					tp->csr6 |= 0x00200000;  /* Store-n-forward. */
 				/* Restart the transmit process. */
-				outl(tp->csr6 | 0x0002, ioaddr + CSR6);
-				outl(tp->csr6 | 0x2002, ioaddr + CSR6);
+				outl_CSR6(tp->csr6 | 0x0002, ioaddr);
+				outl_CSR6(tp->csr6 | 0x2002, ioaddr);
 			}
 			if (csr5 & RxDied) {		/* Missed a Rx frame. */
 				tp->stats.rx_errors++;
@@ -2473,7 +2575,7 @@
 	/* Disable interrupts by clearing the interrupt mask. */
 	outl(0x00000000, ioaddr + CSR7);
 	/* Stop the chip's Tx and Rx processes. */
-	outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6);
+	outl_CSR6(inl(ioaddr + CSR6) & ~0x2002, ioaddr);
 	/* 21040 -- Leave the card in 10baseT state. */
 	if (tp->chip_id == DC21040)
 		outl(0x00000004, ioaddr + CSR13);
@@ -2638,13 +2740,13 @@
 
 	tp->csr6 &= ~0x00D5;
 	if (dev->flags & IFF_PROMISC) {			/* Set promiscuous. */
-		outl(csr6 | 0x00C0, ioaddr + CSR6);
+		outl_CSR6(csr6 | 0x00C0, ioaddr);
 		/* Unconditionally log net taps. */
 		printk(KERN_INFO "%s: Promiscuous mode enabled.\n", dev->name);
 		tp->csr6 |= 0xC0;
 	} else if ((dev->mc_count > 1000)  ||  (dev->flags & IFF_ALLMULTI)) {
 		/* Too many to filter perfectly -- accept all multicasts. */
-		outl(csr6 | 0x0080, ioaddr + CSR6);
+		outl_CSR6(csr6 | 0x0080, ioaddr);
 		tp->csr6 |= 0x80;
 	} else {
 		u32 *setup_frm = tp->setup_frame;
@@ -2728,7 +2830,7 @@
 			/* Trigger an immediate transmit demand. */
 			outl(0, ioaddr + CSR1);
 		}
-		outl(csr6 | 0x0000, ioaddr + CSR6);
+		outl_CSR6(csr6 | 0x0000, ioaddr);
 	}
 }
 
@@ -2795,6 +2897,7 @@
 MODULE_PARM(rx_copybreak, "i");
 MODULE_PARM(options, "1-" __MODULE_STRING(MAX_UNITS) "i");
 MODULE_PARM(full_duplex, "1-" __MODULE_STRING(MAX_UNITS) "i");
+MODULE_PARM(strict_csr6, "i");
 #endif
 
 /* An additional parameter that may be passed in... */