## Automatically generated incremental diff
## From:   linux-2.2.21-pre3
## To:     linux-2.2.21-pre4
## Robot:  $Id: make-incremental-diff,v 1.11 2002/02/20 02:59:33 hpa Exp $

diff -urN linux-2.2.21-pre3/COPYING linux-2.2.21-pre4/COPYING
--- linux-2.2.21-pre3/COPYING	Sun Mar 25 08:31:15 2001
+++ linux-2.2.21-pre4/COPYING	Fri Mar  8 17:42:09 2002
@@ -303,7 +303,7 @@
 the "copyright" line and a pointer to where the full notice is found.
 
     <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) 19yy  <name of author>
+    Copyright (C) <year>  <name of author>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -325,7 +325,7 @@
 If the program is interactive, make it output a short notice like this
 when it starts in an interactive mode:
 
-    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision version 69, Copyright (C) year name of author
     Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
     This is free software, and you are welcome to redistribute it
     under certain conditions; type `show c' for details.
diff -urN linux-2.2.21-pre3/Makefile linux-2.2.21-pre4/Makefile
--- linux-2.2.21-pre3/Makefile	Fri Mar  8 17:42:07 2002
+++ linux-2.2.21-pre4/Makefile	Fri Mar  8 17:42:09 2002
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 2
 SUBLEVEL = 21
-EXTRAVERSION = pre3
+EXTRAVERSION = pre4
 
 ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/)
 
diff -urN linux-2.2.21-pre3/arch/i386/kernel/bluesmoke.c linux-2.2.21-pre4/arch/i386/kernel/bluesmoke.c
--- linux-2.2.21-pre3/arch/i386/kernel/bluesmoke.c	Fri Mar  8 17:42:07 2002
+++ linux-2.2.21-pre4/arch/i386/kernel/bluesmoke.c	Fri Mar  8 17:42:09 2002
@@ -39,13 +39,12 @@
 			if(high&(1<<27))
 			{
 				rdmsr(0x402+i*4, alow, ahigh);
-				printk("[%08x%08x]", alow, ahigh);
+				printk("[%08x%08x]", ahigh, alow);
 			}
 			if(high&(1<<26))
 			{
 				rdmsr(0x402+i*4, alow, ahigh);
-				printk(" at %08x%08x", 
-					ahigh, alow);
+				printk(" at %08x%08x", ahigh, alow);
 			}
 			printk("\n");
 			/* Clear it */
@@ -165,7 +164,7 @@
 	if(l&(1<<8))
 		wrmsr(0x17b, 0xffffffff, 0xffffffff);
 	banks = l&0xff;
-	for(i=1;i<banks;i++)
+	for(i=0;i<banks;i++)
 	{
 		wrmsr(0x400+4*i, 0xffffffff, 0xffffffff); 
 	}
diff -urN linux-2.2.21-pre3/arch/s390/defconfig linux-2.2.21-pre4/arch/s390/defconfig
--- linux-2.2.21-pre3/arch/s390/defconfig	Fri Nov  2 08:39:06 2001
+++ linux-2.2.21-pre4/arch/s390/defconfig	Fri Mar  8 17:42:09 2002
@@ -63,6 +63,7 @@
 #
 # S390 Network devices
 #
+CONFIG_LCS=m
 CONFIG_CTC=y
 CONFIG_IUCV=y
 # CONFIG_DUMMY is not set
diff -urN linux-2.2.21-pre3/arch/sparc64/config.in linux-2.2.21-pre4/arch/sparc64/config.in
--- linux-2.2.21-pre3/arch/sparc64/config.in	Sun Mar 25 08:37:30 2001
+++ linux-2.2.21-pre4/arch/sparc64/config.in	Fri Mar  8 17:42:09 2002
@@ -250,6 +250,7 @@
 			tristate 'RealTek 8129/8139 (not 8019/8029!) support' CONFIG_RTL8139
 			tristate 'PCI NE2000 support' CONFIG_NE2K_PCI
 			tristate 'EtherExpressPro/100 support' CONFIG_EEXPRESS_PRO100
+			tristate 'Adaptec Starfire/DuraLAN support' CONFIG_ADAPTEC_STARFIRE
 			tristate 'SysKonnect SK-98xx support' CONFIG_SK98LIN
 		fi
 		bool 'FDDI driver support' CONFIG_FDDI
diff -urN linux-2.2.21-pre3/drivers/net/sis900.c linux-2.2.21-pre4/drivers/net/sis900.c
--- linux-2.2.21-pre3/drivers/net/sis900.c	Fri Nov  2 08:39:07 2001
+++ linux-2.2.21-pre4/drivers/net/sis900.c	Fri Mar  8 17:42:09 2002
@@ -1,6 +1,6 @@
 /* sis900.c: A SiS 900/7016 PCI Fast Ethernet driver for Linux.
    Copyright 1999 Silicon Integrated System Corporation 
-   Revision:	1.06.09 Sep. 28 2001
+   Revision:	1.06.10 Feb 28 2002
 
    Modified from the driver which is originally written by Donald Becker.
    
@@ -18,6 +18,7 @@
    preliminary Rev. 1.0 Jan. 18, 1998
    http://www.sis.com.tw/support/databook.htm
 
+   Rev 1.06.10 Feb. 28 2002 Allan Jacobsen (aj@2m.dk) Made RTL8201 PHY work on ECS K7S5A
    Rev 1.06.09 Sep. 28 2001 Hui-Fen Hsu (hfhsu@sis.com.tw) update for 630ET & workaround for ICS1893 PHY
    Rev 1.06.08 Mar.  2 2001 Hui-Fen Hsu (hfhsu@sis.com.tw) some bug fix & 635M/B support
    Rev 1.06.07 Jan.  8 2001 Lei-Chun Chang added RTL8201 PHY support
@@ -55,7 +56,7 @@
 #include "sis900.h"
 
 static const char *version =
-"sis900.c: v1.06.09  09/28/2001\n";
+"sis900.c: v1.06.10  02/28/2002\n";
 
 static int max_interrupt_work = 20;
 static int multicast_filter_limit = 128;
@@ -101,6 +102,7 @@
 	{ "AMD 79C901 HomePNA PHY",		0x0000, 0x6B90, HOME},
 	{ "ICS LAN PHY",			0x0015, 0xF440, LAN },
 	{ "NS  83851 PHY",			0x2000, 0x5C20, MIX },
+	{ "Realtek RTL8201 PHY",		0x0000, 0x8200, LAN },
 	{0,},
 };
 
@@ -1089,6 +1091,7 @@
 static void sis900_read_mode(struct device *net_dev, int *speed, int *duplex)
 {
 	struct sis900_private *sis_priv = (struct sis900_private *)net_dev->priv;
+	struct mii_phy *phy = sis_priv->mii;
 	int phy_addr = sis_priv->cur_phy;
 	u32 status;
 	u16 autoadv, autorec;
@@ -1115,6 +1118,14 @@
 
 	sis_priv->autong_complete = 1;
 	
+	/* Workaround for Realtek RTL8201 PHY issue */
+	if((phy->phy_id0 == 0x0000) && ((phy->phy_id1 & 0xFFF0) == 0x8200)){
+		if(mdio_read(net_dev, phy_addr, MII_CONTROL) & MII_CNTL_FDX)
+			*duplex = FDX_CAPABLE_FULL_SELECTED;
+		if(mdio_read(net_dev, phy_addr, 0x0019) & 0x01)
+			*speed = HW_SPEED_100_MBPS;
+	}
+
 	printk(KERN_INFO "%s: Media Link On %s %s-duplex \n",
 	       net_dev->name,
 	       *speed == HW_SPEED_100_MBPS ?
diff -urN linux-2.2.21-pre3/drivers/net/starfire-kcomp22.h linux-2.2.21-pre4/drivers/net/starfire-kcomp22.h
--- linux-2.2.21-pre3/drivers/net/starfire-kcomp22.h	Fri Nov  2 08:39:07 2001
+++ linux-2.2.21-pre4/drivers/net/starfire-kcomp22.h	Fri Mar  8 17:42:09 2002
@@ -73,10 +73,20 @@
 	char	reserved2[32];
 };
 
+/* for passing single values */
+struct ethtool_value {
+	u32	cmd;
+	u32	data;
+};
+
 /* CMDs currently supported */
 #define ETHTOOL_GSET		0x00000001 /* Get settings. */
 #define ETHTOOL_SSET		0x00000002 /* Set settings, privileged. */
 #define ETHTOOL_GDRVINFO	0x00000003 /* Get driver info. */
+#define ETHTOOL_GMSGLVL		0x00000007 /* Get driver message level */
+#define ETHTOOL_SMSGLVL		0x00000008 /* Set driver msg level, priv. */
+#define ETHTOOL_NWAY_RST	0x00000009 /* Restart autonegotiation, priv. */
+#define ETHTOOL_GLINK		0x0000000a /* Get link status */
 
 /* Indicates what features are supported by the interface. */
 #define SUPPORTED_10baseT_Half		(1 << 0)
@@ -163,6 +173,7 @@
 #define __devinit			__init
 #define __devinitdata			__initdata
 #define __devexit
+#define __devexit_p(foo)		foo
 #define MODULE_DEVICE_TABLE(foo,bar)
 #define SET_MODULE_OWNER(dev)
 #define COMPAT_MOD_INC_USE_COUNT	MOD_INC_USE_COUNT
@@ -179,6 +190,10 @@
 #define alloc_etherdev(size)		init_etherdev(NULL, size)
 #define register_netdev(dev)		0
 
+#ifdef CONFIG_SPARC64
+typedef unsigned long dma_addr_t;
+#endif /* CONFIG_SPARC64 */
+
 static inline void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size,
 					 dma_addr_t *dma_handle)
 {
diff -urN linux-2.2.21-pre3/drivers/net/starfire.c linux-2.2.21-pre4/drivers/net/starfire.c
--- linux-2.2.21-pre3/drivers/net/starfire.c	Fri Nov  2 08:39:07 2001
+++ linux-2.2.21-pre4/drivers/net/starfire.c	Fri Mar  8 17:42:09 2002
@@ -93,13 +93,21 @@
 	- Fixed initialization timing problems
 	- Fixed interrupt mask definitions
 
+	LK1.3.5 (jgarzik)
+	- ethtool NWAY_RST, GLINK, [GS]MSGLVL support
+
+	LK1.3.6 (Ion Badulescu)
+	- Sparc64 support and fixes
+	- Better stats and error handling
+
 TODO:
 	- implement tx_timeout() properly
+	- VLAN support
 */
 
 #define DRV_NAME	"starfire"
-#define DRV_VERSION	"1.03+LK1.3.4"
-#define DRV_RELDATE	"August 14, 2001"
+#define DRV_VERSION	"1.03+LK1.3.6"
+#define DRV_RELDATE	"March 6, 2002"
 
 #include <linux/version.h>
 #include <linux/module.h>
@@ -124,8 +132,11 @@
  * for this driver to really use the firmware. Note that Rx/Tx
  * hardware TCP checksumming is not possible without the firmware.
  *
- * I'm currently [Feb 2001] talking to Adaptec about this redistribution
- * issue. Stay tuned...
+ * If Adaptec could allow redistribution of the firmware (even in binary
+ * format), life would become a lot easier. Unfortunately, I've lost my
+ * Adaptec contacts, so progress on this front is rather unlikely to
+ * occur. If anybody from Adaptec reads this and can help with this matter,
+ * please let me know...
  */
 #undef HAS_FIRMWARE
 /*
@@ -250,6 +261,8 @@
 
 MODULE_AUTHOR("Donald Becker <becker@scyld.com>");
 MODULE_DESCRIPTION("Adaptec Starfire Ethernet driver");
+MODULE_LICENSE("GPL");
+
 MODULE_PARM(max_interrupt_work, "i");
 MODULE_PARM(mtu, "i");
 MODULE_PARM(debug, "i");
@@ -603,6 +616,7 @@
 	long ioaddr;
 	int drv_flags, io_size;
 	int boguscnt;
+	u16 cmd;
 	u8 cache;
 
 /* when built into the kernel, we only print version if device is found */
@@ -638,14 +652,22 @@
 		goto err_out_free_netdev;
 	}
 
-	ioaddr = (long) ioremap (ioaddr, io_size);
+	/* ioremap is borken in Linux-2.2.x/sparc64 */
+#if !defined(CONFIG_SPARC64) || LINUX_VERSION_CODE > 0x20300
+	ioaddr = (long) ioremap(ioaddr, io_size);
 	if (!ioaddr) {
 		printk (KERN_ERR DRV_NAME " %d: cannot remap 0x%x @ 0x%lx, aborting\n",
 			card_idx, io_size, ioaddr);
 		goto err_out_free_res;
 	}
+#endif /* !CONFIG_SPARC64 || Linux 2.3.0+ */
 
-	pci_set_master (pdev);
+	pci_set_master(pdev);
+
+	/* enable MWI -- it vastly improves Rx performance on sparc64 */
+	pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+	cmd |= PCI_COMMAND_INVALIDATE;
+	pci_write_config_word(pdev, PCI_COMMAND, cmd);
 
 	/* set PCI cache size */
 	pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &cache);
@@ -664,7 +686,7 @@
 
 	/* Serial EEPROM reads are hidden by the hardware. */
 	for (i = 0; i < 6; i++)
-		dev->dev_addr[i] = readb(ioaddr + EEPROMCtrl + 20-i);
+		dev->dev_addr[i] = readb(ioaddr + EEPROMCtrl + 20 - i);
 
 #if ! defined(final_version) /* Dump the EEPROM contents during development. */
 	if (debug > 4)
@@ -770,7 +792,7 @@
 
 #ifdef ZEROCOPY
 	printk(KERN_INFO "%s: scatter-gather and hardware TCP cksumming enabled.\n",
-	       dev->name,
+	       dev->name);
 #else  /* not ZEROCOPY */
 	printk(KERN_INFO "%s: scatter-gather and hardware TCP cksumming disabled.\n",
 	       dev->name);
@@ -926,7 +948,7 @@
 
 	/* Fill both the unused Tx SA register and the Rx perfect filter. */
 	for (i = 0; i < 6; i++)
-		writeb(dev->dev_addr[i], ioaddr + StationAddr + 5-i);
+		writeb(dev->dev_addr[i], ioaddr + StationAddr + 5 - i);
 	for (i = 0; i < 16; i++) {
 		u16 *eaddrs = (u16 *)dev->dev_addr;
 		long setup_frm = ioaddr + PerfFilterTable + i * 16;
@@ -973,9 +995,9 @@
 #ifdef HAS_FIRMWARE
 	/* Load Rx/Tx firmware into the frame processors */
 	for (i = 0; i < FIRMWARE_RX_SIZE * 2; i++)
-		writel(cpu_to_le32(firmware_rx[i]), ioaddr + RxGfpMem + i * 4);
+		writel(firmware_rx[i], ioaddr + RxGfpMem + i * 4);
 	for (i = 0; i < FIRMWARE_TX_SIZE * 2; i++)
-		writel(cpu_to_le32(firmware_tx[i]), ioaddr + TxGfpMem + i * 4);
+		writel(firmware_tx[i], ioaddr + TxGfpMem + i * 4);
 	/* Enable the Rx and Tx units, and the Rx/Tx frame processors. */
 	writel(0x003F, ioaddr + GenCtrl);
 #else  /* not HAS_FIRMWARE */
@@ -1150,8 +1172,8 @@
 
 	np->tx_ring[entry].first_addr = cpu_to_le32(np->tx_info[entry].first_mapping);
 #ifdef ZEROCOPY
-	np->tx_ring[entry].first_len = cpu_to_le32(skb_first_frag_len(skb));
-	np->tx_ring[entry].total_len = cpu_to_le32(skb->len);
+	np->tx_ring[entry].first_len = cpu_to_le16(skb_first_frag_len(skb));
+	np->tx_ring[entry].total_len = cpu_to_le16(skb->len);
 	/* Add "| TxDescIntr" to generate Tx-done interrupts. */
 	np->tx_ring[entry].status = cpu_to_le32(TxDescID | TxCRCEn);
 	np->tx_ring[entry].nbufs = cpu_to_le32(skb_shinfo(skb)->nr_frags + 1);
@@ -1164,8 +1186,10 @@
 		np->tx_ring[entry].status |= cpu_to_le32(TxRingWrap | TxDescIntr);
 
 #ifdef ZEROCOPY
-	if (skb->ip_summed == CHECKSUM_HW)
+	if (skb->ip_summed == CHECKSUM_HW) {
 		np->tx_ring[entry].status |= cpu_to_le32(TxCalTCP);
+		np->stats.tx_compressed++;
+	}
 #endif /* ZEROCOPY */
 
 	if (debug > 5) {
@@ -1443,6 +1467,7 @@
 #if defined(full_rx_status) || defined(csum_rx_status)
 		if (le32_to_cpu(np->rx_done_q[np->rx_done].status2) & 0x01000000) {
 			skb->ip_summed = CHECKSUM_UNNECESSARY;
+			np->stats.rx_compressed++;
 		}
 		/*
 		 * This feature doesn't seem to be working, at least
@@ -1574,12 +1599,17 @@
 		printk(KERN_NOTICE "%s: Increasing Tx FIFO threshold to %d bytes\n",
 		       dev->name, np->tx_threshold * 16);
 	}
-	if ((intr_status & ~(IntrNormalMask | IntrAbnormalSummary | IntrLinkChange | IntrStatsMax | IntrTxDataLow | IntrPCIPad)) && debug)
+	if (intr_status & IntrRxGFPDead) {
+		np->stats.rx_fifo_errors++;
+		np->stats.rx_errors++;
+	}
+	if (intr_status & (IntrNoTxCsum | IntrDMAErr)) {
+		np->stats.tx_fifo_errors++;
+		np->stats.tx_errors++;
+	}
+	if ((intr_status & ~(IntrNormalMask | IntrAbnormalSummary | IntrLinkChange | IntrStatsMax | IntrTxDataLow | IntrRxGFPDead | IntrNoTxCsum | IntrPCIPad)) && debug)
 		printk(KERN_ERR "%s: Something Wicked happened! %4.4x.\n",
 		       dev->name, intr_status);
-	/* Hmmmmm, it's not clear how to recover from DMA faults. */
-	if (intr_status & IntrDMAErr)
-		np->stats.tx_fifo_errors++;
 }
 
 
@@ -1787,6 +1817,47 @@
 		return 0;
 	}
 
+	/* restart autonegotiation */
+	case ETHTOOL_NWAY_RST: {
+		int tmp;
+		int r = -EINVAL;
+		/* if autoneg is off, it's an error */
+		tmp = mdio_read(dev, np->phys[0], MII_BMCR);
+		if (tmp & BMCR_ANENABLE) {
+			tmp |= (BMCR_ANRESTART);
+			mdio_write(dev, np->phys[0], MII_BMCR, tmp);
+			r = 0;
+		}
+		return r;
+	}
+	/* get link status */
+	case ETHTOOL_GLINK: {
+		struct ethtool_value edata = {ETHTOOL_GLINK};
+		if (mdio_read(dev, np->phys[0], MII_BMSR) & BMSR_LSTATUS)
+			edata.data = 1;
+		else
+			edata.data = 0;
+		if (copy_to_user(useraddr, &edata, sizeof(edata)))
+			return -EFAULT;
+		return 0;
+	}
+
+	/* get message-level */
+	case ETHTOOL_GMSGLVL: {
+		struct ethtool_value edata = {ETHTOOL_GMSGLVL};
+		edata.data = debug;
+		if (copy_to_user(useraddr, &edata, sizeof(edata)))
+			return -EFAULT;
+		return 0;
+	}
+	/* set message-level */
+	case ETHTOOL_SMSGLVL: {
+		struct ethtool_value edata;
+		if (copy_from_user(&edata, useraddr, sizeof(edata)))
+			return -EFAULT;
+		debug = edata.data;
+		return 0;
+	}
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -1961,7 +2032,7 @@
 static struct pci_driver starfire_driver = {
 	name:		DRV_NAME,
 	probe:		starfire_init_one,
-	remove:		starfire_remove_one,
+	remove:		__devexit_p(starfire_remove_one),
 	id_table:	starfire_pci_tbl,
 };
 
diff -urN linux-2.2.21-pre3/drivers/s390/Config.in linux-2.2.21-pre4/drivers/s390/Config.in
--- linux-2.2.21-pre3/drivers/s390/Config.in	Fri Nov  2 08:39:07 2001
+++ linux-2.2.21-pre4/drivers/s390/Config.in	Fri Mar  8 17:42:09 2002
@@ -52,6 +52,7 @@
   bool 'Network device support' CONFIG_NETDEVICES
   if [ "$CONFIG_NETDEVICES" = "y" ]; then
     comment 'S390 Network devices'
+    tristate 'Lan Channel Station Interface' CONFIG_LCS
     tristate 'CTC device support' CONFIG_CTC
     tristate 'IUCV device support (VM only)' CONFIG_IUCV
     tristate 'Dummy net driver support' CONFIG_DUMMY
diff -urN linux-2.2.21-pre3/drivers/s390/net/Makefile linux-2.2.21-pre4/drivers/s390/net/Makefile
--- linux-2.2.21-pre3/drivers/s390/net/Makefile	Sun Mar 25 08:37:35 2001
+++ linux-2.2.21-pre4/drivers/s390/net/Makefile	Fri Mar  8 17:42:09 2002
@@ -22,4 +22,12 @@
   endif
 endif
 
+ifeq ($(CONFIG_LCS),y)
+O_OBJS += lcs.o
+else
+  ifeq ($(CONFIG_LCS),m)
+  M_OBJS += lcs.o
+  endif
+endif
+
 include $(TOPDIR)/Rules.make
diff -urN linux-2.2.21-pre3/drivers/s390/net/lcs.c linux-2.2.21-pre4/drivers/s390/net/lcs.c
--- linux-2.2.21-pre3/drivers/s390/net/lcs.c	Wed Dec 31 16:00:00 1969
+++ linux-2.2.21-pre4/drivers/s390/net/lcs.c	Fri Mar  8 17:42:09 2002
@@ -0,0 +1,3821 @@
+/*
+ *  linux/drivers/s390/net/lcs.c
+ *
+ *  Linux for S/390 Lan Channel Station Network Driver
+ *
+ *
+ *  Copyright (C)  1999-2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ *    Author(s): DJ Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com) 
+ *               Frank Pavlic (pavlic@de.ibm.com)
+ *
+ *    $Revision: 1.90 $    $Date: 2002/03/01 17:20:11 $
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+
+/*
+ 	Thanks to Uli Hild, Martin Schwidefsky,
+	& Ingo who probably will never let it down that 
+	he found the braindamage with my offset calculations on 
+	the IO buffers.
+	( for quality of effort, advise, support & sanity checking )
+	Alex, Boas & the rest of the team for the opportunity to work here.
+
+DRIVER OPERATION OVERVIEW README FIRST
+======================================
+
+The lcs driver currently supports Ethernet & Token Ring, FDDI is still
+experimental.
+
+The lan channel station(lcs) network interface has two channels one read
+channel one  write channel. This is very similar to the S390 CTC interface
+
+The read channel is recognised by having an even cuu number & model 0x3088
+The write channel is recognised by the formula write cuu=read cuu number + 1
+& also a model of 0x3088 only certain cuu models are supported so as not to
+clash with a CTC control unit model.
+
+The driver always has a read outstanding on the read subchannel this is used
+to receive both command replys & network packets ( these are differenciated
+by checking the type field in the lcs header structure ), network packets
+may come in during the startup & shutdown sequence these have to be discarded.
+During normal network I/O the driver will intermittently retry reads to
+permanently keep reads outstanding on the read channel if an -EBUSY or
+similar occurs (otherwise the driver would stop receiving network packets).
+
+The channel progam used work as described below
+===============================================
+The write subchannel has a hack in /net/core/skbuff.c which makes
+sure there is enough head & tailroom to write packets directly out of the
+socket buffer ( If their isn't the code will still work ). 
+
+If the write subchannel is busy network packets are
+aggregated together into a & queued for transmission, the reason I'm doing
+this is to minimise start subchannels & interrupts which I have been told
+& have proven to be very expensive.
+
+e.g. a ping -f -s 1400 -c 20000 <gateway_addr> >/dev/null used take 40 secs,
+on my machine & a ping -f -s 100 -c 20000 <gateway_addr> >/dev/null took
+27 secs, over token ring with a single buffer this has improved a lot with
+the queueing technique (I also made sure that the delay wasn't caused by
+waiting for the free token).
+
+The new program works as follows
+================================
+The read & channel program consists of of 8 read ccw commands 
+followed by a transfer in channel back to the 1st read ccw. 
+A moving suspend bit is switched on in the read
+ccw of the last buffer processed with pci's switched on in the
+remaining ccw's. This way I recieve buffers with pci's without
+having to issue excess resumes or ssch's & only
+need to issue a resume if the read program gets suspended when
+all the buffers are filled.
+
+The write program consists of 4 to 9 write ccw commands folled
+by a transfer in channel back to the 1st write ccw.
+All unfilled buffers all have the suspend bit on & the
+suspend bit is taken off when buffers are either filled or partially
+filled & the write channel is no longer busy.
+
+The lan channel is started as follows
+1) A halt subchannel is issued to the read & write subchannel
+2) A startup primitive is sent to the lan & the reply is read from the
+read subchannel.
+3) Every possible relative adapter number issued a startlan primative
+with attempts to configure it as token ring ethernet & fddi at some stage
+in the boot sequence or when the module is being inserted.
+The relative adapter number is normally equal to the low byte of the read
+channel cuu/2 & hence the reason for the hint field to speed up lcs_detect.
+5) On success of a startlan we  know whether the osa card is configured as
+token ring,ethernet or fddi,a lanstat primative is issued to get the
+mac address.
+6) The device is now ready for network io.
+
+The shutdown is similar to the startup just a stoplan primative followed
+by a shutdown.
+
+The driver instance globals lcs_drvr_globals are held in the priv element
+of the device structure & these in turn point to a lcs_chan_globals
+structure for the read & write channel. The read & write chan_globals &
+driver globals have their own state machine, so that the interrupt handler
+can make decisions on whether to send packets read to the network layer, or
+whether it is should wake up a process currently issuing lan commands.
+*/
+
+#define TRUE 1
+#define FALSE 0
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <linux/netdevice.h>
+#if LINUX_VERSION_CODE<KERNEL_VERSION(2,3,0)
+#include <linux/kcomp.h>
+#endif
+
+#if CONFIG_NET_ETHERNET
+#include <linux/etherdevice.h>
+extern int ethif_probe (struct net_device *dev);
+#endif
+#ifdef CONFIG_TR
+#include <linux/trdevice.h>
+extern int trif_probe (struct net_device *dev);
+#endif
+#ifdef CONFIG_FDDI
+#include <linux/fddidevice.h>
+extern int fddiif_probe (struct net_device *dev);
+#endif
+#include <asm/irq.h>
+#include <asm/queue.h>
+#define LCS_CHANDEV CONFIG_CHANDEV
+#if LCS_CHANDEV
+#include <asm/chandev.h>
+#endif
+#ifdef CONFIG_IP_MULTICAST
+#include <linux/inetdevice.h>
+#include <net/arp.h>
+#include <linux/in.h>
+#include <linux/igmp.h>
+#endif
+#include <net/pkt_sched.h>
+#include <net/sock.h>
+#include <net/dst.h>
+
+#define LCS_USE_IDALS (LINUX_VERSION_CODE>KERNEL_VERSION(2,2,16))
+#if (!CONFIG_ARCH_S390X) || (LCS_USE_IDALS)
+#define lcs_dev_alloc_skb(length) dev_alloc_skb((length))
+#endif
+#if LCS_USE_IDALS
+#  include <asm/idals.h>
+#else
+#define set_normalized_cda(ccw, addr) ((ccw)->cda = (addr))
+#define clear_normalized_cda(ccw)
+#endif
+
+#if (LINUX_VERSION_CODE>=KERNEL_VERSION(2,2,18))
+#define LCS_DEBUG 1
+#else
+#define LCS_DEBUG 0
+#endif
+#define LCS_PRINTK_DEBUG   0
+#define LCS_ZERO_COPY_READ 0
+
+#if LCS_DEBUG && !LCS_PRINTK_DEBUG
+#include <asm/debug.h>
+#endif
+
+#if !defined(CONFIG_NET_ETHERNET)&&!defined(CONFIG_TR)&&!defined(CONFIG_FDDI)
+#error Cannot compile lcs.c without some net devices switched on.
+#endif
+
+#define lcs_inline		// inline
+
+#if LINUX_VERSION_CODE<=KERNEL_VERSION(2,2,16)
+#define lcs_daemonize(name,mask,use_init_fs) s390_daemonize(name,mask)
+#else
+#define lcs_daemonize(noargs...)
+#endif
+
+/* for the proc filesystem */
+static const char *cardname = "S390 Lan Channel Station Interface";
+
+/* lcs ccw command codes */
+
+enum {
+    ccw_write = 0x1,
+    ccw_read = 0x2,
+    ccw_read_backward = 0xc,
+    ccw_control = 0x3,
+    ccw_sense = 0x4,
+    ccw_sense_id = 0xe4,
+    ccw_transfer_in_channel0 = 0x8,
+    ccw_transfer_in_channel1 = 0x8,
+    ccw_set_x_mode = 0xc3,	// according to uli's lan notes
+    ccw_nop = 0x3		// according to uli's notes again
+	// n.b. ccw_control clashes with this
+	// so I presume its a special case of
+	// control
+};
+
+/* startup & shutdown primatives */
+
+typedef enum {
+    lcs_startlan = 1,
+    lcs_stoplan = 2,
+    lcs_lanstat = 4,
+    lcs_startup = 7,
+    lcs_shutdown = 8,
+#ifdef CONFIG_IP_MULTICAST
+    lcs_qipassist = 0xb2,
+    lcs_setipm = 0xb4,
+    lcs_delipm = 0xb5
+#endif
+} lcs_cmd;
+
+typedef enum {
+    command_reject = 0x80,
+    intervention_required = 0x40,
+    bus_out_check = 0x20,
+    equipment_check = 0x10,
+    interface_disconnect = 0x01,
+} lcs_sense_byte_0;
+
+typedef enum {
+    resetting_event = 0x0080,
+    device_online = 0x0020,
+} lcs_sense_byte_1;
+
+/* The type of packet we are transmitting or receiving from the lcs */
+typedef enum {
+    lcs_control_frame_type = 0,	/* for startup shutdown etc */
+    lcs_enet_type = 1,		/* the rest are for normal network io */
+    lcs_token_ring_type = 2,
+    lcs_fddi_type = 7,
+    lcs_autodetect_type = -1,
+} lcs_frame_type;
+
+#define LCS_ILLEGAL_OFFSET 0xffff
+
+static int use_hw_stats = 0;
+static int checksum_received_ip_pkts = 0;
+#if !LCS_CHANDEV
+/* Max number of cuu models we can detect */
+#define LCS_CURR_NUM_MODELS   4
+#define LCS_ADDITIONAL_MODELS 12
+#define LCS_ADDITIONAL_MODELS_X2_STR "24"
+#define LCS_MAX_NUM_MODELS  (LCS_CURR_NUM_MODELS+LCS_ADDITIONAL_MODELS)
+/* max number of parms that can be sent to lcs_setup */
+#define LCS_MAX_NUM_PARMS ((LCS_MAX_NUM_MODELS<<1)+2)
+/* configuration parms for the lcs card */
+typedef struct {
+    int cu_model;
+    int max_rel_adapter_no;
+} lcs_model_info;
+
+/* The models supported currently used in probing */
+#define LCS_CUTYPE 0x3088
+
+/* default configuration parms */
+
+static lcs_model_info lcs_models[LCS_CURR_NUM_MODELS]
+    __attribute__ ((section (".data"))) = {
+    {0x1, 15},			
+    /* P390/Planter 3172 emulation assume maximum 16 to be safe. */
+    {0x8, 15},			
+    /* 3172/2216 Paralell the 2216 allows 16 ports per card the */
+    /* the original 3172 only allows 4 we will assume the max of 16 */
+    {0x60, 1},			
+    /* Only 2 ports allowed on OSA2 cards model 0x60 */
+    {0x1F, 15},			
+    /* 3172/2216 Escon serial the 2216 allows 16 ports per card the */
+    /* the original 3172 only allows 4 we will assume the max of 16 */
+};
+static int additional_model_info[LCS_ADDITIONAL_MODELS << 1] = { 0, };
+/* Set this to one to stop autodetection */
+static int noauto = 0, ignore_sense = 0;
+
+/*
+ * The lcs header 
+ * offset=the offset of the next cmd/network packet 
+ * type=command ethernet token ring fddi frame 
+ * slot=relative adapter no
+ */
+
+#define LCS_MAX_FORCED_DEVNOS 12
+#define LCS_MAX_FORCED_DEVNOS_X2_STR "24"
+typedef struct {
+    int devno;
+    int portno;
+} lcs_dev_portno_pair;
+static int devno_portno_pairs[LCS_MAX_FORCED_DEVNOS << 1] =
+    {[0 ... ((LCS_MAX_FORCED_DEVNOS << 1) - 1)] 2, };
+static lcs_dev_portno_pair *lcs_dev_portno =
+    (lcs_dev_portno_pair *) & devno_portno_pairs[0];
+#endif
+
+#define lcs_header           \
+u16		offset;      \
+u8		type;        \
+u8		slot;
+
+typedef struct {
+lcs_header} lcs_header_type __attribute__ ((packed));
+
+enum {
+    lcs_390_initiated,
+    lcs_lgw_initiated
+};
+
+/*
+ * lcs startup & shutdown commands 
+ */
+#define lcs_base_cmd         \
+u8		cmd_code;    \
+u8		initiator;   \
+u16		sequence_no; \
+u16		return_code; \
+
+typedef struct {
+    lcs_header lcs_base_cmd u8 lan_type;
+    u8 rel_adapter_no;
+    u16 parameter_count;
+    u8 operator_flags[3];
+    u8 reserved[3];
+    u8 command_data[0];
+} lcs_std_cmd __attribute__ ((packed));
+
+typedef struct {
+    lcs_header lcs_base_cmd u16 unused1;
+    u16 buff_size;
+    u8 unused2[6];
+} lcs_startup_cmd __attribute__ ((packed));
+
+#define LCS_ADDR_LEN 6
+
+#ifdef CONFIG_IP_MULTICAST
+typedef struct {
+    u32 ip_addr;
+    u8 mac_address[LCS_ADDR_LEN];
+    u8 reserved[2];
+} lcs_ip_mac_addr_pair __attribute__ ((packed));
+
+typedef enum {
+    ipm_set_required,
+    ipm_delete_required,
+    ipm_on_card
+} lcs_ipm_state;
+typedef struct lcs_ipm_list lcs_ipm_list;
+struct lcs_ipm_list {
+    struct lcs_ipm_list *next;
+    lcs_ip_mac_addr_pair ipm;
+    lcs_ipm_state ipm_state;
+};
+
+#define MAX_IP_MAC_PAIRS      32
+typedef struct {
+    lcs_header lcs_base_cmd u8 lan_type;
+    u8 rel_adapter_no;
+    /* OSA only will only support one IP Multicast entry at a time */
+    u16 num_ipm_pairs;
+    u16 ip_assists_supported;	/* returned by OSA  */
+    u16 ip_assists_enabled;	/* returned by OSA */
+    u16 version;		/* IP version i.e. 4 */
+    lcs_ip_mac_addr_pair ip_mac_pair[MAX_IP_MAC_PAIRS];
+    u32 response_data;
+} lcs_ipm_ctlmsg __attribute__ ((packed));
+typedef struct {
+    lcs_header lcs_base_cmd u8 lan_type;
+    u8 rel_adapter_no;
+    u16 num_ip_pairs;		/* should be 0 */
+    u16 ip_assists_supported;	/* returned by OSA  */
+    u16 ip_assists_enabled;	/* returned by OSA */
+    u16 version;		/* IP version i.e. 4 */
+} lcs_qipassist_ctlmsg __attribute__ ((packed));
+
+typedef enum {
+    /* Not supported by LCS */
+    lcs_arp_processing = 0x0001,
+    lcs_inbound_checksum_support = 0x0002,
+    lcs_outbound_checksum_support = 0x0004,
+    lcs_ip_frag_reassembly = 0x0008,
+    lcs_ip_filtering = 0x0010,
+    /* Supported by lcs 3172 */
+    lcs_ip_v6_support = 0x0020,
+    lcs_multicast_support = 0x0040
+} lcs_assists;
+#define LANCMD_DEFAULT_MCAST_PARMS ,0,NULL
+#else
+#define LANCMD_DEFAULT_MCAST_PARMS
+#endif
+
+typedef struct {
+    lcs_header lcs_base_cmd u8 lan_type;
+    u8 rel_adapter_no;
+    u8 unused1[10];
+    u8 mac_address[LCS_ADDR_LEN];
+    u32 num_packets_deblocked;
+    u32 num_packets_blocked;
+    u32 num_packets_tx_on_lan;
+    u32 num_tx_errors_detected;
+    u32 num_tx_packets_disgarded;
+    u32 num_packets_rx_from_lan;
+    u32 num_rx_errors_detected;
+    u32 num_rx_discarded_nobuffs_avail;
+    u32 num_rx_packets_too_large;
+} lcs_lanstat_reply __attribute__ ((packed));
+
+/* This buffer sizes are used by MVS so they should be reasonable */
+#define LCS_IOBUFFSIZE     0x5000
+#define LCS_NUM_TX_BUFFS    8
+#define LCS_NUM_RX_BUFFS    8
+
+#define LCS_INVALID_LOCK_OWNER            -1
+
+#if LINUX_VERSION_CODE<KERNEL_VERSION(2,2,18)
+typedef struct wait_queue *wait_queue_head_t;
+#endif
+#if LINUX_VERSION_CODE<KERNEL_VERSION(2,4,0)
+typedef dev_info_t s390_dev_info_t;
+#endif
+
+typedef enum {
+    chan_dead,
+    chan_idle,
+    chan_busy,
+    chan_starting_up
+} lcs_chan_busy_state;
+
+#define LCS_MAGIC			0x05A22A05	/* OSA2 to you */
+#define LCS_CHAN_GLOBALS(num_io_buffs)      \
+u32                  irq_allocated_magic;   \
+u16 subchannel;                             \
+u16 devno;                                  \
+struct lcs_drvr_globals *drvr_globals;      \
+atomic_t             sleeping_on_io;        \
+unsigned long        flags;                 \
+int                  rc;                    \
+int                  lock_owner;            \
+int                  lock_cnt;              \
+wait_queue_head_t    wait;                  \
+struct  tq_struct    retry_task;            \
+devstat_t	     devstat;               \
+lcs_chan_busy_state  chan_busy_state;       \
+ccw1_t               ccw[num_io_buffs+1];   \
+
+
+typedef struct {
+    LCS_CHAN_GLOBALS (0)
+} lcs_chan_globals;
+
+typedef struct {
+    LCS_CHAN_GLOBALS (LCS_NUM_RX_BUFFS)
+#if LCS_ZERO_COPY_READ
+    struct sk_buff *skb_buff[LCS_NUM_RX_BUFFS];
+#else
+    u8 *iobuff[LCS_NUM_RX_BUFFS];
+#endif
+    lcs_std_cmd *lancmd_reply_ptr;	/* only used by read channel */
+    u32 rx_idx;
+    u8 pad[0] __attribute__ ((aligned (8)));	/* so the next structure
+						   will be aligned */
+} lcs_read_globals;
+
+typedef struct {
+    LCS_CHAN_GLOBALS (LCS_NUM_TX_BUFFS)
+    u8 *iobuff[LCS_NUM_TX_BUFFS];
+    u32 prepare_idx;
+    u32 tx_idx;
+    unsigned long pkts_still_being_txed;
+    unsigned long bytes_still_being_txed;
+    struct tq_struct resume_task;
+    int resume_queued;
+    int resume_loopcnt;
+    uint64_t last_lcs_txpacket_time;
+    uint64_t last_resume_time;
+    uint64_t adjusted_last_bytes_still_being_txed;
+    u32 buffsfilled;
+} lcs_write_globals;
+
+#define LCS_READ_ALLOCSIZE      LCS_IOBUFFSIZE
+
+/*
+ * drvr_globals main state machine 
+ */
+typedef enum {
+    lcs_idle,
+    lcs_halting_subchannels,
+    lcs_doing_io,
+    lcs_idle_requesting_channels_for_lancmds,
+    lcs_requesting_channels_for_lancmds,
+    lcs_got_write_channel_for_lancmds,
+    lcs_doing_lancmds,
+    lcs_interrupted
+} lcs_state;
+
+enum {
+    lcs_invalid_adapter_no = -1
+};
+
+typedef struct lcs_drvr_globals lcs_drvr_globals;
+struct lcs_drvr_globals {
+    struct lcs_drvr_globals *next;
+    struct net_device *dev;
+    atomic_t usage_cnt;		/* used by drvr_globals_valid &
+				   lcs_usage_free_drvr_globals */
+    u8 lan_type;
+    u8 cmd_code;
+    unsigned short (*lan_type_trans) (struct sk_buff * skb,
+				      struct net_device * dev);
+    wait_queue_head_t lanstat_wait;	/* processes asleep awaiting
+					   lanstat results */
+    int doing_lanstat;
+    int up;
+    lcs_state state;
+    lcs_read_globals *read;
+    lcs_write_globals *write;
+    u16 sequence_no;
+    s8 rel_adapter_no;
+    int slow_hw;
+/* To insure only one kernel thread runs at a time */
+    atomic_t kernel_thread_lock;
+    int (*kernel_thread_routine) (lcs_drvr_globals * drvr_globals);
+    struct tq_struct kernel_thread_task;
+    u16 lgw_sequence_no;	/* this isn't required just being thorough */
+    int reset_retry_cnt;
+    struct net_device_stats stats __attribute__ ((aligned (4)));
+#ifdef CONFIG_IP_MULTICAST
+    spinlock_t ipm_lock;
+    lcs_ipm_list *ipm_list;
+#endif
+    /* So I can use atomic atomic_t for stats if required  */
+    u8 pad[0] __attribute__ ((aligned (8)));
+    /* CCW's need 8 byte alignment ( Thanks Martin ) */
+} __attribute__ ((aligned (8)));
+
+/* A token ring header can be 40 bytes if you include the llc & rcf so
+ * we leave a small bit extra.
+ */
+
+#define LCS_TX_HIWATERMARK          LCS_IOBUFFSIZE-60
+
+#define LCS_ALLOCSIZE \
+	(sizeof(lcs_drvr_globals)+sizeof(lcs_read_globals)+ \
+	 sizeof(lcs_write_globals))
+
+/* Function prototypes */
+void lcs_rxpacket (lcs_drvr_globals * drvr_globals, u8 * start_buff
+#if LCS_ZERO_COPY_READ
+		   , u32 curr_idx
+#endif
+    );
+int lcs_txpacket (struct sk_buff *skb, struct net_device *dev);
+static int lcs_detect (lcs_drvr_globals * drvr_globals, s16 forced_port_no,
+		       u8 hint_port_no, u8 max_port_no,
+		       lcs_frame_type frame_type, u8 * mac_address,
+		       int retrycnt);
+static int lcs_check_reset (lcs_chan_globals * chan_globals);
+static void lcs_restartreadio (lcs_drvr_globals * drvr_globals);
+static void lcs_restartwriteio (lcs_drvr_globals * drvr_globals);
+static void lcs_queued_restartreadio (lcs_drvr_globals * drvr_globals);
+static void lcs_queued_restartwriteio (lcs_drvr_globals * drvr_globals);
+static void lcs_resume_writetask (lcs_write_globals * write);
+#ifdef MODULE
+void cleanup_module (void);
+#endif
+
+#ifdef CONFIG_IP_MULTICAST
+static int lcs_fix_multicast_list (lcs_drvr_globals * drvr_globals);
+#endif
+
+#define lcs_debug_initmessage      char *message=NULL;
+#define lcs_debug_setmessage(string) message=string
+
+#if LCS_DEBUG
+#if LCS_PRINTK_DEBUG
+#define lcs_debug_event(level,args...)      printk(##args)
+#define lcs_debug_exception(level,args...)  printk(##args)
+#define lcs_bad_news(args...)         printk(KERN_CRIT ##args);
+#define lcs_good_news(args...)         printk(##args);
+#else
+debug_info_t *lcs_id = NULL;
+#define lcs_debug_event(args...)      debug_sprintf_event(lcs_id,##args)
+#define lcs_debug_exception(args...)  debug_sprintf_exception(lcs_id,##args)
+#define lcs_bad_news(args...)                 \
+{                                             \
+	lcs_debug_exception(0,##args);        \
+	printk(KERN_CRIT ##args);             \
+}
+
+#define lcs_good_news(args...)                \
+{                                             \
+	lcs_debug_event(0,##args);            \
+	printk(##args);                       \
+}
+#endif
+
+#define lcs_debugchannel(message,chan_globals) \
+lcs_debugchannel_func((message),(lcs_chan_globals *)(chan_globals))
+
+void
+lcs_debugchannel_func (char *message, lcs_chan_globals * chan_globals)
+{
+    lcs_drvr_globals *drvr_globals;
+
+    if (message) {
+	lcs_debug_event (0, "lcs: %s\n", message);
+    }
+    lcs_debug_event (0, "lcs  irq=%04x devno=%04x %s\n",
+		     (int) chan_globals->subchannel, (int) chan_globals->devno,
+		     (message ? message : ""));
+    lcs_debug_event (0, "chan globals=%p\n", chan_globals);
+    if (chan_globals) {
+	drvr_globals = chan_globals->drvr_globals;
+	if (drvr_globals) {
+	    if (drvr_globals->read != (lcs_read_globals *) chan_globals &&
+		drvr_globals->write != (lcs_write_globals *) chan_globals)
+		lcs_debug_event (0, "drvr globals inconsistent\n");
+	    lcs_debug_event (0, "drvr_globals=%p:state=%d\n", drvr_globals,
+			     drvr_globals->state);
+	    lcs_debug_event (0,
+			     "chan globals sleeping_on_io=%d"
+			     " busy_state=%d lock_owner=%d lock_cnt=%d\n",
+			     atomic_read (&chan_globals->sleeping_on_io),
+			     chan_globals->chan_busy_state,
+			     chan_globals->lock_owner, chan_globals->lock_cnt);
+	}
+    }
+    lcs_debug_exception (0, "leaving lcs_debugchannel\n");
+}
+
+void
+lcs_debug_display_read_buff (char *message, lcs_std_cmd * lancmd_reply_ptr)
+{
+    int *read_buff = (int *) lancmd_reply_ptr;
+
+    lcs_debug_event (1, "%s %08x %08x %08x\n", message,
+		     read_buff[0], read_buff[1], read_buff[2]);
+}
+
+#else
+#define lcs_debug_event(noargs...)
+#define lcs_debug_exception(noargs...)
+#define lcs_bad_news(args...)  printk(KERN_CRIT ##args)
+#define lcs_good_news(args...)  printk(##args)
+#define lcs_debugchannel(message,chan_globals)   \
+printk("lcs  irq=%04x devno=%04x %s\n",(int)chan_globals->subchannel, \
+(int)chan_globals->devno,(message ? message:""))
+#define lcs_debug_display_read_buff(noargs...)
+#endif
+
+#if (!LCS_USE_IDALS) && (CONFIG_ARCH_S390X)
+static inline struct sk_buff *
+lcs_dev_alloc_skb (unsigned int length)
+{
+    struct sk_buff *skb;
+
+    skb = alloc_skb (length + 16, GFP_ATOMIC | GFP_DMA);
+    if (skb)
+	skb_reserve (skb, 16);
+    return skb;
+}
+#endif
+
+static void lcs_inline
+lcs_resetstate (lcs_drvr_globals * drvr_globals)
+{
+    drvr_globals->state = lcs_idle;
+}
+
+static __inline__ int
+netif_is_busy (struct net_device *dev)
+{
+#if LINUX_VERSION_CODE<KERNEL_VERSION(2,3,45)
+    return (dev->tbusy);
+#else
+    return (test_bit (__LINK_STATE_XOFF, &dev->flags));
+#endif
+}
+
+/* 
+ * Primatives used to put the process to sleep or wake it up during
+ * startup or shutdown.
+ */
+static lcs_inline void
+lcs_initqueue_func (lcs_chan_globals * chan_globals)
+{
+    atomic_set (&chan_globals->sleeping_on_io, TRUE);
+    chan_globals->rc = 0;
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    init_waitqueue_head (&chan_globals->wait);
+#else
+    chan_globals->wait = NULL;
+#endif
+}
+
+#define lcs_initqueue(chan_globals) \
+	lcs_initqueue_func((lcs_chan_globals *)chan_globals)
+
+static void
+lcs_wakeup_func (lcs_chan_globals * chan_globals, int rc)
+{
+    lcs_debug_event (2, "lcs_wakeup called sch=%04x rc=%d\n,",
+		     (int) chan_globals->subchannel, rc);
+    atomic_set (&chan_globals->sleeping_on_io, FALSE);
+    if (chan_globals->rc == 0)
+	chan_globals->rc = rc;
+    wake_up (&chan_globals->wait);
+}
+
+#define TOD_SHIFT_TO_USECS 12
+static inline uint64_t
+lcs_get_tod (void)
+{
+    uint64_t cc;
+
+    asm volatile ("STCK %0":"=m" (cc));
+    return (cc);
+}
+
+#define lcs_wakeup(chan_globals,rc) \
+	lcs_wakeup_func((lcs_chan_globals *)chan_globals,(rc))
+static int
+lcs_sleepon_func (lcs_chan_globals * chan_globals, int extratime)
+{
+    /*
+       We have a miniscule chance of sleeping here for 1 second
+       we have to unfortunately live with it as down_interruptible
+       dosen't wake up for timers just signals.
+     */
+    int rc = 0;
+
+    lcs_debug_event (2, "lcs_sleepon called sch=%04x\n,",
+		     (int) chan_globals->subchannel);
+    sleep_on_timeout (&chan_globals->wait, HZ);
+    if (atomic_read (&chan_globals->sleeping_on_io) && chan_globals->rc == 0
+	&& extratime) {
+	lcs_bad_news("lcs_sleepon network card taking time responding "
+		     "irq=%04x devno=%04x,\n"
+		     "please be patient, ctrl-c will exit if shell prompt "
+		     "is available.\n",
+	     (int) chan_globals->subchannel, (int) chan_globals->devno);
+	interruptible_sleep_on_timeout (&chan_globals->wait, extratime * HZ);
+    }
+    if (atomic_read (&chan_globals->sleeping_on_io) && chan_globals->rc == 0) {
+	chan_globals->drvr_globals->state = lcs_interrupted;
+	rc = (current->sigpending ? -EINTR : -ETIMEDOUT);
+
+    }
+    if (chan_globals->rc)
+	rc = chan_globals->rc;
+    lcs_debug_event (2, "lcs_sleepon sch=%04x devno=%04x rc=%d\n",
+		     (int) chan_globals->subchannel,
+		     (int) chan_globals->devno, rc);
+    return (rc);
+
+}
+
+#define lcs_sleepon(chan_globals,extratime) \
+	lcs_sleepon_func((lcs_chan_globals *)chan_globals,extratime)
+
+static void
+lcs_rxerror (char *message, lcs_drvr_globals * drvr_globals)
+{
+    lcs_read_globals *read = NULL;
+    struct net_device_stats *stats = NULL;
+    if (drvr_globals) {
+	read = drvr_globals->read;
+	stats = &drvr_globals->stats;
+    }
+    if (use_hw_stats == FALSE && stats)
+	stats->rx_errors++;
+    lcs_debugchannel (message, read);
+}
+
+static void
+lcs_chan_lock_func (lcs_chan_globals * chan_globals)
+{
+    eieio ();
+    if (chan_globals->lock_owner != smp_processor_id ()) {
+	s390irq_spin_lock_irqsave (chan_globals->subchannel,
+				   chan_globals->flags);
+	chan_globals->lock_cnt = 1;
+	chan_globals->lock_owner = smp_processor_id ();
+
+    } else
+	chan_globals->lock_cnt++;
+    if (chan_globals->lock_cnt < 0 || chan_globals->lock_cnt > 100) {
+	lcs_bad_news ("bad lock_cnt %d in lcs_chan_lock chan_globals=%p"
+		      " devno=%04x returnaddrs=%p,%p cpu=%d\n",
+		      chan_globals->lock_cnt, chan_globals,
+		      (int) chan_globals->devno,
+		      __builtin_return_address (0),
+		      __builtin_return_address (1), smp_processor_id ());
+	chan_globals->lock_cnt = 1;
+    }
+}
+
+static void
+lcs_chan_unlock_func (lcs_chan_globals * chan_globals)
+{
+    if (chan_globals->lock_cnt <= 0) {
+	lcs_bad_news ("bad lock_cnt %d in lcs_chan_unlock chan_globals=%p"
+		      " devno=%04x returnaddrs=%p,%p cpu=%d\n",
+		      chan_globals->lock_cnt, chan_globals,
+		      (int) chan_globals->devno,
+		      __builtin_return_address (0),
+		      __builtin_return_address (1), smp_processor_id ());
+	chan_globals->lock_cnt = 1;
+    }
+
+    if (--chan_globals->lock_cnt == 0) {
+	chan_globals->lock_owner = LCS_INVALID_LOCK_OWNER;
+	s390irq_spin_unlock_irqrestore (chan_globals->subchannel,
+					chan_globals->flags);
+    }
+
+}
+
+#define lcs_chan_lock(chan_globals) \
+	lcs_chan_lock_func((lcs_chan_globals *)chan_globals)
+#define lcs_chan_unlock(chan_globals) \
+	lcs_chan_unlock_func((lcs_chan_globals *)chan_globals)
+
+/*
+ * A little utility routine to make a ccw chain.
+ */
+static void
+initccws (ccw1_t * ccws, u8 * iobuff[], int num_ccws, u8 cmd_code, u16 count,
+	  u8 flags)
+{
+    int cnt;
+    ccw1_t *origccws = ccws;
+    memset (ccws, 0, sizeof (ccw1_t) * num_ccws + 1);
+    for (cnt = 0; cnt < num_ccws; cnt++, ccws++) {
+	ccws->cmd_code = cmd_code;
+	ccws->count = count;
+	ccws->flags = flags;
+	/* ccw addresses are 32 bit */
+	(u32) ccws->cda = (u32) virt_to_phys (iobuff[cnt]);
+    }
+    ccws->cmd_code = ccw_transfer_in_channel1;
+    (u32) ccws->cda = (u32) virt_to_phys (origccws);
+}
+
+/*
+ * linked list used to hold all the lcs_drvr_globals so they can be
+ * freed up when the driver is finished 
+ */
+static spinlock_t lcs_card_list_lock = SPIN_LOCK_UNLOCKED;
+static lcs_drvr_globals *lcs_card_list = NULL;
+
+/*
+  This function is required for kernel threads & timers
+  going off after the driver is closed & long gone & causing mischef.
+  ( thanks Ingo for making me aware that this was happening :-) )
+ */
+static int
+lcs_drvr_globals_valid (lcs_drvr_globals * drvr_globals)
+{
+    int in_list = FALSE;
+
+    if (drvr_globals) {
+	spin_lock (&lcs_card_list_lock);
+	in_list = is_in_list ((list *) lcs_card_list, (list *) drvr_globals);
+	if (in_list)
+	    atomic_inc (&drvr_globals->usage_cnt);
+	spin_unlock (&lcs_card_list_lock);
+    }
+    return (in_list);
+}
+
+static void *
+lcs_dma_alloc (size_t allocsize)
+{
+    void *newalloc = kmalloc (allocsize, GFP_KERNEL | GFP_DMA);
+    if (newalloc)
+	memset (newalloc, 0, allocsize);
+    return (newalloc);
+}
+
+static void
+lcs_usage_free_drvr_globals (lcs_drvr_globals * drvr_globals)
+{
+#if LCS_ZERO_COPY_READ
+    struct sk_buff **skb_buff;
+#endif
+    u8 **iobuff;
+    int cnt, refcnt;
+
+    lcs_debug_event (2, "lcs_usage_free_drvr_globals_called %p\n",
+		     drvr_globals);
+    if (drvr_globals) {
+	lcs_read_globals *read = drvr_globals->read;
+	refcnt = atomic_dec_return (&drvr_globals->usage_cnt);
+	if (refcnt < 0)
+	    lcs_bad_news ("bad ref cnt drvr_globals %p ref %d\n", drvr_globals,
+			  refcnt);
+	if (refcnt <= 0) {
+	    spin_lock (&lcs_card_list_lock);
+	    if (!remove_from_list
+		((list **) & lcs_card_list, (list *) drvr_globals))
+		lcs_bad_news ("drvr globals not in list %p\n", drvr_globals);
+	    spin_unlock (&lcs_card_list_lock);
+	    lcs_debug_event (2, "freeing drvr globals\n");
+	    iobuff = drvr_globals->write->iobuff;
+	    for (cnt = 0; cnt < LCS_NUM_TX_BUFFS; cnt++) {
+		if (iobuff[cnt])
+		    kfree (iobuff[cnt]);
+	    }
+#if LCS_ZERO_COPY_READ
+	    skb_buff = read->skb_buff;
+	    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++) {
+		clear_normalized_cda (&read->ccw[cnt]);
+		if (skb_buff[cnt])
+		    dev_kfree_skb (skb_buff[cnt]);
+	    }
+#else
+	    iobuff = read->iobuff;
+	    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++) {
+		if (iobuff[cnt])
+		    kfree (iobuff[cnt]);
+	    }
+#endif
+	    if (drvr_globals->dev)
+		drvr_globals->dev->priv = NULL;
+#ifdef  CONFIG_IP_MULTICAST
+	    while (drvr_globals->ipm_list) {
+		spin_lock (&drvr_globals->ipm_lock);
+		remove_from_list ((list **) & drvr_globals->ipm_list,
+				  (list *) drvr_globals->ipm_list);
+		spin_unlock (&drvr_globals->ipm_lock);
+		kfree (drvr_globals->ipm_list);
+	    }
+#endif
+	    memset (drvr_globals, 0, LCS_ALLOCSIZE);
+	    kfree (drvr_globals);
+	}
+    }
+}
+
+static void
+lcs_kernel_thread (lcs_drvr_globals * drvr_globals)
+{
+#if LINUX_VERSION_CODE<=KERNEL_VERSION(2,2,16)
+    /* tq_scheduler sometimes leaves interrupts disabled from do bottom half */
+    __sti ();
+#endif
+    if (kernel_thread ((int (*)(void *)) drvr_globals->kernel_thread_routine,
+		       (void *) drvr_globals, SIGCHLD) < 0) {
+	atomic_set (&drvr_globals->kernel_thread_lock, 1);
+	lcs_debug_exception (1,
+			     "lcs_kernel_thread failed to launch a new thread "
+			     "drvr_globals=%p routine=%p",
+			     drvr_globals, drvr_globals->kernel_thread_routine);
+    }
+}
+
+/* 
+ * Allocates driver instance globals 
+ */
+static lcs_drvr_globals *
+lcs_alloc_drvr_globals (void)
+{
+    lcs_drvr_globals *drvr_globals;
+    lcs_write_globals *write;
+    lcs_read_globals *read;
+    int cnt;
+    struct tq_struct *lcs_tq;
+
+    lcs_debug_event (2, "lcs_alloc_drvr_globals called\n");
+    drvr_globals = lcs_dma_alloc (LCS_ALLOCSIZE);
+    if (!drvr_globals)
+	return (NULL);
+    atomic_set (&drvr_globals->usage_cnt, 1);
+    drvr_globals->rel_adapter_no = lcs_invalid_adapter_no;
+    read = drvr_globals->read = (lcs_read_globals *) (drvr_globals + 1);
+    write = drvr_globals->write =
+	(lcs_write_globals *) ((addr_t) read + sizeof (lcs_read_globals));
+    write->lock_owner = drvr_globals->read->lock_owner = LCS_INVALID_LOCK_OWNER;
+    for (cnt = 0; cnt < LCS_NUM_TX_BUFFS; cnt++) {
+	if ((write->iobuff[cnt] = lcs_dma_alloc (LCS_IOBUFFSIZE)) == NULL)
+	    goto Fail;
+
+    }
+    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++) {
+#if LCS_ZERO_COPY_READ
+	if ((read->skb_buff[cnt] =
+	     lcs_dev_alloc_skb (LCS_READ_ALLOCSIZE)) == NULL)
+#else
+	if ((read->iobuff[cnt] =
+	     kmalloc (LCS_READ_ALLOCSIZE, GFP_ATOMIC | GFP_DMA)) == NULL)
+#endif
+	    goto Fail;
+    }
+    drvr_globals->read->drvr_globals = drvr_globals;
+    write->drvr_globals = drvr_globals;
+    lcs_resetstate (drvr_globals);
+    lcs_tq = &drvr_globals->kernel_thread_task;
+    lcs_tq->routine = (void (*)(void *)) lcs_kernel_thread;
+    lcs_tq->data = (void *) drvr_globals;
+#if LINUX_VERSION_CODE>KERNEL_VERSION(2,3,0)
+    lcs_tq->sync = read->retry_task.sync = write->retry_task.sync = 0;
+#endif
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    INIT_LIST_HEAD (&lcs_tq->list);
+    INIT_LIST_HEAD (&read->retry_task.list);
+    INIT_LIST_HEAD (&write->retry_task.list);
+#endif
+    read->retry_task.routine = (void (*)(void *)) lcs_queued_restartreadio;
+    write->retry_task.routine = (void (*)(void *)) lcs_queued_restartwriteio;
+    write->retry_task.data = read->retry_task.data = (void *) drvr_globals;
+    write->resume_task.routine = (void (*)(void *)) lcs_resume_writetask;
+    write->resume_task.data = (void *) write;
+    read->chan_busy_state = write->chan_busy_state = chan_dead;
+    atomic_set (&drvr_globals->kernel_thread_lock, 1);
+    spin_lock (&lcs_card_list_lock);
+    add_to_list ((list **) & lcs_card_list, (list *) drvr_globals);
+    spin_unlock (&lcs_card_list_lock);
+    drvr_globals->up = FALSE;
+    return (drvr_globals);
+Fail:
+    lcs_usage_free_drvr_globals (drvr_globals);
+    return (NULL);
+}
+
+void
+lcs_retry (lcs_chan_globals * chan_globals)
+{
+    lcs_debug_event (1, "lcs_retry subchannel %04x scheduling retry\n",
+		     chan_globals->subchannel);
+    MOD_INC_USE_COUNT;
+#if LINUX_VERSION_CODE>KERNEL_VERSION(2,2,16)
+    schedule_task (&chan_globals->retry_task);
+#else
+    queue_task (&chan_globals->retry_task, &tq_scheduler);
+#endif
+}
+
+static int
+lcs_doio_func (lcs_chan_globals * chan_globals, unsigned long flags)
+{
+    int rc;
+    lcs_debug_event (2, "lcs_doio subchannel=%x\n",
+		     (int) chan_globals->subchannel);
+    lcs_chan_lock (chan_globals);
+    rc = do_IO (chan_globals->subchannel, &chan_globals->ccw[0],
+		(addr_t) chan_globals, 0, flags);
+
+    if (rc) {
+#if LCS_DEBUG
+	lcs_debug_event (1, "lcs_doio do_IO returned %d for subchannel %X\n",
+			 rc, chan_globals->subchannel);
+#endif
+	if (rc != -ENODEV)
+	    lcs_retry (chan_globals);
+    }
+    lcs_chan_unlock (chan_globals);
+    return (rc);
+}
+
+#define lcs_doio(chan_globals,flags)  \
+	lcs_doio_func((lcs_chan_globals *)(chan_globals),(flags))
+
+/* 
+ * halt subchannel wrapper used during driver startup 
+ */
+static int
+lcs_hsch_func (lcs_chan_globals * chan_globals)
+{
+    int rc = 0;
+    lcs_debug_event (2, "lcs_hsch subchannel=%x\n",
+		     (int) chan_globals->subchannel);
+    lcs_chan_lock (chan_globals);
+    lcs_initqueue (chan_globals);
+    chan_globals->drvr_globals->state = lcs_halting_subchannels;
+    rc = halt_IO (chan_globals->subchannel, (addr_t) chan_globals, 0);
+    lcs_chan_unlock (chan_globals);
+    if (rc == 0)
+	rc = lcs_sleepon_func (chan_globals, 0);
+    else
+	lcs_debug_event (2, "lcs_hsch subchannel=%x rc=%d\n",
+			 (int) chan_globals->subchannel, rc);
+    return (rc);
+}
+
+#define lcs_hsch(chan_globals) \
+	lcs_hsch_func((lcs_chan_globals *)(chan_globals))
+
+static void
+wait_on_write (lcs_drvr_globals * drvr_globals)
+{
+    lcs_write_globals *write = drvr_globals->write;
+
+    if (drvr_globals->state == lcs_doing_io) {
+	if (drvr_globals->dev)
+	    netif_stop_queue (drvr_globals->dev);
+	lcs_chan_lock (write);
+	if (drvr_globals->state == lcs_doing_io &&
+	    (write->chan_busy_state != chan_idle || write->resume_queued)) {
+	    lcs_initqueue (write);
+	    drvr_globals->state = lcs_requesting_channels_for_lancmds;
+	    lcs_chan_unlock (write);
+	    lcs_sleepon (write, 0);
+	} else
+	    lcs_chan_unlock (write);
+    }
+    drvr_globals->state = lcs_doing_lancmds;
+}
+
+static int
+lcs_getcmdsize (u8 cmd_code)
+{
+    int cmdsize;
+    switch (cmd_code) {
+    case lcs_startlan:
+    case lcs_stoplan:
+    case lcs_lanstat:
+    case lcs_shutdown:
+	cmdsize = sizeof (lcs_std_cmd);
+	break;
+    case lcs_startup:
+	cmdsize = sizeof (lcs_startup_cmd);
+	break;
+#ifdef CONFIG_IP_MULTICAST
+    case lcs_setipm:
+    case lcs_delipm:
+	cmdsize = sizeof (lcs_ipm_ctlmsg);
+	break;
+    case lcs_qipassist:
+	cmdsize = sizeof (lcs_qipassist_ctlmsg);
+	break;
+#endif
+    default:
+	lcs_debug_event (1, "illegal lan cmd %d in lcs_getcmdsize\n", cmd_code);
+	cmdsize = -EINVAL;
+    }
+    if (cmdsize != -EINVAL)
+	cmdsize -= sizeof (lcs_header_type);
+    return (cmdsize);
+}
+
+lcs_header_type *
+lcs_getfreetxbuff (lcs_write_globals * write, u8 type, u8 slot, int len)
+{
+    ccw1_t *curr_ccw;
+    int new_buff = FALSE;
+    lcs_header_type *retbuff;
+    u32 virt_cda;
+
+    if (write->prepare_idx == write->tx_idx)
+	return (NULL);
+    curr_ccw = &write->ccw[write->prepare_idx];
+    if (curr_ccw->count == 0)
+	new_buff = TRUE;
+    else if ((curr_ccw->count + len + sizeof (lcs_header_type) + sizeof (u16)) >
+	     LCS_IOBUFFSIZE) {
+	curr_ccw->flags &= ~CCW_FLAG_SUSPEND;
+	write->prepare_idx =
+	    ((write->prepare_idx + 1) & (LCS_NUM_TX_BUFFS - 1));
+	if (write->prepare_idx == write->tx_idx)
+	    return (NULL);
+	new_buff = TRUE;
+	curr_ccw = &write->ccw[write->prepare_idx];
+    }
+    if (new_buff) {
+	write->buffsfilled++;
+	if ((curr_ccw->flags & CCW_FLAG_SUSPEND) == 0) {
+	    lcs_bad_news
+		("Bug/Race condition detected in lcs_getfreetxbuff "
+		 "CCW_FLAG_SUSPEND not set\n"
+		 "for ccw at %p", curr_ccw);
+	    return (NULL);
+	}
+	if (curr_ccw->count != 0) {
+	    lcs_bad_news
+		("Bug/Race condition detected in lcs_getfreetxbuff count!=0 "
+		 "in new buffer\n"
+		 "for ccw at %p", curr_ccw);
+	    return (NULL);
+	}
+    }
+    len += sizeof (lcs_header_type);
+    virt_cda = (u32) virt_to_phys ((void *) ((addr_t) curr_ccw->cda));
+    if (new_buff) {
+	retbuff = (lcs_header_type *) ((addr_t) virt_cda);
+	curr_ccw->count = len + sizeof (u16);
+    } else {
+	retbuff =
+	    (lcs_header_type *) (virt_cda + curr_ccw->count - sizeof (u16));
+	curr_ccw->count += len;
+	len = curr_ccw->count - sizeof (u16);
+    }
+    retbuff->offset = len;
+    *((u16 *) ((addr_t) (virt_cda + len))) = 0;
+    retbuff->type = type;
+    retbuff->slot = slot;
+    return (retbuff);
+}
+
+lcs_std_cmd *
+lcs_prepare_lancmd (lcs_write_globals * write, u8 cmd_code, u8 rel_adapter_no,
+		    u8 initiator, u8 lan_type)
+{
+    lcs_std_cmd *writecmd;
+    int cmdsize = lcs_getcmdsize (cmd_code);
+    if (cmdsize < 0)
+	return (NULL);
+    /* Force a fresh ccw buff */
+    writecmd =
+	(lcs_std_cmd *) lcs_getfreetxbuff (write, lcs_control_frame_type, 0,
+					   cmdsize);
+    if (writecmd == NULL)
+	return (NULL);
+    memset ((void *) (((addr_t) writecmd) + sizeof (lcs_header_type)), 0,
+	    cmdsize);
+    writecmd->cmd_code = cmd_code;
+    writecmd->initiator = initiator;
+    switch (cmd_code) {
+    case lcs_startlan:
+    case lcs_stoplan:
+    case lcs_lanstat:
+#ifdef CONFIG_IP_MULTICAST
+    case lcs_setipm:
+    case lcs_delipm:
+    case lcs_qipassist:
+#endif
+	writecmd->lan_type = lan_type;
+	writecmd->rel_adapter_no = rel_adapter_no;
+	break;
+    case lcs_startup:
+	((lcs_startup_cmd *) writecmd)->buff_size = LCS_IOBUFFSIZE;
+	break;
+    case lcs_shutdown:
+	break;
+    default:
+    }
+    return (writecmd);
+}
+
+#if LCS_DEBUG_RESUME
+int lcs_read_resume_cnt, lcs_write_resume_cnt;
+#endif
+
+void
+lcs_interrupt_resume_read (lcs_read_globals * read)
+{
+    int rc;
+    ccw1_t *curr_ccw;
+
+    lcs_debug_event (2, "lcs_interrupt_resume_read entered\n");
+    if ((read->ccw[(read->rx_idx - 1) & (LCS_NUM_RX_BUFFS - 1)].
+	 flags & CCW_FLAG_SUSPEND) == 0)
+	lcs_bad_news("lcs_interrupt_resume_read detected possible "
+		     "lack of suspend bug\n");
+    curr_ccw = &read->ccw[read->rx_idx];
+    curr_ccw->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_PCI
+#if LCS_USE_IDALS
+	| (curr_ccw->flags & CCW_FLAG_IDA)
+#endif
+	;
+    if ((rc = resume_IO (read->subchannel)) == 0) {
+#if LCS_DEBUG_RESUME
+	lcs_read_resume_cnt++;
+#endif
+	read->chan_busy_state = chan_busy;
+    } else
+	lcs_bad_news
+	    ("lcs_interrupt_resume_read returned %d for subchannel %X\n", rc,
+	     read->subchannel);
+}
+
+void
+lcs_resume_read (lcs_read_globals * read)
+{
+    lcs_debug_event (2, "lcs_resume_read entered\n");
+    lcs_chan_lock (read);
+    if (read->chan_busy_state == chan_idle)
+	lcs_interrupt_resume_read (read);
+    read->lancmd_reply_ptr = NULL;
+    lcs_chan_unlock (read);
+}
+
+int
+lcs_resume_write (lcs_write_globals * write)
+{
+    int rc = 0;
+    ccw1_t *tx_ccw;
+
+    lcs_debug_event (2, "lcs_resume_write entered\n");
+    lcs_chan_lock (write);
+    tx_ccw = &write->ccw[write->prepare_idx];
+    if (write->chan_busy_state == chan_idle) {
+	if (tx_ccw->flags & CCW_FLAG_SUSPEND) {
+	    tx_ccw->flags &= ~CCW_FLAG_SUSPEND;
+	    write->prepare_idx =
+		((write->prepare_idx + 1) & (LCS_NUM_TX_BUFFS - 1));
+	}
+	write->buffsfilled = 0;
+	if ((rc = resume_IO (write->subchannel)) == 0) {
+#if LCS_DEBUG_RESUME
+	    lcs_write_resume_cnt++;
+#endif
+	    write->chan_busy_state = chan_busy;
+	    /* the << TOD_SHIFT_TO_USECS is to adjust the inequality
+	       to microseconds for the tod in lcs_tx_is_busy
+	       The extra is adjustment is
+	       so that the tx_is_busy will weigh 
+	       in favour of large transmission buffers.
+	     */
+
+	    write->adjusted_last_bytes_still_being_txed =
+		(uint64_t) ((write->bytes_still_being_txed +
+			     500) << TOD_SHIFT_TO_USECS);
+	    write->last_resume_time = lcs_get_tod ();
+	} else
+	    lcs_bad_news ("lcs_resume_write returned %d for subchannel %X\n",
+			  rc, write->subchannel);
+
+    }
+    lcs_chan_unlock (write);
+    return rc;
+}
+
+/*
+  Unfortunately we don't know if the lcs interface is busy
+  simply by knowing we have started a transmission & not
+  recieved a interrupt yet because the osa microcode simply
+  copies the packet into its own internal buffers.
+  I estimate whether the interface maybe busy by assuming
+  an extremely optimistic 30MB/sec transmission rate ( might
+  happen on a gigabit ethernet card ), i.e. if this routine
+  returns true the interface is almost definitely busy.
+ */
+static int
+lcs_tx_is_busy (lcs_write_globals * write, uint64_t tod)
+{
+    uint64_t delta_tx = tod - write->last_resume_time;
+    int rc;
+
+    /* 30MB/sec = 30 bytes per usec */
+    if (write->adjusted_last_bytes_still_being_txed < (30 * delta_tx))
+	rc = FALSE;
+    else
+	rc = TRUE;
+#if 0
+    lcs_debug_event (2,
+		     "%s being txed %08X tod %08X last resume=%08X "
+		     "delta_tx %08X\n",
+		     (rc ? "lcs busy" : "lcs not busy"),
+		     (unsigned) ((write->
+				  adjusted_last_bytes_still_being_txed >>
+				  TOD_SHIFT_TO_USECS)),
+		     (unsigned) (tod >> TOD_SHIFT_TO_USECS),
+		     (unsigned) (write->last_resume_time >> TOD_SHIFT_TO_USECS),
+		     (unsigned) ((delta_tx) >> TOD_SHIFT_TO_USECS));
+#endif
+    return (rc);
+}
+
+static void
+lcs_queue_write (lcs_write_globals * write)
+{
+    /* wait on write in shutdown func should avoid this needing mod
+     * usage counts */
+    write->resume_queued = TRUE;
+    queue_task (&write->resume_task, &tq_immediate);
+    mark_bh (IMMEDIATE_BH);
+}
+
+static void
+lcs_queue_write_if_necessary (lcs_write_globals * write)
+{
+    if (!write->resume_queued) {
+	write->resume_loopcnt = 0;
+	lcs_queue_write (write);
+    }
+}
+
+int
+lcs_should_queue_write (lcs_write_globals * write, uint64_t tod)
+{
+    if ((write->buffsfilled < (LCS_NUM_TX_BUFFS - 1))
+	&& (lcs_tx_is_busy (write, tod) ||
+	    ((tod - write->last_lcs_txpacket_time) <
+	     (25 << TOD_SHIFT_TO_USECS))))
+	return (TRUE);
+    return (FALSE);
+
+}
+
+static void
+lcs_resume_writetask (lcs_write_globals * write)
+{
+    uint64_t tod;
+
+    if (write->buffsfilled) {
+	tod = lcs_get_tod ();
+	if (write->resume_loopcnt < 20 && lcs_should_queue_write(write,tod)) {
+	    write->resume_loopcnt++;
+	    lcs_queue_write (write);
+	    return;
+	} else {
+#if 0
+	    if (write->resume_loopcnt > 10)
+		lcs_debug_event (1, "lcs_resume_writetask bad loopcnt %d\n",
+				 write->resume_loopcnt);
+#endif
+	    lcs_resume_write (write);
+	}
+
+    }
+    write->resume_queued = FALSE;
+
+}
+
+int
+lcs_calcsleeptime (lcs_drvr_globals * drvr_globals, u8 cmd_code)
+{
+    if ((cmd_code == lcs_startlan || cmd_code == lcs_stoplan))
+	return (drvr_globals->slow_hw ? 60 : 5);
+    else
+	return (drvr_globals->slow_hw ? 5 : 0);
+
+}
+
+int
+lcs_resumeandwait (lcs_write_globals * write, u8 cmd_code)
+{
+    int rc;
+
+    lcs_initqueue (write);
+    if ((rc = lcs_resume_write (write)))
+	return (rc);
+    return (lcs_sleepon
+	    (write, lcs_calcsleeptime (write->drvr_globals, cmd_code)));
+}
+
+static int
+lcs_lgw_common (lcs_drvr_globals * drvr_globals, u8 cmd_code)
+{
+
+    lcs_write_globals *write = drvr_globals->write;
+    lcs_std_cmd *writecmd;
+    wait_on_write (drvr_globals);
+    writecmd = lcs_prepare_lancmd (write, cmd_code,
+				   drvr_globals->rel_adapter_no,
+				   lcs_lgw_initiated, drvr_globals->lan_type);
+    writecmd->sequence_no = drvr_globals->lgw_sequence_no;
+    return (lcs_resumeandwait (write, cmd_code));
+}
+
+static void
+lcs_start_doing_io (lcs_drvr_globals * drvr_globals)
+{
+    netif_start_queue (drvr_globals->dev);
+    drvr_globals->state = lcs_doing_io;
+}
+
+static int
+lcs_lgw_stoplan (lcs_drvr_globals * drvr_globals)
+{
+    struct net_device *dev;
+
+    lcs_daemonize ("lcs_lgw_stoplan", 0, TRUE);
+    if (!lcs_drvr_globals_valid (drvr_globals))
+	goto Done2;
+    if (drvr_globals->state != lcs_doing_io)
+	goto Done1;
+    dev = drvr_globals->dev;
+    lcs_bad_news ("lcs problems detected %s temporarily unavailable.\n",
+		  dev->name);
+    lcs_lgw_common (drvr_globals, lcs_stoplan);
+    netif_stop_queue (dev);
+    drvr_globals->state = lcs_doing_io;
+Done1:
+    atomic_set (&drvr_globals->kernel_thread_lock, 1);
+    lcs_usage_free_drvr_globals (drvr_globals);
+Done2:
+    MOD_DEC_USE_COUNT;
+    return (0);
+}
+
+static int
+lcs_lgw_startlan (lcs_drvr_globals * drvr_globals)
+{
+    struct net_device *dev;
+
+    lcs_daemonize ("lcs_lgw_startlan", 0, TRUE);
+    if (!lcs_drvr_globals_valid (drvr_globals))
+	goto Done2;
+    if (drvr_globals->state != lcs_doing_io)
+	goto Done1;
+    dev = drvr_globals->dev;
+    lcs_debug_event (0, "lcs device %s restarted.\n", dev->name);
+    lcs_lgw_common (drvr_globals, lcs_startlan);
+    lcs_start_doing_io (drvr_globals);
+Done1:
+    atomic_set (&drvr_globals->kernel_thread_lock, 1);
+    lcs_usage_free_drvr_globals (drvr_globals);
+Done2:
+    MOD_DEC_USE_COUNT;
+    return (0);
+}
+
+static int
+lcs_lgw_resetlan (lcs_drvr_globals * drvr_globals)
+{
+    struct net_device *dev;
+    int retrycnt;
+
+    lcs_daemonize ("lcs_lgw_resetlan", 0, TRUE);
+    if (!lcs_drvr_globals_valid (drvr_globals))
+	goto Done2;
+    if (drvr_globals->state != lcs_doing_io)
+	goto Done1;
+    dev = drvr_globals->dev;
+    lcs_debug_event (0, "lcs reset %s.\n", dev->name);
+    for (retrycnt = 0; retrycnt < 10; retrycnt++) {
+	if (lcs_detect (drvr_globals, drvr_globals->rel_adapter_no, 0, 0,
+			drvr_globals->lan_type,
+			dev->dev_addr, drvr_globals->reset_retry_cnt) == 0) {
+	    lcs_start_doing_io (drvr_globals);
+	    lcs_good_news ("lcs reset %s successfully.\n", dev->name);
+	    goto Done1;
+	}
+	lcs_debug_event (0, "lcs_resetlan retrycnt %d\n", retrycnt);
+	schedule_timeout (3 * HZ);
+	drvr_globals->reset_retry_cnt = 0;
+    }
+    lcs_resetstate (drvr_globals);
+    lcs_bad_news ("lcs problems occured restarting after reset %s.\n",
+		  dev->name);
+Done1:
+    atomic_set (&drvr_globals->kernel_thread_lock, 1);
+    lcs_usage_free_drvr_globals (drvr_globals);
+Done2:
+    MOD_DEC_USE_COUNT;
+    return (0);
+}
+
+static void
+lcs_queue_thread (int (*routine) (lcs_drvr_globals *),
+		  lcs_drvr_globals * drvr_globals)
+{
+    if (atomic_dec_and_test (&drvr_globals->kernel_thread_lock)) {
+	netif_stop_queue (drvr_globals->dev);
+	MOD_INC_USE_COUNT;
+	drvr_globals->kernel_thread_routine = routine;
+#if LINUX_VERSION_CODE<=KERNEL_VERSION(2,2,16)
+	queue_task (&drvr_globals->kernel_thread_task, &tq_scheduler);
+#else
+	schedule_task (&drvr_globals->kernel_thread_task);
+#endif
+    } else
+	lcs_debug_event (1, "lcs_queue_thread busy drvr_globals=%p routine=%p",
+			 drvr_globals, routine);
+}
+
+static int
+lcs_check_reset (lcs_chan_globals * chan_globals)
+{
+    lcs_drvr_globals *drvr_globals = chan_globals->drvr_globals;
+    devstat_t *devstat = &chan_globals->devstat;
+
+    if (devstat->flag & DEVSTAT_FLAG_SENSE_AVAIL) {
+	if (drvr_globals->state == lcs_doing_io &&
+	    (devstat->scnt >= 1) &&
+	    ((lcs_sense_byte_1) (devstat->ii.sense.data[1]))
+	    == resetting_event) {
+	    lcs_debug_exception (0, "lcs reseting event occuring devno=%04x\n",
+				 (int) chan_globals->devno);
+	    drvr_globals->reset_retry_cnt = 0;
+	    switch (drvr_globals->state) {
+	    case lcs_idle_requesting_channels_for_lancmds:
+	    case lcs_requesting_channels_for_lancmds:
+	    case lcs_got_write_channel_for_lancmds:
+	    case lcs_doing_lancmds:
+		lcs_restartreadio (drvr_globals);
+		lcs_restartwriteio (drvr_globals);
+		break;
+	    case lcs_doing_io:
+		lcs_queue_thread (lcs_lgw_resetlan, drvr_globals);
+		break;
+	    default:
+	    }
+	    return (TRUE);
+	} else {
+	    if ((devstat->scnt >= 1) && ((lcs_sense_byte_0)
+					 (devstat->ii.sense.data[0]) ==
+					 (intervention_required |
+					  interface_disconnect)))
+		lcs_debug_exception (0,
+				     "lcs intervention_required "
+				     "& interface_disconnect detected "
+				     "devno=0x%04x\n",
+				     (int) chan_globals->devno);
+	    else {
+		lcs_debug_event (0, "lcs unknown sense data devno=0x%04x\n"
+				 "sense_cnt=%d sense_data=\n"
+				 "%08x %08x %08x %08x\n"
+				 "%08x %08x %08x %08x\n",
+				 (int) chan_globals->devno,
+				 (int) devstat->scnt,
+				 ((int *) &devstat->ii.sense.data)[0],
+				 ((int *) &devstat->ii.sense.data)[1],
+				 ((int *) &devstat->ii.sense.data)[2],
+				 ((int *) &devstat->ii.sense.data)[3],
+				 ((int *) &devstat->ii.sense.data)[4],
+				 ((int *) &devstat->ii.sense.data)[5],
+				 ((int *) &devstat->ii.sense.data)[6],
+				 ((int *) &devstat->ii.sense.data)[7]);
+		if (devstat->scnt > 2 ||
+		    (devstat->ii.sense.data[0] == 0
+		     && devstat->ii.sense.data[1] == 0)) {
+		    lcs_debug_event (0,"lcs sense data looks like "
+				     "rubbish ignoring it\n");
+		    devstat->flag &= ~DEVSTAT_FLAG_SENSE_AVAIL;
+		}
+	    }
+	}
+    }
+    return (FALSE);
+}
+
+#if LCS_ZERO_COPY_READ
+static inline void
+lcs_rxskb (lcs_drvr_globals * drvr_globals, struct sk_buff *skb, u16 pkt_len,
+	   u16 offset, struct net_device_stats *stats)
+{
+    stats->rx_bytes += pkt_len;
+    if (use_hw_stats == FALSE)
+	stats->rx_packets++;
+    lcs_debug_event (2, "netif_rx called\n");
+    lcs_debug_event (2, "received %u bytes rx_bytes=%lu\n",
+		     (unsigned) pkt_len, stats->rx_bytes);
+    skb_reserve (skb, sizeof (lcs_header_type) + offset);
+    skb_put (skb, pkt_len);
+    /* Horrible horrible kludge to fix bug in some socket code */
+    /* usage of skb->truesize hopefully this bug will be fixed in */
+    /* upper layers soon */
+    skb->truesize = skb->len;
+    skb->protocol = drvr_globals->lan_type_trans (skb, skb->dev);
+    netif_rx (skb);
+}
+#endif
+
+void
+lcs_rxpacket (lcs_drvr_globals * drvr_globals, u8 * start_lcs_buff
+#if LCS_ZERO_COPY_READ
+	      , u32 curr_idx
+#endif
+    )
+{
+    lcs_read_globals *read = NULL;
+
+    struct sk_buff *curr_skb
+#if LCS_ZERO_COPY_READ
+           , *prev_skb
+#endif
+           ;
+    struct net_device *dev = NULL;
+    struct net_device_stats *stats = NULL;
+    lcs_header_type *lcs_header_ptr = (lcs_header_type *) start_lcs_buff;
+    u16 curr_offset, curr_pkt_len
+#if  LCS_ZERO_COPY_READ
+       , prev_pkt_len, curr_skb_offset, prev_skb_offset
+#endif
+       ;
+
+    lcs_debug_event (2, "Entering lcs_rxpacket\n");
+    if (drvr_globals == NULL) {
+	lcs_rxerror ("drvr_globals=NULL\n", drvr_globals);
+	return;
+    }
+
+    dev = drvr_globals->dev;
+    read = drvr_globals->read;
+#if LCS_ZERO_COPY_READ
+    if (atomic_read (skb_datarefp (read->skb_buff[curr_idx])) != 1) {
+	lcs_bad_news ("lcs_rxpacket skb->refcnt=%d start_lcs_buff=%p\n",
+		      atomic_read (skb_datarefp (read->skb_buff[curr_idx])),
+		      start_lcs_buff);
+	return;
+    }
+#endif
+    stats = &drvr_globals->stats;
+    lcs_debug_event (2, "We appear to have received a packet buff=%p\n",
+		     lcs_header_ptr);
+#if LCS_ZERO_COPY_READ
+    curr_skb = NULL;
+    curr_pkt_len = curr_skb_offset =
+#endif
+	curr_offset = 0;
+    while (lcs_header_ptr->offset) {
+	lcs_debug_event(3,"lcs_header_ptr=%p curroffset=%d\n", lcs_header_ptr,
+			curr_offset);
+#if LCS_ZERO_COPY_READ
+	prev_pkt_len = curr_pkt_len;
+	prev_skb = curr_skb;
+	prev_skb_offset = curr_skb_offset;
+#endif
+#if 0
+	static int idx = 0;
+	idx++;
+	if ((idx & 0x1ff) == 0) {
+	    lcs_header_ptr->type = lcs_control_frame_type;
+	    ((lcs_std_cmd *) lcs_header_ptr)->initiator = lcs_lgw_initiated;
+	    (((lcs_std_cmd *) lcs_header_ptr)->cmd_code) = lcs_startup;
+	}
+#endif
+	if (lcs_header_ptr->offset > LCS_IOBUFFSIZE ||
+	    lcs_header_ptr->offset < curr_offset) {
+	    if (use_hw_stats == FALSE)
+		stats->rx_length_errors++;
+	    lcs_rxerror ("lcs whacko buffer error\n", drvr_globals);
+	    return;
+	}
+
+	if (lcs_header_ptr->type == lcs_control_frame_type) {
+	    if (((lcs_std_cmd *) lcs_header_ptr)->initiator == lcs_lgw_initiated
+		&& drvr_globals->state == lcs_doing_io) {
+		drvr_globals->lgw_sequence_no =
+		    ((lcs_std_cmd *) lcs_header_ptr)->sequence_no;
+		lcs_debug_display_read_buff ("lgw_initiated_command read buff",
+					     ((lcs_std_cmd *) lcs_header_ptr));
+		switch ((int) (((lcs_std_cmd *) lcs_header_ptr)->cmd_code)) {
+		case lcs_startup:
+		    drvr_globals->reset_retry_cnt = -1;
+		    lcs_queue_thread (lcs_lgw_resetlan, drvr_globals);
+		    break;
+		case lcs_startlan:
+		    lcs_queue_thread (lcs_lgw_startlan, drvr_globals);
+		    break;
+		case lcs_stoplan:
+		    lcs_queue_thread (lcs_lgw_stoplan, drvr_globals);
+		    break;
+		default:
+		    lcs_debug_exception (1, "lcs unrecognised lgw"
+					 " command received 0x%02x"
+					 " on devno=%04x\n",
+					 (int) ((lcs_std_cmd *)
+						lcs_header_ptr)
+					 ->cmd_code, (int) read->devno);
+		}
+	    } else {
+		if (drvr_globals->state == lcs_doing_lancmds) {
+		    if (((((lcs_std_cmd *) lcs_header_ptr)->sequence_no ==
+			  drvr_globals->sequence_no)) &&
+			(((lcs_std_cmd *) lcs_header_ptr)->cmd_code ==
+			 drvr_globals->cmd_code)) {
+			read->lancmd_reply_ptr = (lcs_std_cmd *) lcs_header_ptr;
+
+			lcs_debug_event (2, "got a valid lancommand %p "
+					 "cmd_code=%02x sequence_no=%d\n",
+					 read->lancmd_reply_ptr,
+					 (int) ((lcs_std_cmd *)
+						lcs_header_ptr)->cmd_code,
+					 (int) ((lcs_std_cmd *)
+						lcs_header_ptr)->sequence_no);
+			lcs_wakeup ((lcs_chan_globals *) read, 0);
+			break;
+		    }
+		}
+	    }
+	} else if (dev && drvr_globals->state == lcs_doing_io) {	
+	    /* the packet is for tcpip */
+
+	    /* dev_alloc_skb allocs with GFP_ATOMIC currently */
+	    /* so this should be okay under interrupt */
+	    /* hopefully .... */
+#if LCS_ZERO_COPY_READ
+	    if (curr_skb == NULL) {
+		/* We need to detach the buffer */
+		struct sk_buff *new_skb;
+		if ((new_skb=lcs_dev_alloc_skb(LCS_READ_ALLOCSIZE)) == NULL) {
+		    stats->rx_dropped++;
+		    lcs_bad_news ("%s: Memory squeeze, dropping packet.\n",
+				  dev->name);
+		    return;
+		}
+
+		curr_skb = read->skb_buff[curr_idx];
+		curr_skb->dev = dev;
+		curr_skb->ip_summed = (checksum_received_ip_pkts
+				       ? CHECKSUM_NONE : CHECKSUM_UNNECESSARY);
+		clear_normalized_cda (&read->ccw[curr_idx]);
+		read->skb_buff[curr_idx] = new_skb;
+		set_normalized_cda (&read->ccw[curr_idx],
+				    virt_to_phys ((void *) ((addr_t) new_skb->
+							    data)));
+	    } else {
+		curr_skb = skb_clone (prev_skb, GFP_ATOMIC);
+		if (curr_skb == NULL) {
+		    curr_skb = prev_skb;
+		    stats->rx_dropped++;
+		    lcs_bad_news ("%s: Memory squeeze, dropping packet.\n",
+				  dev->name);
+		    break;
+		}
+		curr_skb_offset = curr_offset;
+	    }
+	    if (prev_skb)
+		lcs_rxskb (drvr_globals, prev_skb, prev_pkt_len,
+			   prev_skb_offset, stats);
+	    curr_pkt_len =
+		lcs_header_ptr->offset - curr_skb_offset -
+		sizeof (lcs_header_type);
+#else
+	    curr_pkt_len =
+		lcs_header_ptr->offset - curr_offset - sizeof (lcs_header_type);
+	    curr_skb = lcs_dev_alloc_skb (curr_pkt_len);
+	    if (curr_skb) {
+		curr_skb->dev = dev;
+		memcpy (skb_put (curr_skb, curr_pkt_len), lcs_header_ptr + 1,
+			curr_pkt_len);
+		curr_skb->protocol =
+		    drvr_globals->lan_type_trans (curr_skb, dev);
+		curr_skb->ip_summed =
+		    (checksum_received_ip_pkts ? CHECKSUM_NONE :
+		     CHECKSUM_UNNECESSARY);
+		stats->rx_bytes += curr_pkt_len;
+		if (use_hw_stats == FALSE)
+		    stats->rx_packets++;
+		lcs_debug_event (2, "netif_rx called\n");
+		lcs_debug_event (2, "received %u bytes rx_bytes=%lu\n",
+				 (unsigned) curr_pkt_len, stats->rx_bytes);
+		netif_rx (curr_skb);
+	    } else {
+		lcs_bad_news ("%s: Memory squeeze, dropping packet.\n",
+			      dev->name);
+		drvr_globals->stats.rx_dropped++;
+	    }
+#endif
+	}
+	curr_offset = lcs_header_ptr->offset;
+	((addr_t) lcs_header_ptr) = start_lcs_buff + curr_offset;
+    }
+#if LCS_ZERO_COPY_READ
+    if (curr_skb)
+	lcs_rxskb (drvr_globals, curr_skb, curr_pkt_len, curr_skb_offset,
+		   stats);
+#endif
+    return;
+}
+
+/*
+ * This is the main interrupt handler it does a lot of sanity checking &
+ * wakes up a process if doing startup or shutdown or calls 
+ * queues lcs_rxpacket if a network packet is received or
+ * resets transmit busy a network packet has been transmitted &
+ * marks the bottom half so that networking knows it can transmit more packets 
+ * N.B. if we decide to call ssch directly or indirectly from the irq_handler 
+ * we had better increment chan_globals->lock_cnt & decrement it when done
+ * to avoid deadlocks (this count can be over 2 inside the interrupt handler),
+ * but not in normal tasks.
+ */
+
+void
+lcs_handle_irq (int irq, devstat_t * devstat, struct pt_regs *regs)
+{
+    union {
+	lcs_read_globals *read;
+	lcs_write_globals *write;
+	lcs_chan_globals *globals;
+    } chan;
+    struct net_device *dev = NULL;
+    lcs_drvr_globals *drvr_globals = NULL;
+    lcs_debug_initmessage struct net_device_stats *stats;
+    ccw1_t *curr_ccw;
+    lcs_header_type *lcs_hdr;
+    u32 curr_ccwidx, curr_ccwidx2, curr_idx;
+    int chan_globals_valid = FALSE;
+
+    chan.globals = NULL;
+    if (!devstat)
+	goto lcs_int_spurious;
+    chan.globals = (lcs_chan_globals *) devstat->intparm;
+    if (!chan.globals || (irq != chan.globals->subchannel))
+	goto lcs_int_spurious;
+    chan_globals_valid = TRUE;
+    drvr_globals = chan.globals->drvr_globals;
+    lcs_debug_event (2, "lcs_handle_irq irq=%x chan_globals=%p drvr_globals=%p"
+		     " drvr_state=%d devstat=%p irb=%p cpu=%d\n",
+		     irq, chan.globals, drvr_globals, drvr_globals->state,
+		     &chan.globals->devstat,
+		     &chan.globals->devstat.ii.irb, smp_processor_id ());
+    if (chan.globals->lock_cnt)
+	lcs_bad_news ("lcs_handle_irq lock owner %d lock_cnt=%d\n",
+		      chan.globals->lock_owner, chan.globals->lock_cnt);
+    if (drvr_globals->state == lcs_interrupted) {
+	lcs_wakeup (chan.globals, -EINTR);
+    } else {
+	if (lcs_check_reset (chan.globals))
+	    return;
+	if (chan.globals->irq_allocated_magic != LCS_MAGIC)
+	    goto lcs_int_spurious;
+    }
+    dev = drvr_globals->dev;
+    if (dev == NULL && drvr_globals->state == lcs_doing_io) {
+	lcs_debug_setmessage ("dev=NULL in handle irq");
+	goto lcs_int_debug_info;
+    }
+    stats = &drvr_globals->stats;
+    if (drvr_globals->state == lcs_halting_subchannels) {
+	lcs_wakeup (chan.globals, 0);
+	return;
+    }
+    chan.globals->lock_owner = smp_processor_id ();
+    chan.globals->lock_cnt = 1;
+    if (drvr_globals->read == chan.read) {
+	if (devstat->dstat != 0 || (devstat->cstat & ~SCHN_STAT_PCI) != 0) {
+
+	    lcs_debug_event (0,
+			     "lcs_handle_irq dstat=%02x cstat=%02x "
+			     "drvr_globals_state=%d\n",
+			     devstat->dstat, devstat->cstat,
+			     drvr_globals->state);
+	    switch (drvr_globals->state) {
+	    case lcs_idle_requesting_channels_for_lancmds:
+	    case lcs_requesting_channels_for_lancmds:
+	    case lcs_got_write_channel_for_lancmds:
+	    case lcs_doing_lancmds:
+		lcs_restartreadio (drvr_globals);
+		break;
+	    case lcs_doing_io:
+		lcs_retry (chan.globals);
+		break;
+	    default:
+	    }
+	    goto lcs_normal_ret;
+	}
+	if (chan.read->chan_busy_state == chan_starting_up)
+	    chan.read->chan_busy_state = chan_idle;
+	else {
+	    switch (drvr_globals->state) {
+	    case lcs_doing_io:
+	    case lcs_doing_lancmds:
+
+		curr_idx =
+		    ((ccw1_t *) phys_to_virt (devstat->cpa) -
+		     &chan.read->ccw[0]);
+		/* We don't need to divide by sizeof(ccw1_t) here as we are
+		   doing pointer arithmetic */
+		curr_ccwidx = ((curr_idx - 2) & (LCS_NUM_RX_BUFFS - 1));
+		curr_ccwidx2 = ((curr_idx - 1) & (LCS_NUM_RX_BUFFS - 1));
+		for (curr_idx = chan.read->rx_idx;
+		     curr_idx != curr_ccwidx2;
+		     curr_idx = ((curr_idx + 1) & (LCS_NUM_RX_BUFFS - 1))) {
+#if LCS_ZERO_COPY_READ
+		    lcs_hdr =
+			(lcs_header_type *) (chan.read->skb_buff[curr_idx]->
+					     data);
+#else
+		    lcs_hdr = (lcs_header_type *)
+			(chan.read->iobuff[curr_idx]);
+#endif
+
+		    if (lcs_hdr->offset != LCS_ILLEGAL_OFFSET) {
+			/* Detect duplicate interrupts unfilled buffers etc. */
+			lcs_rxpacket (drvr_globals, (u8 *) lcs_hdr
+#if LCS_ZERO_COPY_READ
+				      , curr_idx
+#endif
+			    );
+			if (chan.read->lancmd_reply_ptr == NULL) {
+#if LCS_ZERO_COPY_READ
+			    lcs_hdr =
+				(lcs_header_type *) (chan.read->
+						     skb_buff[curr_idx]->data);
+#endif
+			    lcs_hdr->offset = LCS_ILLEGAL_OFFSET;
+			}
+		    } else {
+			lcs_bad_news ("lcs_handle_irq race condition Illegal"
+				      " offset buffer for ccw=%p\n",
+				      &chan.read->ccw[curr_idx]);
+			goto lcs_int_debug_info;
+		    }
+		}
+		if (chan.read->rx_idx != curr_ccwidx2) {
+		    curr_ccw = &chan.read->ccw[curr_ccwidx];
+		    curr_ccw->flags =
+			CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_SUSPEND
+#if LCS_USE_IDALS
+			| (curr_ccw->flags & CCW_FLAG_IDA)
+#endif
+			;
+		    curr_idx = (chan.read->rx_idx - 1) & (LCS_NUM_RX_BUFFS - 1);
+		    curr_ccw = &chan.read->ccw[curr_idx];
+		    curr_ccw->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_PCI
+#if LCS_USE_IDALS
+			| (curr_ccw->flags & CCW_FLAG_IDA)
+#endif
+			;
+		    chan.read->rx_idx = curr_ccwidx2;
+		}
+		break;
+	    default:
+	    }
+	}
+
+	/*
+	   We need to suspend if till the lancmd reply is consumed.
+	 */
+	if (
+#if BUG
+	       (devstat->flag & DEVSTAT_SUSPENDED)
+#else
+	       devstat->ii.irb.scsw.actl & SCSW_ACTL_SUSPENDED
+#endif
+	       && chan.read->lancmd_reply_ptr == NULL) {
+
+	    chan.read->chan_busy_state = chan_idle;
+	    lcs_debug_event (2, "resume called from interrupt handler.\n");
+	    lcs_interrupt_resume_read (chan.read);
+	}
+    } else {
+
+	if (devstat->dstat != 0 ||
+	    devstat->flag & DEVSTAT_FLAG_SENSE_AVAIL || devstat->cstat != 0) {
+	    lcs_debug_event (0,
+			     "lcs_handle_irq write channel dstat=%02x flag=%x "
+			     "cstat=%02x drvr_globals_state=%d\n",
+			     (int) devstat->dstat, (int) devstat->flag,
+			     (int) devstat->cstat, drvr_globals->state);
+	    switch (drvr_globals->state) {
+	    case lcs_idle_requesting_channels_for_lancmds:
+	    case lcs_requesting_channels_for_lancmds:
+	    case lcs_got_write_channel_for_lancmds:
+	    case lcs_doing_lancmds:
+		lcs_restartwriteio (drvr_globals);
+		break;
+	    case lcs_doing_io:
+		lcs_queue_thread (lcs_lgw_resetlan, drvr_globals);
+		break;
+	    default:
+	    }
+	    goto lcs_normal_ret;
+	}
+	if (chan.write->chan_busy_state == chan_starting_up)
+	    chan.write->chan_busy_state = chan_idle;
+	else {
+	    chan.write->chan_busy_state = chan_idle;
+
+#if BUG
+	    if (devstat->flag & DEVSTAT_SUSPENDED)
+#else
+	    if (devstat->ii.irb.scsw.actl & SCSW_ACTL_SUSPENDED)
+#endif
+	    {
+		switch (drvr_globals->state) {
+		case lcs_doing_io:
+		case lcs_requesting_channels_for_lancmds:
+		case lcs_doing_lancmds:
+		    curr_idx =
+			((ccw1_t *) phys_to_virt (devstat->cpa) -
+			 &chan.write->ccw[0]);
+		    curr_ccwidx = ((curr_idx - 2) & (LCS_NUM_TX_BUFFS - 1));
+		    curr_ccwidx2 = ((curr_idx - 1) & (LCS_NUM_TX_BUFFS - 1));
+		    if (curr_ccwidx == chan.write->tx_idx) {
+			lcs_bad_news
+			    ("lcs_handle_irq race tx_idx=curr_ccwidx "
+			     "write_globals=%p\n",
+			     chan.write);
+			goto lcs_int_debug_info;
+		    }
+
+		    for (curr_idx =
+			 ((chan.write->tx_idx + 1) & (LCS_NUM_TX_BUFFS - 1));
+			 curr_idx != curr_ccwidx2;
+			 curr_idx = ((curr_idx + 1) & (LCS_NUM_TX_BUFFS - 1))) {
+			curr_ccw = &chan.write->ccw[curr_idx];
+			curr_ccw->flags |= CCW_FLAG_SUSPEND;
+			curr_ccw->count = 0;
+		    }
+		    chan.write->tx_idx = curr_ccwidx;
+		    curr_ccw = &chan.write->ccw[chan.write->prepare_idx];
+		    if (curr_ccw != &chan.write->ccw[curr_ccwidx]) {
+			if (curr_ccw->count != 0) {
+			    /* Tx the buffer currently being prepared */
+			    if (drvr_globals->state == lcs_doing_io &&
+				lcs_should_queue_write (chan.write,
+							lcs_get_tod ()))
+				lcs_queue_write_if_necessary (chan.write);
+			    else
+				lcs_resume_write (chan.write);
+			}
+		    } else {
+			lcs_bad_news("lcs_handle_irq race tx_idx=prepare_idx "
+				     "write_globals=%p\n",
+			     chan.write);
+			goto lcs_normal_ret;
+		    }
+		    break;
+		default:
+		}
+	    }
+	}
+	switch (drvr_globals->state) {
+	case lcs_idle_requesting_channels_for_lancmds:
+	case lcs_requesting_channels_for_lancmds:
+	    drvr_globals->state = lcs_got_write_channel_for_lancmds;
+	case lcs_doing_lancmds:
+	    lcs_wakeup (chan.globals, 0);
+	    break;
+	case lcs_doing_io:
+	    stats->tx_bytes += chan.write->bytes_still_being_txed;
+	    chan.write->bytes_still_being_txed = 0;
+	    if (!use_hw_stats) {
+		stats->tx_packets += chan.write->pkts_still_being_txed;
+		chan.write->pkts_still_being_txed = 0;
+
+	    }
+	    netif_start_queue (dev);
+	    break;
+	default:
+	}
+    }
+    goto lcs_normal_ret;
+lcs_int_spurious:
+    lcs_debug_setmessage ("received spurious interrupt");
+lcs_int_debug_info:
+    lcs_debugchannel (message, chan.globals);
+lcs_normal_ret:
+    lcs_debug_event (2, "lcs irq exited irq=%x\n", irq);
+    if (chan_globals_valid) {
+	chan.globals->lock_owner = LCS_INVALID_LOCK_OWNER;
+	chan.globals->lock_cnt = 0;
+    }
+}
+
+/* 
+ * Wrappers to allocate irqs if these fail the io channel is probably
+ * being used by another device.
+ */
+static int
+lcs_allocirq_func (lcs_chan_globals * chan_globals)
+{
+    int rc;
+
+    lcs_debug_event (2, "lcs_allocirq called chan_globals=%p\n", chan_globals);
+    if (chan_globals->irq_allocated_magic) {
+	lcs_bad_news ("lcs_allocirq irq already allocated");
+	return -EBUSY;
+    } else {
+	chan_globals->devstat.intparm = (addr_t) chan_globals;
+	rc =
+#if LCS_CHANDEV
+	    chandev_request_irq
+#else
+	    request_irq
+#endif
+	    (chan_globals->subchannel,
+	     (void (*)(int, void *, struct pt_regs *)) lcs_handle_irq,
+	     0, cardname, &chan_globals->devstat);
+	if (!rc)
+	    chan_globals->irq_allocated_magic = LCS_MAGIC;
+
+#if LCS_DEBUG
+	else
+	    lcs_debug_event (2,"request irq failed error code %d "
+			     "subchannel %2X\n",
+			     rc, chan_globals->subchannel);
+#endif
+	return rc;
+    }
+}
+
+#define lcs_allocirq(chan_globals) \
+	lcs_allocirq_func((lcs_chan_globals *)(chan_globals))
+
+static void
+lcs_freeirq (lcs_chan_globals * chan_globals)
+{
+    if (chan_globals->irq_allocated_magic) {
+#if LCS_CHANDEV
+	chandev_free_irq (chan_globals->subchannel, &chan_globals->devstat);
+#else
+	free_irq (chan_globals->subchannel, &chan_globals->devstat);
+#endif
+	chan_globals->irq_allocated_magic = 0;
+    }
+}
+
+static lcs_inline void
+lcs_freeallirqs (lcs_drvr_globals * drvr_globals)
+{
+    lcs_debug_event (2, "lcs_freeallirqs called %p\n", drvr_globals);
+    lcs_freeirq ((lcs_chan_globals *) drvr_globals->read);
+    lcs_freeirq ((lcs_chan_globals *) drvr_globals->write);
+}
+
+/*
+ * Generic routine for sending all startup & shutdown command primatives  
+ * to lcs.
+ * A unit check with a sense code of 0x42 may be presented here if this
+ * happens we restart the startup procedure, currently the osa card has been
+ * seen to present the unit check very late in the startup sequence 
+ * & resend outstanding buffers already read, the source of this problem
+ * is unknown, this problem is handled basically by checking sequence 
+ * numbers & disgarding packets already received ).
+ */
+static int
+lcs_lancmd (lcs_cmd cmd_code, u8 rel_adapter_no, lcs_drvr_globals * drvr_globals
+#ifdef CONFIG_IP_MULTICAST
+	    , u16 num_ipm_pairs, lcs_ip_mac_addr_pair * ip_mac_pairs
+#endif
+    )
+{
+    lcs_std_cmd *readcmd, *writecmd;
+    lcs_read_globals *read;
+    lcs_write_globals *write;
+    int rc = 0;
+    int retry_cnt = 0;
+
+    read = drvr_globals->read;
+    write = drvr_globals->write;
+    lcs_debug_exception (2, "lcs_lancmd cmd code=%02x drvr_globals=%p\n",
+			 (int) cmd_code, drvr_globals);
+    wait_on_write (drvr_globals);
+Retry:
+    read->lancmd_reply_ptr = NULL;
+    do {
+	if ((writecmd = lcs_prepare_lancmd (write, cmd_code, rel_adapter_no,
+					    lcs_390_initiated,
+					    drvr_globals->lan_type)) == 0) {
+	    lcs_debug_event (2, "lancmd2 lcs_prepare_cmd failed\n");
+	    return (-EIO);
+	}
+	lcs_debug_event (2, "lcs_lancmd writecmd=%p\n", writecmd);
+	read->lancmd_reply_ptr = NULL;
+	lcs_initqueue (read);
+#ifdef CONFIG_IP_MULTICAST
+	if (cmd_code == lcs_setipm || cmd_code == lcs_delipm) {
+	    ((lcs_ipm_ctlmsg *) writecmd)->version = 4;
+	    ((lcs_ipm_ctlmsg *) writecmd)->num_ipm_pairs = num_ipm_pairs;
+	    memcpy (&((lcs_ipm_ctlmsg *) writecmd)->ip_mac_pair[0],
+		    ip_mac_pairs,
+		    sizeof (lcs_ip_mac_addr_pair) * num_ipm_pairs);
+	}
+	if (cmd_code == lcs_qipassist) {
+	    ((lcs_qipassist_ctlmsg *) writecmd)->num_ip_pairs = 0;
+	    ((lcs_qipassist_ctlmsg *) writecmd)->version = 4;
+	}
+#endif
+	writecmd->sequence_no = ++drvr_globals->sequence_no;
+	lcs_debug_event (2, "sequence_no=%d\n",
+			 (int) drvr_globals->sequence_no);
+	drvr_globals->cmd_code = cmd_code;
+	rc = lcs_resumeandwait (write, cmd_code);
+    } while (rc == -EAGAIN && retry_cnt++ < 3);
+    if (rc)
+	goto Done;
+    rc = lcs_sleepon (read, lcs_calcsleeptime (drvr_globals, cmd_code));
+    if ((readcmd = read->lancmd_reply_ptr) == NULL || rc == -EAGAIN) {
+	if (retry_cnt++ < 3) {
+	    lcs_debug_event (2, "retrying cmd_code=0x02x read_cmd=%p rc=%d\n",
+			     (int) cmd_code, readcmd, rc);
+	    goto Retry;
+	} else {
+	    lcs_debug_event (2, "lcs_lancmd retry count exceeded giving up\n");
+	    goto Done;
+	}
+    }
+    if (readcmd->cmd_code != cmd_code
+	|| (writecmd->sequence_no - readcmd->sequence_no) > 3) {
+	lcs_debug_event (1,
+			 "readcmd=%p read_cmd_code=%02x write_cmd_code=%02x "
+			 "read_seq=%04x write_seq=%04x\n",
+			 readcmd, (int) readcmd->cmd_code, (int) cmd_code,
+			 (int) readcmd->sequence_no,
+			 (int) writecmd->sequence_no);
+	goto Fail;
+    } else {
+	switch (cmd_code) {
+	case lcs_lanstat:
+	    if (readcmd->rel_adapter_no != rel_adapter_no) {
+		lcs_debug_event (1, "rel adapters differ\n");
+		goto Fail;
+	    }
+	    /* I got back a funny rel adapter no from stoplan so I'll assume 
+	     * it's broke */
+	case lcs_startlan:
+	    if (readcmd->lan_type != writecmd->lan_type) {
+		lcs_debug_event (1, "lan types differ\n");
+		goto Fail;
+	    }
+	    if (readcmd->return_code != 0) {
+		lcs_debug_event (2, "readcmd_return_code=%02x\n",
+				 (int) readcmd->return_code);
+		rc = -ENXIO;
+		goto Done;
+	    }
+	case lcs_stoplan:
+	    /* the stoplan primative should return the adapter type */
+	    /* it dosen't appar to though */
+	case lcs_startup:
+	case lcs_shutdown:
+#ifdef CONFIG_IP_MULTICAST
+	case lcs_setipm:
+	case lcs_delipm:
+	case lcs_qipassist:
+#endif
+	    if (readcmd->return_code != 0)
+		goto Fail;
+
+	}
+    }
+Done:
+    lcs_debug_event (2, "lcs_lancmd cmd_code=%02x rc=%d reply_ptr=%p\n",
+		     (int) cmd_code, rc, read->lancmd_reply_ptr);
+#if LCS_DEBUG
+    if (rc && read->lancmd_reply_ptr)
+	lcs_debug_display_read_buff ("reply", read->lancmd_reply_ptr);
+#endif
+    drvr_globals->state = lcs_idle;
+    if (rc || (cmd_code != lcs_lanstat
+#ifdef CONFIG_IP_MULTICAST
+	       && cmd_code != lcs_qipassist
+#endif
+	))
+	lcs_resume_read (read);
+    return rc;
+Fail:
+    rc = -EIO;
+    goto Done;
+}
+
+#ifdef CONFIG_IP_MULTICAST
+int
+lcs_print_ipassists (lcs_qipassist_ctlmsg * reply, char *name,
+		     char *ipassist_str, lcs_assists mask)
+{
+    lcs_good_news ("%s: %s supported %s enabled %s\n",
+		   name, ipassist_str,
+		   reply->ip_assists_supported & mask ? "yes" : "no",
+		   reply->ip_assists_enabled & mask ? "yes" : "no");
+    return ((reply->ip_assists_supported & reply->
+	     ip_assists_enabled & mask) ? TRUE : FALSE);
+}
+static int
+lcs_check_multicast_supported (lcs_drvr_globals * drvr_globals)
+{
+    lcs_qipassist_ctlmsg *lcs_reply;
+    char *name = drvr_globals->dev->name;
+    int rc = FALSE;
+    if (lcs_lancmd (lcs_qipassist, drvr_globals->rel_adapter_no,
+		    drvr_globals LANCMD_DEFAULT_MCAST_PARMS) == 0) {
+	lcs_reply = (lcs_qipassist_ctlmsg *)
+	    drvr_globals->read->lancmd_reply_ptr;
+	if (lcs_reply) {
+	    lcs_print_ipassists (lcs_reply, name, "ip v6", lcs_ip_v6_support);
+	    rc = lcs_print_ipassists (lcs_reply, name, "multicast",
+				      lcs_multicast_support);
+	}
+    } else
+	lcs_good_news
+	    ("qipassist failed, ipassists assumed unsupported for %s\n", name);
+    lcs_resume_read (drvr_globals->read);
+    return (rc);
+}
+#endif
+
+/* 
+ * Update network statistics from hardware.
+ */
+int
+lcs_gethwinfo (struct net_device *dev)
+{
+    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);
+    lcs_lanstat_reply *lcs_reply;
+    struct net_device_stats *stats;
+    int rc;
+    lcs_read_globals *read = drvr_globals->read;
+
+    if ((rc = lcs_lancmd (lcs_lanstat,
+			  drvr_globals->rel_adapter_no,
+			  drvr_globals LANCMD_DEFAULT_MCAST_PARMS)) == 0) {
+
+	lcs_reply = (lcs_lanstat_reply *)
+	    read->lancmd_reply_ptr;
+	if (lcs_reply) {
+	    stats = &drvr_globals->stats;
+	    stats->rx_packets = lcs_reply->num_packets_rx_from_lan;
+	    stats->rx_errors = lcs_reply->num_rx_errors_detected;
+	    stats->rx_length_errors = lcs_reply->num_rx_packets_too_large;
+	    stats->rx_missed_errors = lcs_reply->num_rx_discarded_nobuffs_avail;
+	    stats->tx_packets = lcs_reply->num_packets_tx_on_lan;
+	    stats->tx_errors = lcs_reply->num_tx_errors_detected;
+	    stats->tx_aborted_errors = lcs_reply->num_tx_packets_disgarded;
+	}
+    }
+#if LCS_DEBUG
+    else
+	lcs_debug_event (2, "gethwinfo failed\n");
+#endif
+    lcs_resume_read (read);
+    return rc;
+}
+
+/*
+ * Displays the mac address etc. on startup.
+ */
+static void
+lcs_displayinfo (struct net_device *dev)
+{
+    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);
+    u8 *mac_address = dev->dev_addr;
+    lcs_read_globals *read = drvr_globals->read;
+    lcs_write_globals *write = drvr_globals->write;
+
+    lcs_good_news
+	("\nlcs: %s configured as follows read subchannel=%x "
+	 "write subchannel=%x\n"
+	 "read_devno=%04x write_devno=%04x\n"
+	 "hw_address=%02X:%02X:%02X:%02X:%02X:%02X rel_adapter_no=%d\n",
+	 dev->name, read->subchannel, write->subchannel, (int) read->devno,
+	 (int) write->devno, (int) mac_address[0], (int) mac_address[1],
+	 (int) mac_address[2], (int) mac_address[3], (int) mac_address[4],
+	 (int) mac_address[5], drvr_globals->rel_adapter_no);
+}
+
+/* shutdown osa card */
+
+int
+lcs_halt_channels (lcs_drvr_globals * drvr_globals, int delay)
+{
+    int rc, rc2;
+    if (delay & 1) {
+
+	current->state = TASK_INTERRUPTIBLE;
+	schedule_timeout (2);
+    }
+    rc = lcs_hsch (drvr_globals->read);
+    if (delay & 2) {
+
+	current->state = TASK_INTERRUPTIBLE;
+	schedule_timeout (2);
+    }
+    drvr_globals->read->chan_busy_state = chan_dead;
+    rc2 = lcs_hsch (drvr_globals->write);
+    if (delay & 4) {
+
+	current->state = TASK_INTERRUPTIBLE;
+	schedule_timeout (2);
+    }
+    drvr_globals->write->chan_busy_state = chan_dead;
+    return (rc ? rc : rc2);
+}
+
+static int
+lcs_stopcard (lcs_drvr_globals * drvr_globals)
+{
+    int rc1, rc2, rc3;
+
+    rc1 = rc2 = rc3 = 0;
+    drvr_globals->up = FALSE;
+    lcs_debug_event (2, "lcs_stopcard called\n");
+    if (drvr_globals->state == lcs_interrupted)
+	drvr_globals->state = lcs_idle;
+    /* Make sure that we don't attempt to resume a channel */
+    /* that was halted or dead to avoid the -ENOTCONNECTED */
+    /* errors reported from Ingo on resume_IO */
+    if (drvr_globals->read->chan_busy_state != chan_dead &&
+	drvr_globals->write->chan_busy_state != chan_dead) {
+	rc1 = lcs_lancmd (lcs_stoplan, drvr_globals->rel_adapter_no,
+			  drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
+	/* We probably haven't got a good relative adapter no */
+	if (drvr_globals->rel_adapter_no != lcs_invalid_adapter_no)
+	    rc2 = lcs_lancmd (lcs_shutdown, 0, drvr_globals
+			      LANCMD_DEFAULT_MCAST_PARMS);
+    }
+    lcs_resetstate (drvr_globals);
+    rc3 = lcs_halt_channels (drvr_globals, 7);
+    lcs_resetstate (drvr_globals);
+    return (rc1 ? rc1 : (rc2 ? rc2 : rc3));
+}
+
+static void
+lcs_initreadccws (lcs_read_globals * read)
+{
+
+    int cnt;
+    ccw1_t *origccws, *ccws;
+#if LCS_ZERO_COPY_READ
+    struct sk_buff *curr_skb;
+#else
+    u8 *curr_iobuff;
+#endif
+
+    origccws = ccws = read->ccw;
+    memset (ccws, 0, sizeof (ccw1_t) * LCS_NUM_RX_BUFFS + 1);
+    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++, ccws++) {
+	ccws->cmd_code = ccw_read;
+	ccws->count = LCS_IOBUFFSIZE;
+	ccws->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_PCI;
+#if LCS_ZERO_COPY_READ
+	curr_skb = read->skb_buff[cnt];
+	set_normalized_cda (ccws, virt_to_phys ((void *) curr_skb->data));
+	((lcs_header_type *) curr_skb->data)->offset = LCS_ILLEGAL_OFFSET;
+#else
+	curr_iobuff = read->iobuff[cnt];
+	set_normalized_cda (ccws, virt_to_phys ((void *) curr_iobuff));
+	((lcs_header_type *) curr_iobuff)->offset = LCS_ILLEGAL_OFFSET;
+#endif
+    }
+    ccws->cmd_code = ccw_transfer_in_channel1;
+    (u32) ccws->cda = (u32) virt_to_phys (origccws);
+    ccws = &read->ccw[LCS_NUM_RX_BUFFS - 1];
+    ccws->flags = CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_SUSPEND
+#if LCS_USE_IDALS
+	| (ccws->flags & CCW_FLAG_IDA)
+#endif
+	;
+    read->lancmd_reply_ptr = NULL;
+    read->rx_idx = 0;
+    read->chan_busy_state = chan_starting_up;
+}
+
+static void
+lcs_initwriteccws (lcs_write_globals * write)
+{
+    initccws (write->ccw, write->iobuff, LCS_NUM_TX_BUFFS, ccw_write, 0,
+	      CCW_FLAG_CC | CCW_FLAG_SLI | CCW_FLAG_SUSPEND);
+    write->prepare_idx = 0;
+    write->tx_idx = LCS_NUM_TX_BUFFS - 1;
+    write->resume_queued = FALSE;
+    write->bytes_still_being_txed = write->pkts_still_being_txed = 0;
+    write->adjusted_last_bytes_still_being_txed = 0;
+    write->last_resume_time = lcs_get_tod ();
+    write->chan_busy_state = chan_starting_up;
+}
+
+static void
+lcs_startreadio (lcs_read_globals * read)
+{
+    lcs_debug_event (0, "lcs_startreadio read_globals=%p\n", read);
+    lcs_initreadccws (read);
+    lcs_doio (read, DOIO_DENY_PREFETCH | DOIO_ALLOW_SUSPEND
+	      /*|DOIO_REPORT_ALL */ );
+
+}
+
+static void
+lcs_startwriteio (lcs_write_globals * write)
+{
+    lcs_debug_event (0, "lcs_startwriteio write_globals=%p\n", write);
+    lcs_initwriteccws (write);
+    lcs_doio (write,
+	      DOIO_DENY_PREFETCH | DOIO_ALLOW_SUSPEND /*|DOIO_REPORT_ALL */ );
+}
+
+static void
+lcs_restartreadio (lcs_drvr_globals * drvr_globals)
+{
+#if LCS_USE_IDALS
+    int cnt;
+    ccw1_t *ccws;
+#endif
+#if LCS_USE_IDALS
+    ccws = drvr_globals->read->ccw;
+    for (cnt = 0; cnt < LCS_NUM_RX_BUFFS; cnt++, ccws++)
+	clear_normalized_cda (ccws);
+#endif
+    lcs_wakeup (drvr_globals->read, -EAGAIN);
+    lcs_startreadio (drvr_globals->read);
+}
+
+static void
+lcs_queued_restartreadio (lcs_drvr_globals * drvr_globals)
+{
+
+    if (!lcs_drvr_globals_valid (drvr_globals))
+	goto Done2;
+    if (drvr_globals->state == lcs_idle
+	|| drvr_globals->state == lcs_halting_subchannels)
+	goto Done1;
+    lcs_restartreadio (drvr_globals);
+Done1:
+    lcs_usage_free_drvr_globals (drvr_globals);
+Done2:
+    MOD_DEC_USE_COUNT;
+}
+
+static void
+lcs_restartwriteio (lcs_drvr_globals * drvr_globals)
+{
+    lcs_write_globals *write;
+
+    write = drvr_globals->write;
+    write->bytes_still_being_txed = 0;
+    if (!use_hw_stats) {
+	drvr_globals->stats.tx_dropped += write->pkts_still_being_txed;
+	drvr_globals->stats.tx_errors += write->pkts_still_being_txed;
+	write->pkts_still_being_txed = 0;
+    }
+    switch (drvr_globals->state) {
+    case lcs_doing_io:
+	netif_start_queue (drvr_globals->dev);
+	break;
+    case lcs_idle_requesting_channels_for_lancmds:
+    case lcs_requesting_channels_for_lancmds:
+    case lcs_got_write_channel_for_lancmds:
+	lcs_wakeup (write, 0);
+	break;
+    case lcs_doing_lancmds:
+	lcs_wakeup (write, -EAGAIN);
+	break;
+    default:
+    }
+    lcs_startwriteio (write);
+}
+
+static void
+lcs_queued_restartwriteio (lcs_drvr_globals * drvr_globals)
+{
+    if (!lcs_drvr_globals_valid (drvr_globals))
+	goto Done2;
+    if (drvr_globals->state == lcs_idle ||
+	drvr_globals->state == lcs_halting_subchannels)
+	goto Done1;
+    lcs_restartwriteio (drvr_globals);
+Done1:
+    lcs_usage_free_drvr_globals (drvr_globals);
+Done2:
+    MOD_DEC_USE_COUNT;
+}
+
+void
+lcs_startallio (lcs_drvr_globals * drvr_globals)
+{
+    if (drvr_globals->dev)
+	netif_stop_queue (drvr_globals->dev);
+    drvr_globals->state = lcs_idle_requesting_channels_for_lancmds;
+    lcs_startreadio (drvr_globals->read);
+    lcs_initqueue (drvr_globals->write);
+    lcs_startwriteio (drvr_globals->write);
+    lcs_sleepon (drvr_globals->write, 0);
+    drvr_globals->state = lcs_doing_lancmds;
+}
+
+/*
+ * The osa detection/startup routine called from lcs_probe on bootup & 
+ * lcs_open this is called after the irq's are allocated & successful
+ * sensing of the read & write subchannels.
+ */
+
+static int
+lcs_detect (lcs_drvr_globals * drvr_globals, s16 forced_port_no,
+	    u8 hint_port_no, u8 max_port_no, lcs_frame_type frame_type,
+	    u8 * mac_address, int retrycnt)
+{
+    int rc, successful_startup = FALSE;
+    int rel_adapter_no, rel_adapter_idx;
+    lcs_read_globals *read;
+    lcs_write_globals *write;
+    lcs_lanstat_reply *lcs_reply;
+
+    read = drvr_globals->read;
+    write = drvr_globals->write;
+#ifdef CONFIG_IP_MULTICAST
+    spin_lock_init (&drvr_globals->ipm_lock);
+#endif
+    lcs_debug_event (1, "Entering detect read subchannel=%X\n",
+		     (unsigned) drvr_globals->read->subchannel);
+    if (drvr_globals->dev)
+	netif_stop_queue (drvr_globals->dev);
+    do {
+	lcs_debug_event (1, "lcs_detect retrycnt=%d\n", retrycnt);
+	if (retrycnt >= 0) {
+	    if (retrycnt > 0)
+		lcs_resetstate (drvr_globals);
+	    if (retrycnt > 1) {
+		/* Time to change tactics */
+		rc = lcs_stopcard (drvr_globals);
+	    } else
+		rc = lcs_halt_channels (drvr_globals, retrycnt == 0 ? 0 : 7);
+	    if (rc)
+		continue;
+	    lcs_startallio (drvr_globals);
+	    if ((rc =
+		 lcs_lancmd (lcs_startup, 0,
+			     drvr_globals LANCMD_DEFAULT_MCAST_PARMS)))
+		continue;
+	    else
+		successful_startup = TRUE;
+	} else if ((rc = lcs_lgw_common (drvr_globals, lcs_startup)))
+	    continue;
+	else
+	    successful_startup = TRUE;
+	for (rel_adapter_idx = 0; rel_adapter_idx <= max_port_no &&
+	     (rc == -ENXIO || rel_adapter_idx == 0); rel_adapter_idx++) {
+	    if (forced_port_no != -1) {
+		if (rel_adapter_idx > 0)
+		    break;
+		rel_adapter_no = forced_port_no;
+	    } else {
+		rel_adapter_no = (rel_adapter_idx == 0 ? hint_port_no :
+				  (rel_adapter_idx ==
+				   hint_port_no ? 0 : rel_adapter_idx));
+	    }
+	    lcs_debug_event (2, "Trying startlan rel adapter no %u\n",
+			     (unsigned) rel_adapter_no);
+	    rc = -1;
+	    if (frame_type == lcs_autodetect_type) {
+#ifdef CONFIG_NET_ETHERNET
+		drvr_globals->lan_type = lcs_enet_type;
+		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
+				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
+		if (rc == 0)
+		    goto lcs_good_detect;
+#endif
+#ifdef CONFIG_TR
+		drvr_globals->lan_type = lcs_token_ring_type;
+		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
+				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
+		if (rc == 0)
+		    goto lcs_good_detect;
+#endif
+#ifdef CONFIG_FDDI
+		drvr_globals->lan_type = lcs_fddi_type;
+		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
+				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
+#endif
+	    } else {
+		drvr_globals->lan_type = frame_type;
+		rc = lcs_lancmd (lcs_startlan, rel_adapter_no,
+				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
+	    }
+  lcs_good_detect:
+	    if (rc == 0) {
+		lcs_debug_event (2, "Found relative adapter number %d\n",
+				 rel_adapter_no);
+		drvr_globals->rel_adapter_no = rel_adapter_no;
+      lcs_do_lanstat:
+		rc = lcs_lancmd (lcs_lanstat,
+				 drvr_globals->rel_adapter_no,
+				 drvr_globals LANCMD_DEFAULT_MCAST_PARMS);
+		if (!rc) {
+		    lcs_reply = (lcs_lanstat_reply *)
+			drvr_globals->read->lancmd_reply_ptr;
+		    if (lcs_reply == NULL)
+			goto lcs_do_lanstat;
+		    memcpy (mac_address, lcs_reply->mac_address, LCS_ADDR_LEN);
+		    lcs_resume_read (drvr_globals->read);
+		    drvr_globals->up = TRUE;
+		    return (0);
+		}
+	    }
+	}
+
+    } while ((rc == -EAGAIN || rc == -ETIMEDOUT) && retrycnt++ < 5);
+    if (successful_startup && rc)
+	lcs_bad_news
+	    ("A partially successful startup read_devno=%04x write_devno=%4x "
+	     "was detected\n"
+	     "please check your configuration parameters,cables & connection "
+	     "to the network.\n",
+	     drvr_globals->read->devno, drvr_globals->write->devno);
+    return (rc == EINTR ? -EINTR : -ENODEV);
+}
+
+/* Called by user doing ifconfig <network_device e.g. tr0> up */
+int
+lcs_open (struct net_device *dev)
+{
+    int rc = 0;
+    lcs_drvr_globals *drvr_globals;
+    lcs_read_globals *read;
+    lcs_write_globals *write;
+
+#if LCS_DEBUG_RESUME
+    lcs_read_resume_cnt = lcs_write_resume_cnt = 0;
+#endif
+    drvr_globals = ((lcs_drvr_globals *) dev->priv);
+    read = drvr_globals->read;
+    write = drvr_globals->write;
+    drvr_globals->state = lcs_idle;
+    if (use_hw_stats == FALSE)
+	memset (&drvr_globals->stats, 0, sizeof (struct net_device_stats));
+    if ((rc = lcs_detect (drvr_globals, drvr_globals->rel_adapter_no, 0, 0,
+			  drvr_globals->lan_type, dev->dev_addr, 0))) {
+	lcs_debug_event (2, "Opening %s failed errcode=%d.\n", dev->name, rc);
+	lcs_resetstate (drvr_globals);
+    } else {
+	MOD_INC_USE_COUNT;
+	lcs_start_doing_io (drvr_globals);
+	lcs_debug_event (2, "Successfully opened %s\n", dev->name);
+#if LCS_DEBUG && !LCS_PRINTK_DEBUG
+	/* When debugging we are usually only intrested till the
+	 * device is open. */
+	if (lcs_id && lcs_id->level != DEBUG_MAX_LEVEL)
+	    debug_set_level (lcs_id, 0);
+#endif
+    }
+    return rc;
+}
+
+/* Called by user doing ifconfig <network_device e.g. tr0> down */
+int
+lcs_close (struct net_device *dev)
+{
+    lcs_debug_event (2, "lcs_close called\n");
+    netif_stop_queue (dev);
+    lcs_stopcard ((lcs_drvr_globals *) dev->priv);
+    MOD_DEC_USE_COUNT;
+    return (0);
+}
+
+static int
+lcs_txerror (char *message, struct net_device *dev)
+{
+    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);
+    struct net_device_stats *stats = &drvr_globals->stats;
+#if LCS_DEBUG_DISPLAY_MESSAGES
+    lcs_write_globals *write = drvr_globals->write;
+    lcs_debugchannel (message, write);
+#endif
+    if (!use_hw_stats) {
+	stats->tx_dropped++;
+	stats->tx_errors++;
+    }
+    return -EIO;
+}
+
+int
+lcs_txpacket (struct sk_buff *skb, struct net_device *dev)
+{
+    lcs_drvr_globals *drvr_globals;
+    lcs_write_globals *write;
+    u16 pkt_len;
+    lcs_header_type *tx_lcs_header_ptr;
+    uint64_t tod, delta_tx;
+
+    lcs_debug_event (2, "lcs_txpacket\n");
+    if (dev == NULL || dev->priv == NULL) {
+	lcs_debug_event (1, "lcs txpacket detected sick code\n");
+	return -EINVAL;
+    }
+    drvr_globals = ((lcs_drvr_globals *) dev->priv);
+    write = drvr_globals->write;
+    tod = lcs_get_tod ();
+    delta_tx = tod - write->last_lcs_txpacket_time;
+    write->last_lcs_txpacket_time = tod;
+    if (write->chan_busy_state == chan_dead ||
+	write->chan_busy_state == chan_starting_up) {
+	dst_link_failure (skb);
+	dev_kfree_skb (skb);
+	if (!use_hw_stats) {
+	    struct net_device_stats *stats = &drvr_globals->stats;
+	    stats->tx_dropped++;
+	    stats->tx_errors++;
+	    stats->tx_carrier_errors++;
+	}
+	return (0);
+    }
+    if (netif_is_busy (dev))
+	return -EBUSY;
+    if (!skb)
+	return (lcs_txerror ("skb==NULL in lcs_txpacket\n", dev));
+    lcs_debug_event (2, "skb_len %d\n", skb->len);
+    pkt_len = skb->len;
+#if 0
+    /* This apparently can be omitted & saves us a lot of hassle */
+    pkt_len = (ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN);
+#endif
+    dev->trans_start = jiffies;
+    lcs_chan_lock (write);
+    {
+
+	if ((tx_lcs_header_ptr = lcs_getfreetxbuff (write,
+						    drvr_globals->lan_type,
+						    drvr_globals->
+						    rel_adapter_no,
+						    pkt_len)) == NULL) {
+	    netif_stop_queue (dev);
+	    lcs_debug_event (1, "lcs_txpacket no tx buffers available\n");
+	    lcs_resume_write (write);
+	    lcs_chan_unlock (write);
+	    return -EBUSY;
+	}
+	write->bytes_still_being_txed += pkt_len;
+	if (!use_hw_stats)
+	    write->pkts_still_being_txed++;
+	memcpy (++tx_lcs_header_ptr, skb->data, pkt_len);
+	dev_kfree_skb (skb);
+
+	if (lcs_should_queue_write (write, tod)) {
+	    lcs_queue_write_if_necessary (write);
+	} else {
+	    lcs_debug_event (1, "normal resume called\n");
+	    lcs_resume_write (write);
+	}
+    }
+    lcs_chan_unlock (write);
+    return (0);
+}
+
+/*
+ * Used by ifconfig & the proc filesystem to receive network statistics
+ */
+struct net_device_stats *
+lcs_getstats (struct net_device *dev)
+{
+    lcs_drvr_globals *drvr_globals = (lcs_drvr_globals *) dev->priv;
+    lcs_debug_event (2, "lcs_enet_stats\n");
+
+    if (dev == NULL || dev->priv == NULL)
+	return NULL;		/* shouldn't happen */
+    if (use_hw_stats) {
+	if (drvr_globals->doing_lanstat)
+	    interruptible_sleep_on (&drvr_globals->lanstat_wait);
+	else {
+	    if (drvr_globals->state == lcs_doing_io) {
+		drvr_globals->doing_lanstat = TRUE;
+		lcs_gethwinfo (dev);
+		drvr_globals->doing_lanstat = FALSE;
+		netif_start_queue (dev);
+		/* Gracefully start the lan again */
+		drvr_globals->state = lcs_doing_io;
+		/* Wake up processes awaiting lanstat results */
+		wake_up_interruptible (&drvr_globals->lanstat_wait);
+	    }
+	}
+    }
+    return &((lcs_drvr_globals *) dev->priv)->stats;
+
+}
+
+#if !LCS_CHANDEV
+/*
+ * Used to setup kernel parameters for the osa card
+ * read the printk's in the function for a description of how it
+ * should be called.
+ */
+static __inline__ lcs_model_info *
+lcs_get_model_info_by_idx (int idx)
+{
+    return (idx < LCS_CURR_NUM_MODELS ? &lcs_models[idx] :
+	    (lcs_model_info *) & additional_model_info
+	    [(idx - LCS_CURR_NUM_MODELS) << 1]);
+}
+static void
+lcs_display_conf (void)
+{
+    int cu_idx;
+    lcs_model_info *model_info;
+
+    lcs_good_news ("\nlcs configured to use %s statistics,\n"
+		   "ip checksumming of received packets is %s.\n"
+		   "autodetection is %s ignore sense is %s.\n",
+		   (use_hw_stats ? "hw" : "sw"),
+		   (checksum_received_ip_pkts ? "on" : "off"),
+		   (noauto ? "off" : "on"), (ignore_sense ? "on" : "off"));
+    lcs_good_news ("configured to detect\n");
+    for (cu_idx = 0; cu_idx < LCS_MAX_NUM_MODELS; cu_idx++) {
+	model_info = lcs_get_model_info_by_idx (cu_idx);
+	if ((model_info->max_rel_adapter_no == 0))
+	    break;
+	lcs_good_news ("cu_model 0x%02X,%d rel_adapter(s)\n",
+		       model_info->cu_model, model_info->max_rel_adapter_no);
+    }
+}
+
+#ifdef MODULE
+MODULE_AUTHOR
+("(C) 1999 IBM Corp. by DJ Barrow <djbarrow@de.ibm.com,barrow_dj@yahoo.com>");
+MODULE_DESCRIPTION ("Linux for S/390 Lan Channel Station Network Driver");
+MODULE_PARM (use_hw_stats, "i");
+MODULE_PARM_DESC (use_hw_stats,
+		  "Get network stats from LANSTAT lcs primitive as opposed to "
+		  "doing it in software\n"
+		  "this isn't used by MVS not recommended & dosen't appear to "
+		  "work usually\n");
+MODULE_PARM (checksum_received_ip_pkts, "i");
+MODULE_PARM_DESC (checksum_received_ip_pkts,
+		  "Do ip checksumming on inbound packets");
+
+MODULE_PARM (additional_model_info, "0-" LCS_ADDITIONAL_MODELS_X2_STR "i");
+MODULE_PARM_DESC (additional_model_info,
+		  "This is made up of sets of model/max rel adapter no pairs\n"
+		  "e.g. insmod additional_model_info=0x70,3 will look for "
+		  "2 ports on a model 0x70.\n");
+MODULE_PARM (devno_portno_pairs, "0-" LCS_MAX_FORCED_DEVNOS_X2_STR "i");
+MODULE_PARM_DESC (devno_portno_pairs, "devno,rel_adapter_no pairs\n"
+		  "rel adapter no of -1 indicates don't use this adapter.");
+MODULE_PARM (noauto, "i");
+MODULE_PARM_DESC (noauto,
+		  "set this equal to 1 if you want autodetection off, "
+		  "this means you have\n"
+		  "explicitly configure LCS devices with the "
+		  "devno_portno_pairs module parameter\n.");
+MODULE_PARM (ignore_sense, "i");
+MODULE_PARM_DESC (ignore_sense,
+		  "set this to 1 if you wish to ignore sense information "
+		  "from the device layer for\n"
+		  "non auto detected devices.\n");
+#else
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+static int __init
+lcs_setup_models (char *str)
+#else
+void __init
+lcs_setup_models (char *str, int *ints)
+#endif
+{
+    int max_parms = LCS_MAX_NUM_PARMS - (LCS_CURR_NUM_MODELS << 1);
+    int num_new_models;
+#if  LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    int ints[(LCS_ADDITIONAL_MODELS << 1) + 1];
+    get_options (str, (LCS_ADDITIONAL_MODELS << 1), ints);
+#endif
+    num_new_models = (ints[0] - 2);
+    if (num_new_models < 0)
+	num_new_models = 0;
+    else if (num_new_models & 1)
+	num_new_models = -1;
+    else
+	num_new_models >>= 1;
+    if ((ints[0] >= 1 && ints[0] <= max_parms) && num_new_models >= 0) {
+	use_hw_stats = ints[1];
+	if (ints[0] >= 2)
+	    checksum_received_ip_pkts = ints[2];
+	if (num_new_models) {
+	    memcpy (&additional_model_info[0], &ints[3],
+		    num_new_models * sizeof (lcs_model_info));
+	}
+	lcs_display_conf ();
+    } else {
+	lcs_bad_news ("lcs_setup: incorrect number of arguments\n");
+	printk
+	    ("Usage: lcs=hw-stats,ip-checksumming,additional cu model "
+	     "/max rel adapter no. pairs. \n");
+	printk
+	    ("    The driver now auto detects how the card is configured.\n");
+	printk ("    e.g lcs=1,1,0x60,2\n");
+	printk
+	    ("    Will collect network stats from LANSTAT lcs primitive "
+	     "as opposed\n");
+	printk
+	    ("    to doing it in software for ifconfig (this isn't used by "
+	     "MVS & doesn't\n");
+	printk ("    to be widely supported, ip checksum received packets\n");
+	printk ("    and detect 0x3088 model 0x60 devices\n");
+	printk
+	    ("    will look for 2 ports per card associated with this model.\n");
+	printk
+	    ("    The default is to use sw stats, with ip checksumming off,\n");
+	printk ("    this improves performance, network hw uses a crc32\n");
+	printk
+	    ("    ( crc64 for fddi ) which should be adequete to gaurantee\n");
+	printk ("    integrity for normal use\n");
+	printk
+	    ("    however, financial institutions etc. may like the "
+	     "additional\n");
+	printk ("    security of ip checksumming.\n");
+    }
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    return (1);
+#endif
+}
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+static int __init
+lcs_setup_devnos (char *str)
+#else
+void __init
+lcs_setup_devnos (char *str, int *ints)
+#endif
+{
+    int fixed_devnos;
+#if  LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    int ints[(LCS_MAX_FORCED_DEVNOS << 1) + 1];
+    get_options (str, (LCS_MAX_FORCED_DEVNOS << 1), ints);
+#endif
+    fixed_devnos = ints[0];
+    if ((fixed_devnos & 1) == 0 && fixed_devnos > 0) {
+	memcpy (devno_portno_pairs, &ints[1], sizeof (int) * fixed_devnos);
+    } else {
+	lcs_bad_news ("lcs_setup: incorrect number of arguments\n");
+	printk ("usage lcs_devno=devno,rel_adapter_no pairs\n");
+	printk ("rel adapter no of -1 indicates don't use this adapter.\n");
+    }
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    return (1);
+#endif
+}
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+static int __init
+lcs_noauto (char *str)
+#else
+void __init
+lcs_noauto (char *str, int *ints)
+#endif
+{
+    noauto = 1;
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    return (1);
+#endif
+}
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+static int __init
+lcs_ignore_sense (char *str)
+#else
+void __init
+lcs_ignore_sense (char *str, int *ints)
+#endif
+{
+    ignore_sense = 1;
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+    return (1);
+#endif
+}
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+__setup ("lcs_devno=", lcs_setup_devnos);
+__setup ("lcs=", lcs_setup_models);
+__setup ("lcs_noauto", lcs_noauto);
+__setup ("lcs_ignore_sense", lcs_ignore_sense);
+#endif
+#endif
+
+static lcs_inline int
+lcs_model_supported (s390_dev_info_t * dev_info_ptr, int *forced_rel_adapter_no)
+{
+    int cu_idx, dev_port_idx;
+    lcs_model_info *model_info;
+    lcs_dev_portno_pair *dev_portno;
+
+    *forced_rel_adapter_no = -1;
+    for (dev_port_idx=0; dev_port_idx<LCS_MAX_FORCED_DEVNOS; dev_port_idx++) {
+	dev_portno = &lcs_dev_portno[dev_port_idx];
+	if (dev_portno->portno == -2)
+	    break;
+	/*
+	   Check are we dealing with the device passed in as a parameter.
+	 */
+	if ((dev_portno->devno & 0xfffe) == (dev_info_ptr->devno & 0xfffe)) {
+	    /* portno=-1 we want to ignore this device */
+	    if (dev_portno->portno == -1)
+		return (-1);
+	    else {
+		*forced_rel_adapter_no = dev_portno->portno;
+		break;
+	    }
+	}
+    }
+    if (noauto && *forced_rel_adapter_no == -1)
+	return (-1);
+    if (ignore_sense)
+	return (*forced_rel_adapter_no);
+    if (dev_info_ptr->sid_data.cu_type != LCS_CUTYPE)
+	return (-1);
+    for (cu_idx = 0; cu_idx < LCS_MAX_NUM_MODELS; cu_idx++) {
+	model_info = lcs_get_model_info_by_idx (cu_idx);
+	if (model_info->max_rel_adapter_no == 0)
+	    break;
+	if (model_info->cu_model == dev_info_ptr->sid_data.cu_model) {
+	    if (*forced_rel_adapter_no == -1)
+		return (model_info->max_rel_adapter_no);
+	    else
+		return (*forced_rel_adapter_no);
+	}
+    }
+    return (-1);
+}
+
+#endif				/* LCS_CHANDEV */
+
+#if !LCS_CHANDEV
+void
+lcs_unregister (struct net_device *dev, u8 lan_type)
+{
+    if (dev) {
+	switch (lan_type) {
+#ifdef CONFIG_FDDI
+	case lcs_fddi_type:
+	    unregister_netdev (dev);
+	    break;
+#endif
+#ifdef CONFIG_NET_ETHERNET
+	case lcs_enet_type:
+	    unregister_netdev (dev);
+	    break;
+#endif
+#ifdef CONFIG_TR
+	case lcs_token_ring_type:
+	    unregister_trdev (dev);
+	    break;
+#endif
+	default:
+	    lcs_bad_news("lcs_unregister attempt to unregister %p "
+			 "unknown device type\n",dev);
+	    return;
+	}
+
+    }
+}
+#endif
+#ifdef CONFIG_IP_MULTICAST
+static int
+lcs_fix_multicast_list (lcs_drvr_globals * drvr_globals)
+{
+    lcs_ipm_list *curr_lmem;
+    lcs_state oldstate;
+
+    lcs_daemonize ("lcs_fix_multi", 0, TRUE);
+    if (!lcs_drvr_globals_valid (drvr_globals))
+	goto Done2;
+    if (drvr_globals->up == FALSE)
+	goto Done1;
+    oldstate = drvr_globals->state;
+    spin_lock (&drvr_globals->ipm_lock);
+    for (curr_lmem = drvr_globals->ipm_list; curr_lmem;
+	 curr_lmem = curr_lmem->next) {
+	switch (curr_lmem->ipm_state) {
+	case ipm_set_required:
+	    spin_unlock (&drvr_globals->ipm_lock);
+	    if (lcs_lancmd
+		(lcs_setipm, drvr_globals->rel_adapter_no, drvr_globals, 1,
+		 &curr_lmem->ipm)) {
+		lcs_good_news("lcs_fix_multicast_list Failed to add "
+			      "multicast entry %x\n",
+			      (u32) curr_lmem->ipm.ip_addr);
+	    } else
+		curr_lmem->ipm_state = ipm_on_card;
+	    spin_lock (&drvr_globals->ipm_lock);
+	    break;
+	case ipm_delete_required:
+	    spin_unlock (&drvr_globals->ipm_lock);
+	    lcs_lancmd (lcs_delipm, drvr_globals->rel_adapter_no, drvr_globals,
+			1, &curr_lmem->ipm);
+	    spin_lock (&drvr_globals->ipm_lock);
+	    if (remove_from_list ((list **) & drvr_globals->ipm_list,
+				  (list *) curr_lmem))
+		kfree (curr_lmem);
+	    else
+		lcs_bad_news
+		    ("lcs_fix_multicast_list detected nonexistant entry"
+		     " ipm_list=%p member %p\n", drvr_globals->ipm_list,
+		     curr_lmem);
+	    break;
+	case ipm_on_card:
+	    break;
+	}
+    }
+    spin_unlock (&drvr_globals->ipm_lock);
+    drvr_globals->state = oldstate;
+    if (oldstate == lcs_doing_io && drvr_globals->dev)
+	netif_start_queue (drvr_globals->dev);
+Done1:
+    atomic_set (&drvr_globals->kernel_thread_lock, 1);
+    lcs_usage_free_drvr_globals (drvr_globals);
+Done2:
+    MOD_DEC_USE_COUNT;
+    return (0);
+}
+
+/*
+  Owing to a quirk in the osa microcode it is incompatible with its API's 
+  & we can only add or delete a single ipm entry at a time.
+  We are also called with the spin_lock_bh held meaning we in_interrupt is true
+  to add & delete multicast entries to get around this we indirectly start
+  a kernel thread which executes the io commands necessary at a later
+  time.
+  We also need ip addresses for the setipm & delipm primatives
+  to do this we use arp_mc_map.
+  N.B. We even need to kmalloc GFP_ATOMIC here
+
+ */
+static void
+lcs_set_multicast_list (struct net_device *dev)
+{
+    lcs_drvr_globals *drvr_globals;
+    struct ip_mc_list *im4;
+    struct in_device *in4_dev;
+    int addr_in_list;
+
+#if 0				/* LCS doesn't do IPV6 yet. */
+    struct ifmcaddr6 *im6;
+    struct inet6_dev *in6_dev;
+#endif
+    char buf[MAX_ADDR_LEN];
+    lcs_ipm_list *curr_lmem, *new_lmem;
+    lcs_state oldstate;
+
+    if ((dev == NULL) || (dev->priv == NULL)) {
+	lcs_bad_news ("invalid dev=%p in lcs_set_multicast_list\n", dev);
+	return;
+    }
+    drvr_globals = (lcs_drvr_globals *) dev->priv;
+    oldstate = drvr_globals->state;
+#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
+    if ((in4_dev = in_dev_get (dev)) == NULL)
+	return;
+    read_lock (&in4_dev->lock);
+#else
+    if ((in4_dev = dev->ip_ptr) == NULL)
+	return;
+#endif
+    /* Pass 1 find the entries to be deleted */
+    /* This is done first to avoid overflowing the cards */
+    /* onboard multicast list. */
+    spin_lock (&drvr_globals->ipm_lock);
+    for (curr_lmem = drvr_globals->ipm_list; curr_lmem;
+	 curr_lmem = curr_lmem->next) {
+	addr_in_list = FALSE;
+	for (im4 = in4_dev->mc_list; im4; im4 = im4->next) {
+	    arp_mc_map (im4->multiaddr, buf, dev, 0);
+	    if ((memcmp (buf, &curr_lmem->ipm.mac_address,
+			 LCS_ADDR_LEN) == 0) &&
+		(curr_lmem->ipm.ip_addr == im4->multiaddr)) {
+		addr_in_list = TRUE;
+		break;
+	    }
+
+	}
+	if (!addr_in_list)
+	    curr_lmem->ipm_state = ipm_delete_required;
+    }
+    /* Pass 2 find the new entries to be added */
+    for (im4 = in4_dev->mc_list; im4; im4 = im4->next) {
+	arp_mc_map (im4->multiaddr, buf, dev, 0);
+	addr_in_list = FALSE;
+	for (curr_lmem = drvr_globals->ipm_list; curr_lmem;
+	     curr_lmem = curr_lmem->next) {
+	    if ((memcmp (buf, &curr_lmem->ipm.mac_address, LCS_ADDR_LEN) == 0)
+		&& (curr_lmem->ipm.ip_addr == im4->multiaddr)) {
+		addr_in_list = TRUE;
+		break;
+	    }
+	}
+	if (!addr_in_list) {
+	    new_lmem =
+		(lcs_ipm_list *) kmalloc (sizeof (lcs_ipm_list), GFP_ATOMIC);
+	    if (!new_lmem) {
+		lcs_good_news("lcs_set_multicast_list Failed to add "
+			      "multicast entry %x\n",
+			      (u32) im4->multiaddr);
+		goto done;
+	    }
+	    memset (new_lmem, 0, sizeof (lcs_ipm_list));
+	    memcpy (&new_lmem->ipm.mac_address, buf, LCS_ADDR_LEN);
+	    new_lmem->ipm.ip_addr = im4->multiaddr;
+	    new_lmem->ipm_state = ipm_set_required;
+	    add_to_list ((list **) & drvr_globals->ipm_list, (list *) new_lmem);
+	}
+    }
+#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
+    read_unlock (&in4_dev->lock);
+    in_dev_put (in4_dev);
+#endif
+#if 0				/* LCS doesn't do IPV6 yet */
+    if ((in6_dev = in6_dev_get (dev)) == NULL)
+	return;
+    read_lock_bh (&in6_dev->lock);
+    for (im6 = in6_dev->mc_list; im6; im6 = im6->next) {
+    }
+    read_unlock (&in4_dev->lock);
+    in6_dev_put (idev);
+#endif
+done:
+    spin_unlock (&drvr_globals->ipm_lock);
+    lcs_queue_thread (lcs_fix_multicast_list, drvr_globals);
+}
+#endif
+
+void
+lcs_set_slow_hw (lcs_drvr_globals * drvr_globals, u16 cu_type, u8 cu_model)
+{
+    drvr_globals->slow_hw =
+	((cu_type == 0x3088
+	  && (cu_model == 0x8 || cu_model == 0x1f)) ? TRUE : FALSE);
+}
+
+#if LCS_DEBUG && !LCS_PRINTK_DEBUG
+
+static void
+lcs_debug_register (void)
+{
+    if (lcs_id == NULL) {
+	lcs_id = debug_register ("lcs", 1, 16, 16 * sizeof (long));
+	debug_register_view (lcs_id, &debug_sprintf_view);
+	debug_set_level (lcs_id, 0);
+    }
+}
+#endif
+
+/*
+ * The main device detection routine called  on bootup or module
+ * installation to do. An unfortunately unwieldly function which dosen't
+ * lend itself easily to being broken into smaller functional blocks &
+ * my reason for hating tabs of 8. 
+ */
+
+#if LCS_CHANDEV
+int
+lcs_probe (chandev_probeinfo * probeinfo)
+{
+    lcs_drvr_globals *drvr_globals = NULL;
+    int rc = -ENODEV;
+    lcs_write_globals *write;
+    lcs_read_globals *read;
+    u8 mac_address[LCS_ADDR_LEN];
+    struct net_device *dev = NULL;
+    char *basename = NULL;
+    struct net_device *(*init_netdevfunc) (struct net_device * dev,
+					   int sizeof_priv) = NULL;
+    void (*unreg_netdevfunc) (struct net_device * dev) = NULL;
+
+    drvr_globals = lcs_alloc_drvr_globals ();
+    if (!drvr_globals) {
+	lcs_bad_news ("lcs_probe kmalloc failed\n");
+	return -ENOMEM;
+    }
+    read = drvr_globals->read;
+    write = drvr_globals->write;
+    read->subchannel = probeinfo->read.irq;
+    write->subchannel = probeinfo->write.irq;
+    read->devno = probeinfo->read.devno;
+    write->devno = probeinfo->write.devno;
+    lcs_set_slow_hw (drvr_globals, probeinfo->read.cu_type,
+		     probeinfo->read.cu_model);
+    if (lcs_allocirq (read) || lcs_allocirq (write)) {
+	rc = -ENODEV;
+	goto Fail;
+    }
+    checksum_received_ip_pkts = probeinfo->checksum_received_ip_pkts;
+    use_hw_stats = probeinfo->use_hw_stats;
+    rc = lcs_detect (drvr_globals, probeinfo->port_protocol_no,
+		     probeinfo->hint_port_no, probeinfo->max_port_no,
+		     lcs_autodetect_type, mac_address, 0);
+
+    if (rc) {
+	lcs_stopcard (drvr_globals);
+	goto Fail;
+    } else {
+	/*Fill in the fields of the device structure 
+	 * with interface specific values.
+	 */
+	switch (drvr_globals->lan_type) {
+#ifdef CONFIG_NET_ETHERNET
+	case lcs_enet_type:
+	    init_netdevfunc = init_etherdev;
+	    unreg_netdevfunc = unregister_netdev;
+	    basename = "eth";
+	    drvr_globals->lan_type_trans = eth_type_trans;
+	    break;
+#endif
+#ifdef CONFIG_TR
+	case lcs_token_ring_type:
+	    init_netdevfunc = init_trdev;
+	    unreg_netdevfunc = unregister_netdev;
+	    basename = "tr";
+	    drvr_globals->lan_type_trans = tr_type_trans;
+	    break;
+#endif
+#ifdef CONFIG_FDDI
+	case lcs_fddi_type:
+	    init_netdevfunc = init_fddidev;
+	    unreg_netdevfunc = unregister_netdev;
+	    basename = "fddi";
+	    drvr_globals->lan_type_trans = fddi_type_trans;
+	    break;
+#endif
+	}
+	if ((dev =
+	     chandev_initnetdevice (probeinfo, drvr_globals->rel_adapter_no,
+				    NULL, 0, basename, init_netdevfunc,
+				    unreg_netdevfunc)) == 0)
+	    goto Fail;
+	memcpy (dev->dev_addr, mac_address, LCS_ADDR_LEN);
+	dev->open = lcs_open;
+	dev->stop = lcs_close;
+	dev->hard_start_xmit = lcs_txpacket;
+	dev->get_stats = lcs_getstats;
+
+	drvr_globals->dev = dev;
+
+	dev->priv = drvr_globals;
+#ifdef CONFIG_IP_MULTICAST
+	dev->set_multicast_list = (lcs_check_multicast_supported (drvr_globals)
+				   ? lcs_set_multicast_list : NULL);
+#endif
+	lcs_stopcard (drvr_globals);
+	netif_stop_queue (dev);
+	lcs_displayinfo (dev);
+	lcs_debug_exception (0, "device structure=%p drvr globals=%p\n"
+			     "read=%p write=%p\n"
+			     "read_ccws=%p  write_cwws=%p\n", dev, drvr_globals,
+			     drvr_globals->read, drvr_globals->write,
+			     &drvr_globals->read->ccw[0],
+			     &drvr_globals->write->ccw[0]);
+	return 0;
+    }
+Fail:
+    if (drvr_globals) {
+	lcs_freeallirqs (drvr_globals);
+	lcs_usage_free_drvr_globals (drvr_globals);
+    }
+    lcs_debug_event (2, "osa_probe error %d\n", rc);
+    return rc;
+}
+#else
+int __init
+lcs_probe (void)
+{
+    int read_irq, write_irq;
+    s390_dev_info_t read_devinfo, write_devinfo;
+    lcs_drvr_globals *drvr_globals = NULL;
+    int hint, detect_error;
+    int rc = -ENODEV;
+    int max_rel_adapter_no;
+    int err;
+    int forced_rel_adapter_no;
+    int lcs_loopcnt = 0;
+    u8 mac_address[LCS_ADDR_LEN];
+    struct net_device *dev = NULL;
+    lcs_frame_type lan_type = lcs_autodetect_type;
+    lcs_write_globals *write;
+    lcs_read_globals *read;
+
+    lcs_debug_event (1, "Starting lcs_probe dev=%p\n", dev);
+
+    for (read_irq = get_irq_first (); read_irq >= 0;
+	 read_irq = get_irq_next (read_irq)) {
+	/* check read channel
+	 * we had to do the cu_model check also because ctc devices
+	 * have the same cutype & after asking some people
+	 * the model numbers are given out pseudo randomly so
+	 * we can't just take a range of them also the dev_type & models are 0
+	 */
+	lcs_loopcnt++;
+	if (lcs_loopcnt > 0x10000) {
+	    lcs_bad_news
+		("lcs probe detected infinite loop bug in get_irq_next\n");
+	    goto Fail;
+	}
+	if ((err = get_dev_info_by_irq (read_irq, &read_devinfo))) {
+	    lcs_bad_news
+		("lcs_probe get_dev_info_by_irq reported err=%X on irq %d\n"
+		 "should not happen\n", err, read_irq);
+	    continue;
+	}
+	if (read_devinfo.status & DEVSTAT_DEVICE_OWNED)
+	    continue;
+	if (read_devinfo.devno & 1)
+	    continue;
+	if ((max_rel_adapter_no =
+	     lcs_model_supported (&read_devinfo, &forced_rel_adapter_no)) < 0)
+	    continue;
+	if (get_dev_info_by_devno (read_devinfo.devno + 1, &write_devinfo))
+	    continue;
+	if (write_devinfo.status & DEVSTAT_DEVICE_OWNED)
+	    continue;
+	write_irq = write_devinfo.irq;
+	if ((ignore_sense == FALSE && forced_rel_adapter_no != -1) &&
+	    ((write_devinfo.sid_data.cu_type != read_devinfo.sid_data.cu_type)
+	     || (write_devinfo.sid_data.cu_model !=
+		 read_devinfo.sid_data.cu_model)))
+	    continue;
+	if (drvr_globals == NULL) {
+	    drvr_globals = lcs_alloc_drvr_globals ();
+	    if (!drvr_globals) {
+		lcs_bad_news ("lcs_probe kmalloc failed\n");
+		return -ENOMEM;
+	    }
+	}
+	read = drvr_globals->read;
+	write = drvr_globals->write;
+	read->subchannel = read_irq;
+	write->subchannel = write_irq;
+	read->devno = read_devinfo.devno;
+	write->devno = write_devinfo.devno;
+	lcs_set_slow_hw (drvr_globals, read_devinfo.sid_data.cu_type,
+			 read_devinfo.sid_data.cu_model);
+	if (lcs_allocirq (read) || lcs_allocirq (write)) {
+	    lcs_freeallirqs (drvr_globals);
+	    /* We may find another card further up the chain */
+	} else {
+	    if (forced_rel_adapter_no == -1) {
+		hint = (read_devinfo.devno & 0xFF) >> 1;
+		if (hint > max_rel_adapter_no) {
+		    /* The card is possibly emulated e.g P/390 */
+		    /* or possibly configured to use a shared */
+		    /* port configured by osa-sf. */
+		    hint = 0;
+		}
+	    } else {
+		hint = -1;
+	    }
+	    detect_error =
+		lcs_detect (drvr_globals, forced_rel_adapter_no, hint,
+			    max_rel_adapter_no, lan_type, mac_address, 0);
+	    if (detect_error) {
+		lcs_stopcard (drvr_globals);
+		lcs_freeallirqs (drvr_globals);
+	    } else {
+
+		/*Fill in the fields of the device structure 
+		 * with interface specific values.
+		 */
+		switch (drvr_globals->lan_type) {
+#ifdef CONFIG_NET_ETHERNET
+		case lcs_enet_type:
+		    dev = init_etherdev (dev, 0);
+		    drvr_globals->lan_type_trans = eth_type_trans;
+		    break;
+#endif
+#ifdef CONFIG_TR
+		case lcs_token_ring_type:
+		    dev = init_trdev (dev, 0);
+		    drvr_globals->lan_type_trans = tr_type_trans;
+		    break;
+#endif
+#ifdef CONFIG_FDDI
+		case lcs_fddi_type:
+/* I'm not sure whether this function is in the generic kernel anymore */
+		    dev = init_fddidev (dev, 0);
+		    drvr_globals->lan_type_trans = fddi_type_trans;
+		    break;
+#endif
+		}
+		if (dev)
+		    memcpy (dev->dev_addr, mac_address, LCS_ADDR_LEN);
+		else {
+		    rc = -ENOMEM;
+		    goto Fail;
+		}
+		dev->open = lcs_open;
+		dev->stop = lcs_close;
+		dev->hard_start_xmit = lcs_txpacket;
+		dev->get_stats = lcs_getstats;
+		drvr_globals->dev = dev;
+		dev->priv = drvr_globals;
+#ifdef CONFIG_IP_MULTICAST
+		dev->set_multicast_list =
+		    (lcs_check_multicast_supported (drvr_globals)
+		     ? lcs_set_multicast_list : NULL);
+#endif
+		lcs_stopcard (drvr_globals);
+		netif_stop_queue (dev);
+		lcs_displayinfo (dev);
+		lcs_debug_event (0, "device structure=%p drvr globals=%p\n"
+				 "read=%p write=%p\n"
+				 "read_ccws=%p  write_cwws=%p\n", dev,
+				 drvr_globals, drvr_globals->read,
+				 drvr_globals->write,
+				 &drvr_globals->read->ccw[0],
+				 &drvr_globals->write->ccw[0]);
+
+		return 0;
+	    }
+	}
+    }
+Fail:
+    if (drvr_globals) {
+
+	lcs_freeallirqs (drvr_globals);
+	lcs_usage_free_drvr_globals (drvr_globals);
+
+    }
+    lcs_debug_event (2, "osa_probe error %d\n", rc);
+    return rc;
+}
+#endif
+
+#if LCS_CHANDEV || MODULE
+static int
+lcs_shutdown_card (struct net_device *dev)
+{
+    lcs_drvr_globals *drvr_globals = ((lcs_drvr_globals *) dev->priv);
+
+#if !LCS_CHANDEV
+    lcs_unregister (dev, drvr_globals->lan_type);	
+    /* unregister_netdev calls close */
+#endif
+    lcs_freeallirqs (drvr_globals);
+    lcs_usage_free_drvr_globals (drvr_globals);
+    kfree (dev);
+    return (0);
+}
+#endif
+
+#if LCS_CHANDEV
+static void
+lcs_msck_notification_func (struct net_device *dev, int msck_irq,
+			    chandev_msck_status prevstatus,
+			    chandev_msck_status newstatus)
+{
+    lcs_drvr_globals *drvr_globals = (lcs_drvr_globals *) dev->priv;
+
+    if (drvr_globals) {
+	lcs_debug_event (0,"lcs_msck_notifcation_func drvr_globals=%p "
+			 "msck_irq=%d prevstatus=%d newstatus=%d",
+			 drvr_globals, msck_irq, prevstatus, newstatus);
+	if (newstatus == chandev_status_good
+	    || newstatus == chandev_status_all_chans_good) {
+	    switch (prevstatus) {
+	    case chandev_status_no_path:
+	    case chandev_status_gone:
+	    case chandev_status_not_oper:
+		if (msck_irq == drvr_globals->read->subchannel)
+		    lcs_restartreadio (drvr_globals);
+		else
+		    lcs_restartwriteio (drvr_globals);
+		break;
+	    default:
+	    }
+	} else if (msck_irq == drvr_globals->write->subchannel
+		   && newstatus != chandev_status_revalidate)
+	    drvr_globals->write->chan_busy_state = chan_dead;
+
+    } else
+	lcs_debug_exception (0, "lcs_msck_notification_func drvr_globals=NULL");
+}
+#endif
+
+#if MODULE
+static void
+lcs_cleanup (void)
+{
+#if LCS_CHANDEV
+    chandev_unregister (lcs_probe, TRUE);
+#else
+    while (lcs_card_list)
+	lcs_shutdown_card (lcs_card_list->dev);
+#endif
+}
+#endif
+
+#if MODULE
+int
+init_module (void)
+#else
+#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
+static
+#endif
+    int __init
+lcs_init (void)
+#endif
+{
+    int cardsfound = 0;
+
+    lcs_good_news ("Starting lcs\n");
+#if LCS_DEBUG && !LCS_PRINTK_DEBUG
+    lcs_debug_register ();
+#endif
+
+#if LCS_CHANDEV
+    cardsfound =
+	chandev_register_and_probe (lcs_probe,
+				    (chandev_shutdownfunc) lcs_shutdown_card,
+				    (chandev_msck_notification_func)
+				    lcs_msck_notification_func,
+				    chandev_type_lcs);
+#else
+    while ((lcs_probe () == 0)) {
+	cardsfound++;
+    }
+#endif
+    if (!cardsfound) {
+	lcs_bad_news ("No lcs capable cards found\n");
+#if MODULE
+	cleanup_module ();
+#endif
+    }
+#if !LCS_CHANDEV
+    else {
+	lcs_display_conf ();
+    }
+#endif
+    return (cardsfound ? 0 : -ENODEV);
+}
+
+/*
+ * Code for module loading & unloading
+ */
+
+#ifdef MODULE
+void
+cleanup_module (void)
+{
+    lcs_good_news ("Terminating lcs module.\n");
+    lcs_cleanup ();
+#if LCS_DEBUG && !LCS_PRINTK_DEBUG
+    if (lcs_id) {
+	debug_unregister_view (lcs_id, &debug_sprintf_view);
+	debug_unregister (lcs_id);
+	lcs_id = NULL;
+    }
+#endif
+}
+#else
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,2,18)
+__initcall (lcs_init);
+#endif
+#endif
diff -urN linux-2.2.21-pre3/fs/fat/inode.c linux-2.2.21-pre4/fs/fat/inode.c
--- linux-2.2.21-pre3/fs/fat/inode.c	Fri Mar  8 17:42:07 2002
+++ linux-2.2.21-pre4/fs/fat/inode.c	Fri Mar  8 17:42:09 2002
@@ -849,6 +849,7 @@
 	loff_t i_pos;
 
 retry:
+	i_pos = MSDOS_I(inode)->i_location;
 	if (inode->i_ino == MSDOS_ROOT_INO || !i_pos) return;
 	if (!(bh = fat_bread(sb, ((unsigned long long) i_pos) >> MSDOS_DPB_BITS))) {
 		printk("dev = %s, ino = %ld\n", kdevname(inode->i_dev), i_pos);
diff -urN linux-2.2.21-pre3/fs/file.c linux-2.2.21-pre4/fs/file.c
--- linux-2.2.21-pre3/fs/file.c	Sun Mar 25 08:31:02 2001
+++ linux-2.2.21-pre4/fs/file.c	Fri Mar  8 17:42:09 2002
@@ -1,5 +1,5 @@
 /*
- *  linux/fs/open.c
+ *  linux/fs/file.c
  *
  *  Copyright (C) 1998, Stephen Tweedie and Bill Hawes
  *
diff -urN linux-2.2.21-pre3/include/asm-i386/processor.h linux-2.2.21-pre4/include/asm-i386/processor.h
--- linux-2.2.21-pre3/include/asm-i386/processor.h	Fri Mar  8 17:42:07 2002
+++ linux-2.2.21-pre4/include/asm-i386/processor.h	Fri Mar  8 17:42:09 2002
@@ -73,7 +73,7 @@
 #define X86_FEATURE_PGE		0x00002000	/* Page Global Enable */
 #define X86_FEATURE_MCA		0x00004000	/* Machine Check Architecture */
 #define X86_FEATURE_CMOV	0x00008000	/* CMOV instruction (FCMOVCC and FCOMI too if FPU present) */
-#define X86_FEATURE_PAT	0x00010000	/* Page Attribute Table */
+#define X86_FEATURE_PAT		0x00010000	/* Page Attribute Table */
 #define X86_FEATURE_PSE36	0x00020000	/* 36-bit PSEs */
 #define X86_FEATURE_PN		0x00040000	/* Processor serial number */
 #define X86_FEATURE_19		0x00080000
@@ -137,51 +137,52 @@
 		  "=b" (*ebx),
 		  "=c" (*ecx),
 		  "=d" (*edx)
-		: "a" (op)
-		: "cc");
+		: "0" (op));
 }
 
 /*
  *  * CPUID functions returning a single datum
  *   */
-extern inline unsigned int cpuid_eax(unsigned int op)
+static inline unsigned int cpuid_eax(unsigned int op)
 {
-	unsigned int eax, ebx, ecx, edx;
+	unsigned int eax;
 
 	__asm__("cpuid"
-		: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
-		: "a" (op));
+	: "=a" (eax)
+	: "0" (op)
+	: "bx", "cx", "dx");
 	return eax;
-}
-
-extern inline unsigned int cpuid_ebx(unsigned int op)
-{
-	unsigned int eax, ebx, ecx, edx;
+}   
+static inline unsigned int cpuid_ebx(unsigned int op)
+{       
+	unsigned int eax, ebx;
 
 	__asm__("cpuid"
-		: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
-		: "a" (op));
+	: "=a" (eax), "=b" (ebx)
+	: "0" (op)
+	: "cx", "dx" );
 	return ebx;
-}
-extern inline unsigned int cpuid_ecx(unsigned int op)
-{
-	unsigned int eax, ebx, ecx, edx;
+}   
+static inline unsigned int cpuid_ecx(unsigned int op)
+{ 
+	unsigned int eax, ecx;
 
 	__asm__("cpuid"
-		: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
-		: "a" (op));
+	: "=a" (eax), "=c" (ecx)
+	: "0" (op)
+	: "bx", "dx" );
 	return ecx;
 }
-extern inline unsigned int cpuid_edx(unsigned int op)
+static inline unsigned int cpuid_edx(unsigned int op)
 {
-	unsigned int eax, ebx, ecx, edx;
+	unsigned int eax, edx;
 
 	__asm__("cpuid"
-		: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
-		: "a" (op));
+	: "=a" (eax), "=d" (edx)
+	: "0" (op)
+	: "bx", "cx");
 	return edx;
 }
-
 /*
  *      Cyrix CPU configuration register indexes
  */
diff -urN linux-2.2.21-pre3/mm/vmscan.c linux-2.2.21-pre4/mm/vmscan.c
--- linux-2.2.21-pre3/mm/vmscan.c	Sun Mar 25 08:37:41 2001
+++ linux-2.2.21-pre4/mm/vmscan.c	Fri Mar  8 17:42:09 2002
@@ -399,6 +399,12 @@
 
 	priority = 5;
 	do {
+		/* Always pick on the dcache even if we didnt need to,
+		   without this some workloads cause excessive dcache
+	   	   growth */
+
+		shrink_dcache_memory(priority, gfp_mask);
+	
 		while (shrink_mmap(priority, gfp_mask)) {
 			if (!--count)
 				goto done;
@@ -418,7 +424,6 @@
 				goto done;
 		}
 
-		shrink_dcache_memory(priority, gfp_mask);
 	} while (--priority > 0);
 done:
 	unlock_kernel();
