patch-2.4.22 linux-2.4.22/drivers/s390/net/c7000.c

Next file: linux-2.4.22/drivers/s390/net/ctcmain.c
Previous file: linux-2.4.22/drivers/s390/net/Makefile
Back to the patch index
Back to the overall index

diff -urN linux-2.4.21/drivers/s390/net/c7000.c linux-2.4.22/drivers/s390/net/c7000.c
@@ -0,0 +1,3293 @@
+/*
+	Cisco/7000 driver -- Copyright (C) 2000 UTS Global LLC.
+	Author: Bob Scardapane (UTS Global LLC).
+	Version: 3.
+
+	To use this driver, run the LINUX command:
+
+	insmod c7000 base0=0xYYYY lhost0=s1 uhost0=s2 lappl0=s3 uappl0=s4 dbg=x
+
+	base0=0xYYYY defines the base unit address of the interface.
+	lhost0=s1 defines the local host name.
+	uhost0=s2 defines the unit host name.
+	lappl0=s3 defines the local application name.
+	uappl0=s4 defines the unit application name.
+	dbg=x defines the message level.  Higher values will result in
+	additional diagnostic messages.
+
+	Additional interfaces are defined on insmod by using the variable
+	name groups "base1,lhost1,lappl1,uhost1,uappl1", etc... up to three
+	additional groups.
+
+	In addition, the module will automatically detect the unit base
+	addresses by scanning all active irq's for a control unit type
+	of 0x3088 and a model of 0x61 (CLAW mode). The noauto parameter
+	can be used to suppress automatic detection.
+
+	The values of lhostx, lapplx, uhostx and uapplx default to:
+
+	lapplx - TCPIP
+	lhostx - UTS
+	uapplx - TCPIP
+	uhostx - C7011
+
+	Note that the values passed in the insmod command will always
+	override the automatic detection of the unit base addreeses and
+	the default values of lapplx, lhostx, uapplx and uhostx.
+
+	The parameter noauto can be used to disable automatic detection of
+	devices:
+
+	noauto=1 (disable automatic detection)
+	noauto=0 (Enable automatic detectio.  This is the default value.)
+
+	The values in base0 - base3 will be copied to the bases array when
+	the module is loaded. 
+
+	To configure the interface(s), run the LINUX command(s):
+
+	ifconfig ci0 ...
+	ifconfig ci1 ...
+	ifconfig ci2 ...
+	ifconfig ci3 ...
+
+	There is one device structure for each controller in the c7000_devices
+	array.  The base address of each controller is in the bases array.
+	These arrays parallel each other.  There is also one c7000_controller
+	structure for each controller.  This structure is pointed to by field
+	priv in the individual device structure. 
+
+	In each c7000_controller, there are embedded c7000_unit structures.
+	There is one c7000_unit structure per device number that makes up
+	a controller (currently 2 units per controller).
+*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/string.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+#include <asm/irq.h>
+ 
+/*
+	Global defines
+*/
+
+/*
+	Maximum number of controllers.
+*/
+
+#define	MAX_C7000	4
+
+/*
+	Number of units per controller.
+*/
+
+#define	NUNITS		2
+
+/*
+	Define indexes of read and write units in the cunits array.
+*/
+
+#define	C7000_RD	0
+#define	C7000_WR	1
+
+/*
+	Number of device buffers.
+*/
+
+#define	C7000_MAXBUF	40
+
+/*
+	Transmission queue length.
+*/
+
+#define	C7000_TXQUEUE_LEN	100
+/*
+	Size of the IP packet data.
+*/
+
+#define	C7000_DATAL	4096
+
+/*
+	Size of the read header data.
+*/
+
+#define	C7000_READHDRL	4
+
+/*
+	Size of read flag byte.
+*/
+
+#define	C7000_READFFL	1
+
+/*
+	Size of a device buffer.  This is how it is arranged in memory:
+	4096 (IP packet data) + 4 (read header) + 1 (read flag) = 4101.
+*/
+
+#define	C7000_BUFSIZE	C7000_DATAL + C7000_READHDRL + C7000_READFFL
+
+/*
+	Size of sigsmod data.
+*/
+
+#define	C7000_SIGSMODL	1
+
+/*
+	Flag value that indicates a read was completed in the flag
+	field of a c7000_rd_header.
+*/
+
+#define	FLAG_FF		0xff
+/*
+	Size of C7000 sense id data.
+*/
+
+#define SIDL		32
+
+/*
+	Maximum number of read and write retries.
+*/
+
+#define	C7000_MAX_RETRIES	3
+
+/*
+	Define sense byte0 value for a box reset.
+*/
+
+#define	C7000_BOX_RESET		0x41
+
+/*
+	CCW commands.
+*/
+
+#define	C7000_WRITE_CCW		0x01	/* normal write */
+#define	C7000_READ_CCW		0x02	/* normal read */
+#define	C7000_NOOP_CCW		0x03	/* no operation */
+#define	C7000_SIGSMOD_CCW	0x05	/* signal status modifier */
+#define	C7000_TIC_CCW		0x08	/* transfer in channel */
+#define	C7000_READHDR_CCW	0x12	/* read header  */
+#define	C7000_READFF_CCW	0x22	/* read FF flag */
+#define	C7000_SID_CCW		0xe4	/* sense identification */
+
+/*
+	Control commands.
+*/
+
+#define	C7000_SYS_VALIDATE		1
+#define C7000_SYS_VALIDATE_RESP		2
+#define	C7000_CONN_REQ			33
+#define C7000_CONN_RESP			34
+#define C7000_CONN_CONFRM		35
+#define C7000_DISCONN			36
+#define C7000_BOXERROR			65
+
+/*
+	State machine values.
+*/
+
+#define	C7000_INIT	1
+#define C7000_HALT	2
+#define C7000_SID	3
+#define C7000_SYSVAL	4
+#define	C7000_CONNECT	5
+#define C7000_READY	6
+#define C7000_READ	7
+#define C7000_WRITE	8
+#define	C7000_DISC	9
+#define C7000_STOP	10
+#define	C7000_STOPPED	11
+#define C7000_ERROR	12
+
+/*
+	The lower subchannel is used for read operations and the one that is
+	one higher is used for write operations.
+
+	Both subchannels are initially in state C7000_INIT.  A transition to
+	state C7000_HALT occurs when halt_IO is issued on each.  When the
+	halts completes a transition to state C7000_SID occurs and a channel
+	program is issued to do a sense identification on both subchannels.
+
+	When the sense identification completes, the state C7000_SYSVAL is
+	entered on the read subchannel.  A read channel program is issued.
+
+	When the sense identification completes, the write subchannel enters
+	state C7000_SYSVAL and a system validation request is written.  The
+	read subchannel is also put into this state.
+	
+	When both the system validation response is read and an inbound system
+	validation request is read, the inbound system validation request is
+	responded to and both subchannels enter the C7000_CONNECT state.
+
+	A read channel program is posted to look for the inbound connect
+	request.  When that is received a connection confirmation is written.
+	The state of both subchannels is then changed to C7000_READY.  A
+	read channel program is then posted and the state is changed to
+	C7000_READ.  When a read completes, the packet is sent to the higher
+	layers and the read channel program is restarted.
+
+	When there is a packet to be written, state C7000_WRITE is entered
+	and a channel program is issued to write the data.  The subchannel
+	is in state C7000_READY when there is nothing to be written.
+
+	When the stop method is executed, a disconnect message is sent and
+	the state is changed to C7000_DISC in both subchannels.  A halt_IO
+	will be issued to both subchannels and state C7000_STOP will be entered.
+	When the halt IO completes, state C7000_STOPPED will be set. 
+
+	State C7000_ERROR is set when an error occurs in the interrupt
+	routine.  Recycle the interface (ifconfig down / ifconfig up)
+	to reset this state.
+*/
+
+/*
+	Results from c7000_check_csw.
+*/
+
+enum	c7000_rupt {
+	C7000_NORMAL,
+	C7000_CHANERR,
+	C7000_UCK,
+	C7000_UCK_RESET,
+	C7000_UE,
+	C7000_ATTN,
+	C7000_BUSY,
+	C7000_OTHER
+};
+
+/*
+	Bits set in device structure tbusy field.
+*/
+
+#define	TB_TX		0	/* sk buffer handling in progress */
+#define	TB_STOP		1	/* network device stop in progress */
+#define	TB_RETRY	2	/* retry in progress */
+#define	TB_NOBUFFER	3	/* no buffer on free queue */
+
+/*
+	Bit in c7000_unit.flag_a that indicates the bh routine is busy.
+*/
+
+#define	C7000_BH_ACTIVE	0
+
+#define CPrintk(level, args...) \
+	if (level <= dbg) \
+		printk(args)
+
+/*
+	Maximum length of a system validation string.
+*/
+
+#define	NAMLEN	8
+
+#define	Err_Conn_Confirm	1
+#define	Err_Names_not_Matched	166
+#define	Err_C7000_NOT_READY	167
+#define	Err_Duplicate		170
+#define	Err_Closing		171
+#define	Err_No_Such_App		172
+#define	Err_Host_Not_Ready	173
+#define	Err_CLOSING		174
+#define	Err_Dup_Link		175
+#define	Err_Wrong_Version	179
+#define	Err_Wrong_Frame_Size	180
+
+/*
+	Define a macro to extract the logical link identifier from
+	the c7000 read header command field.
+*/
+
+#define	C7000_LINKID(cmd)	((unsigned char)cmd >> 3)
+
+/*
+	Define the control unit type for a Cisco 7000.
+*/
+
+#define	C7000_CU_TYPE		0x3088
+
+/*
+	Define the control unit model for a Cisco 7000.
+*/
+
+#define	C7000_CU_MODEL		0x61
+
+/*
+	Define the default system validate parameters (lapplx,
+	lhostx, uapplx, uhostx).
+*/
+
+#define	C7000_DFLT_LAPPL	"TCPIP"
+#define	C7000_DFLT_LHOST	"UTS"
+#define	C7000_DFLT_UAPPL	"TCPIP"
+#define	C7000_DFLT_UHOST	"C7011"
+
+/*
+	Global variables.
+*/
+
+/*
+	Base device addresses of the controllers.
+*/
+
+static int	base0 = -1;
+static int	base1 = -1;
+static int	base2 = -1;
+static int	base3 = -1;
+
+static int	bases[MAX_C7000];
+
+/*
+	Local application names.
+*/
+
+static char	*lappl0;
+static char	*lappl1;
+static char	*lappl2;
+static char	*lappl3;
+
+/*
+	Local host names.
+*/
+
+static char	*lhost0;
+static char	*lhost1;
+static char	*lhost2;
+static char	*lhost3;
+
+/*
+	Unit application names.
+*/
+
+static char	*uappl0;
+static char	*uappl1;
+static char	*uappl2;
+static char	*uappl3;
+
+/*
+	Unit hosts names.
+*/
+
+static char	*uhost0;
+static char	*uhost1;
+static char	*uhost2;
+static char	*uhost3;
+
+/*
+	Debugging level (higher numbers emit lower priority messages).
+*/
+
+static unsigned int	dbg = 0;
+
+/*
+	Parameter that controls auto detection.
+*/
+
+static int	noauto = 0;
+
+/*
+	Interface names.
+*/
+
+static char	ifnames[MAX_C7000][8] = {"ci0", "ci1", "ci2", "ci3"};
+
+/*
+	One device structure per controller.
+*/
+
+/* RBH Try out the new code for 2.4.0 */
+#define NEWSTUFF
+
+#ifdef NEWSTUFF
+#define STRUCT_NET_DEVICE struct net_device
+#else
+#define STRUCT_NET_DEVICE struct device
+#endif
+
+STRUCT_NET_DEVICE	c7000_devices[MAX_C7000];
+
+/*
+	Scratch variable filled in with controller name.
+*/
+
+static char	*controller;
+
+/*
+	Identify parameters that can be passed on the LINUX insmod command.
+*/
+
+MODULE_AUTHOR("Robert Scardapane (UTS Global)");
+MODULE_DESCRIPTION("Network module for Cisco 7000 box.");
+
+MODULE_PARM(base0, "1i");
+MODULE_PARM_DESC(base0, "Base unit address for 1st C7000 box.");
+MODULE_PARM(base1, "1i");
+MODULE_PARM_DESC(base1, "Base unit address for 2nd C7000 box.");
+MODULE_PARM(base2, "1i");
+MODULE_PARM_DESC(base2, "Base unit address for 3rd C7000 box.");
+MODULE_PARM(base3, "1i");
+MODULE_PARM_DESC(base3, "Base unit address for 4th C7000 box.");
+
+MODULE_PARM(lappl0, "s");
+MODULE_PARM_DESC(lappl0, "Application name for 1st C7000 box.");
+MODULE_PARM(lappl1, "s");
+MODULE_PARM_DESC(lappl1, "Application name for 2nd C7000 box.");
+MODULE_PARM(lappl2, "s");
+MODULE_PARM_DESC(lappl2, "Application name for 3rd C7000 box.");
+MODULE_PARM(lappl3, "s");
+MODULE_PARM_DESC(lappl3, "Application name for 4th C7000 box.");
+
+MODULE_PARM(lhost0, "s");
+MODULE_PARM_DESC(lhost0, "Host name for 1st C7000 box.");
+MODULE_PARM(lhost1, "s");
+MODULE_PARM_DESC(lhost1, "Host name for 2nd C7000 box.");
+MODULE_PARM(lhost2, "s");
+MODULE_PARM_DESC(lhost2, "Host name for 3rd C7000 box.");
+MODULE_PARM(lhost3, "s");
+MODULE_PARM_DESC(lhost3, "Host name for 4th C7000 box.");
+
+MODULE_PARM(uhost0, "s");
+MODULE_PARM_DESC(uhost0, "Unit name for 1st C7000 box.");
+MODULE_PARM(uhost1, "s");
+MODULE_PARM_DESC(uhost1, "Unit name for 2nd C7000 box.");
+MODULE_PARM(uhost2, "s");
+MODULE_PARM_DESC(uhost2, "Unit name for 3rd C7000 box.");
+MODULE_PARM(uhost3, "s");
+MODULE_PARM_DESC(uhost3, "Unit name for 4th C7000 box.");
+
+MODULE_PARM(uappl0, "s");
+MODULE_PARM_DESC(uappl0, "Unit application name for 1st C7000 box.");
+MODULE_PARM(uappl1, "s");
+MODULE_PARM_DESC(uappl1, "Unit application name for 2nd C7000 box.");
+MODULE_PARM(uappl2, "s");
+MODULE_PARM_DESC(uappl2, "Unit application name for 3rd C7000 box.");
+MODULE_PARM(uappl3, "s");
+MODULE_PARM_DESC(uappl3, "Unit application name for 4th C7000 box.");
+
+MODULE_PARM(dbg, "1i");
+MODULE_PARM_DESC(dbg, "Message level for debugging.");
+
+MODULE_PARM(noauto, "1i");
+MODULE_PARM_DESC(noauto, "Control automatic detection of unit base addresses.");
+
+/*
+	Structure used to manage unit buffers.
+*/
+
+struct	c7000_buffer {
+	ccw1_t			ccws[7];	/* channel program */
+	struct	c7000_buffer	*next;		/* list pointer */
+	char			*data;		/* pointer to actual data */
+	int			len;		/* length of the data */
+};
+
+/*
+	C7000 Control Block.
+ */
+
+struct	c7000_control_blk {
+	unsigned char	cmd;
+	unsigned char	ver;
+	unsigned char	link_id;
+	unsigned char	correlator;
+	unsigned char	ret_code;
+	unsigned char	resvd1[3];
+	unsigned char	unitname[NAMLEN];
+	unsigned char	hostname[NAMLEN];
+	unsigned short	rdsize;		/* read frame size   */
+	unsigned short	wrtsize;	/* write frame size  */
+	unsigned char	resvd2[4];
+};
+
+/*
+	Per unit structure contained within the c7000_controller structure.
+*/
+
+struct	c7000_unit {
+	ccw1_t				ccws[5];	/* control ccws */
+	int				devno;		/* device number */
+	int				irq;		/* subchannel number */
+	int				IO_active;	/* IO activity flag */
+	int				state;		/* fsm state */
+	int				retries;	/* retry counter */
+	unsigned long			flag_a;		/* bh activity flag */
+	devstat_t			devstat;	/* device status */
+#ifdef NEWSTUFF
+	wait_queue_head_t		wait;		/* sleep q head */
+#else
+	struct wait_queue		*wait;		/* sleep q pointer */
+#endif
+	struct c7000_controller		*cntlp;		/* controller pointer */
+	struct c7000_buffer		*free;		/* free buffer anchor */
+	struct c7000_buffer		*proc_head;	/* proc head */
+	struct c7000_buffer		*proc_tail;	/* proc tail */
+	struct c7000_buffer		*bh_head;	/* bh head */
+	struct c7000_buffer		*bh_tail;	/* bh tail */
+	struct tq_struct		tq;		/* bh scheduling */
+	char				senseid[SIDL];	/* sense id data */
+	struct c7000_control_blk	control_blk;	/* control block */
+	unsigned char			sigsmod;	/* sigsmod flag */
+	unsigned char			readhdr[4];	/* read header */
+	unsigned char			readff;		/* readff flag */
+};
+
+/*
+	Private structure pointed to by dev->priv.
+*/
+
+struct c7000_controller {
+	struct	net_device_stats	stats;		/* statistics */
+	STRUCT_NET_DEVICE		*dev;		/* -> device struct */
+	unsigned int			base_addr;	/* base address */
+	char				lappl[NAMLEN];	/* local appl */
+	char				lhost[NAMLEN];	/* local host */
+	char				uappl[NAMLEN];	/* unit appl */
+	char				uhost[NAMLEN];	/* unit host */
+	unsigned char			version;	/* version = 2 */
+	unsigned char			linkid;		/* link id */
+	struct	c7000_unit		cunits[NUNITS];	/* embedded units */
+#ifdef NEWSTUFF
+	int				tbusy;
+#endif
+};
+
+/*
+	This is the structure returned by the C7000_READHDR_CCW.
+*/
+
+struct	c7000_rd_header {
+	unsigned short	len;	/* packet length */
+	unsigned char	cmd;	/* command code */
+	unsigned char	flag;	/* flag */
+};
+
+/*
+	Set the device structure transmission busy flag.
+*/
+
+#ifdef NEWSTUFF
+#define c7000_set_busy(dev) netif_stop_queue(dev)
+#else
+static __inline__ void
+c7000_set_busy(STRUCT_NET_DEVICE *dev)
+{
+	dev->tbusy = 1;
+	eieio();
+	return;
+}
+#endif
+	
+/*
+	Clear the device structure transmission busy flag.
+*/
+
+#ifdef NEWSTUFF
+#define c7000_clear_busy(dev) netif_wake_queue(dev)
+#else
+static __inline__ void
+c7000_clear_busy(STRUCT_NET_DEVICE *dev)
+{
+	dev->tbusy = 0;
+	eieio();
+	return;
+}
+#endif
+
+/*
+	Extract the device structure transmission busy flag.
+*/
+
+#ifdef NEWSTUFF
+#define c7000_check_busy(dev) netif_queue_stopped(dev)
+#else
+static __inline__ int
+c7000_check_busy(STRUCT_NET_DEVICE *dev)
+{
+	eieio();
+	return(dev->tbusy);
+}
+#endif
+
+/*
+	Set a bit in the device structure transmission busy flag.
+*/
+
+static __inline__ void
+c7000_setbit_busy(int nr, STRUCT_NET_DEVICE *dev)
+{
+#ifdef NEWSTUFF
+	netif_stop_queue(dev);
+	test_and_set_bit(nr, &((struct c7000_controller *)dev->priv)->tbusy);
+#else
+	set_bit(nr, (void *)&dev->tbusy);
+#endif
+	return;
+}
+
+/*
+	Clear a bit in the device structure transmission busy flag.
+*/
+
+static __inline__ void
+c7000_clearbit_busy(int nr, STRUCT_NET_DEVICE *dev)
+{
+#ifdef NEWSTUFF
+	clear_bit(nr, &((struct c7000_controller *)dev->priv)->tbusy);
+	netif_wake_queue(dev);
+#else
+	clear_bit(nr, (void *)&dev->tbusy);
+#endif
+	return;
+}
+
+/*
+	Test and set a bit in the device structure transmission busy flag.
+*/
+
+static __inline__ int
+c7000_ts_busy(int nr, STRUCT_NET_DEVICE *dev)
+{
+#ifdef NEWSTUFF
+	netif_stop_queue(dev);
+	return test_and_set_bit(nr, &((struct c7000_controller *)dev->priv)->tbusy);
+#else
+	return(test_and_set_bit(nr, (void *)&dev->tbusy));
+#endif
+}
+
+/*
+	Set the C7000 controller in the error state.
+*/
+
+static void
+c7000_error(struct c7000_controller *ccp)
+{
+	int			i;
+	struct	c7000_unit	*cup;
+	STRUCT_NET_DEVICE	*dev = ccp->dev;
+
+	for (i = 0; i < NUNITS; i++) {
+		cup = &ccp->cunits[i];
+		cup->state = C7000_ERROR;
+	}
+
+	if (dev != NULL)
+#ifdef NEWSTUFF
+		/* RBH XXX Should we be doing this? */
+		dev->state &= ~__LINK_STATE_START;
+#else
+		dev->flags &= ~IFF_RUNNING;
+#endif
+
+	CPrintk(0, "c7000: c7000_error: base unit 0x%x is down\n", ccp->base_addr);
+	return;
+}
+
+/*
+	Based on the SENSE ID information, fill in the
+	controller name.  Note that this is the SENSE ID
+	information saved by LINUX/390 at boot time.
+*/
+
+static int
+c7000_check_type(senseid_t *id)
+{
+
+	switch (id->cu_type) {
+
+		case C7000_CU_TYPE:
+
+			if (id->cu_model == C7000_CU_MODEL) {
+				controller = "C7000  ";
+ 				return(0);
+			}
+
+			break;
+
+                default:
+			break;
+	}
+
+	return(-1);
+}
+
+/*
+	Check the device information for the controller.
+*/
+
+static int
+c7000_check_devices(int devno)
+{
+	int		i;
+	s390_dev_info_t	temp;
+
+	/*
+		Get the SENSE ID information for each device.
+	*/
+
+	for (i = devno; i < (devno + NUNITS); i++) {
+
+		if (get_dev_info_by_devno(devno, &temp) != 0)
+			return(-1);
+		
+		if (c7000_check_type(&temp.sid_data) == -1)
+			return(-1);
+	}
+
+	CPrintk(1, "c7000: c7000_check_devices: device type is %s\n", controller);
+	return(0);
+}
+
+/*
+	Issue a halt I/O to device pointed to by cup.
+*/
+
+static int
+c7000_haltio(struct c7000_unit *cup)
+{
+	__u32			parm;
+	__u8			flags = 0x00;
+	__u32			saveflags;
+	DECLARE_WAITQUEUE(wait, current);
+	int			rc;
+
+	s390irq_spin_lock_irqsave(cup->irq, saveflags);
+	parm = (unsigned long)cup;
+
+	if ((rc = halt_IO(cup->irq, parm, flags)) != 0) {
+		s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+		return(rc);
+	}
+
+	/*
+		Wait for the halt I/O to finish.
+	*/
+
+	add_wait_queue(&cup->wait, &wait);
+	current->state = TASK_UNINTERRUPTIBLE;
+	s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+	schedule();
+	remove_wait_queue(&cup->wait, &wait);
+	return(0);
+}
+
+/*
+	Issue a start I/O to device pointed to by cup.
+*/
+
+static int
+c7000_doio(struct c7000_unit *cup)
+{
+	__u32			parm;
+	__u8			flags = 0x00;
+	__u32			saveflags;
+	DECLARE_WAITQUEUE(wait, current);
+	int			rc;
+
+	/*
+		Do no further I/O while the device is in the ERROR, STOP
+		or STOPPED state.
+	*/
+
+	if (cup->state == C7000_ERROR || cup->state == C7000_STOP || cup->state == C7000_STOPPED)
+		return(-1);
+
+	s390irq_spin_lock_irqsave(cup->irq, saveflags);
+	parm = (unsigned long)cup;
+
+	if ((rc = do_IO(cup->irq, &cup->ccws[0], parm, 0xff, flags)) != 0) {
+		s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+		return(rc);
+	}
+	
+	/*
+		Wait for the I/O to complete.
+	*/
+
+	add_wait_queue(&cup->wait, &wait);
+	current->state = TASK_UNINTERRUPTIBLE;
+	s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+	schedule();
+	remove_wait_queue(&cup->wait, &wait);
+
+	/*
+		Interrupt handling may have marked the device in ERROR.
+	*/
+
+	if (cup->state == C7000_ERROR)
+		return(-1);
+
+	return(0);
+}
+
+/*
+	Build a channel program to do a sense id channel program.
+*/
+
+static void
+c7000_bld_senseid_chpgm(struct c7000_unit *cup)
+{
+	ccw1_t	*ccwp;
+
+	ccwp = &cup->ccws[0];
+	ccwp->cmd_code = C7000_SID_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(&cup->senseid);
+	ccwp->count = SIDL;
+	ccwp++;
+	ccwp->cmd_code = C7000_NOOP_CCW;
+	ccwp->flags = CCW_FLAG_SLI;
+	ccwp->cda = (__u32)NULL;
+	ccwp->count = 1;
+	return;
+}
+
+/*
+	Build a channel program to write a control message.
+*/
+
+static void
+c7000_bld_wrtctl_chpgm(struct c7000_unit *cup)
+{
+	ccw1_t	*ccwp;
+
+	ccwp = &cup->ccws[0];
+	ccwp->cmd_code = C7000_WRITE_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(&cup->control_blk);
+	ccwp->count = sizeof(struct c7000_control_blk);
+	ccwp++;
+	ccwp->cmd_code = C7000_READFF_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(&cup->readff);
+	ccwp->count = C7000_READFFL;
+	ccwp++;
+	ccwp->cmd_code = C7000_TIC_CCW;
+	ccwp->flags = 0;
+	ccwp->cda = (__u32)virt_to_phys(ccwp + 1);
+	ccwp->count = 0;
+	ccwp++;
+	ccwp->cmd_code = C7000_NOOP_CCW;
+	ccwp->flags = CCW_FLAG_SLI;
+	ccwp->cda = (__u32)NULL;
+	ccwp->count = 1;
+	return;
+}
+
+/*
+	Build a write channel program to write the indicated buffer.
+*/
+
+static void
+c7000_bld_wrt_chpgm(struct c7000_unit *cup, struct c7000_buffer *buf)
+{
+	ccw1_t				*ccwp;
+	struct	c7000_controller	*ccp = cup->cntlp;
+
+	ccwp = &buf->ccws[0];
+	ccwp->cmd_code = C7000_WRITE_CCW | (ccp->linkid << 3);
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(buf->data);
+	ccwp->count = buf->len;
+	ccwp++;
+	ccwp->cmd_code = C7000_READFF_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(buf->data + C7000_DATAL + C7000_READHDRL);
+	ccwp->count = C7000_READFFL;
+	ccwp++;
+	ccwp->cmd_code = C7000_TIC_CCW;
+	ccwp->flags = 0;
+	ccwp->cda = (__u32)virt_to_phys(ccwp + 1);
+	ccwp->count = 0;
+	ccwp++;
+	ccwp->cmd_code = C7000_NOOP_CCW;
+	ccwp->flags = (CCW_FLAG_SLI);
+	ccwp->cda = (__u32)NULL;
+	ccwp->count = 1;
+	return;
+}
+
+/*
+	Build a channel program to read a control message.
+*/
+
+static void
+c7000_bld_readctl_chpgm(struct c7000_unit *cup)
+{
+	ccw1_t	*ccwp;
+
+	ccwp = &cup->ccws[0];
+	ccwp->cmd_code = C7000_READ_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(&cup->control_blk);
+	ccwp->count = sizeof(struct c7000_control_blk);
+	ccwp++;
+	ccwp->cmd_code = C7000_READHDR_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(&cup->readhdr);
+	ccwp->count = C7000_READHDRL;
+	ccwp++;
+	ccwp->cmd_code = C7000_SIGSMOD_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(&cup->sigsmod);
+	ccwp->count = C7000_SIGSMODL;
+	ccwp++;
+	ccwp->cmd_code = C7000_TIC_CCW;
+	ccwp->flags = 0;
+	ccwp->cda = (__u32)virt_to_phys(ccwp + 1);
+	ccwp->count = 0;
+	ccwp++;
+	ccwp->cmd_code = C7000_NOOP_CCW;
+	ccwp->flags = (CCW_FLAG_SLI);
+	ccwp->cda = (__u32)NULL;
+	ccwp->count = 1;
+	return;
+}
+
+/*
+	Build a channel program to read the indicated buffer.
+*/
+
+static void
+c7000_bld_read_chpgm(struct c7000_unit *cup, struct c7000_buffer *buf)
+{
+	ccw1_t	*ccwp;
+
+	ccwp = &buf->ccws[0];
+	ccwp->cmd_code = C7000_READ_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(buf->data);
+	ccwp->count = C7000_DATAL;
+	ccwp++;
+	ccwp->cmd_code = C7000_READHDR_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(buf->data + C7000_DATAL);
+	ccwp->count = C7000_READHDRL;
+	ccwp++;
+	ccwp->cmd_code = C7000_SIGSMOD_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC);
+	ccwp->cda = (__u32)virt_to_phys(&cup->sigsmod);
+	ccwp->count = C7000_SIGSMODL;
+	ccwp++;
+	ccwp->cmd_code = C7000_TIC_CCW;
+	ccwp->flags = 0;
+	ccwp->cda = (__u32)virt_to_phys(ccwp + 3);
+	ccwp->count = 0;
+	ccwp++;
+	ccwp->cmd_code = C7000_READFF_CCW;
+	ccwp->flags = (CCW_FLAG_SLI | CCW_FLAG_CC | CCW_FLAG_PCI);
+	ccwp->cda = (__u32)virt_to_phys(&cup->readff);
+	ccwp->count = C7000_READFFL;
+	ccwp++;
+	ccwp->cmd_code = C7000_TIC_CCW;
+	ccwp->flags = 0;
+	ccwp->cda = (__u32)virt_to_phys(ccwp + 1);
+	ccwp->count = 0;
+	ccwp++;
+	ccwp->cmd_code = C7000_NOOP_CCW;
+	ccwp->flags = (CCW_FLAG_SLI);
+	ccwp->cda = (__u32)NULL;
+	ccwp->count = 1;
+	return;
+}
+
+/*
+	Allocate buffer structure headers and buffers for all units
+	A return value of 0 means that all allocations worked.  A -1
+	means that an allocation failed.  It is expected that the caller
+	will call c7000_free_buffers when -1 is returned.
+*/
+
+static int
+c7000_alloc_buffers(STRUCT_NET_DEVICE *dev)
+{
+	int				i;
+	int				j;
+	char				*data;
+	struct	c7000_buffer		*bufptr;
+	struct	c7000_controller	*ccp = (struct c7000_controller *) dev->priv;
+	struct	c7000_unit		*cup;
+	
+	for (i = 0; i < NUNITS; i++) {
+		cup = &ccp->cunits[i];
+		cup->free = NULL;
+
+		for (j = 0; j < C7000_MAXBUF; j++) {
+			bufptr = kmalloc(sizeof(struct c7000_buffer), GFP_KERNEL);
+			data = kmalloc(C7000_BUFSIZE, GFP_KERNEL);
+
+			if (bufptr == NULL)
+			{
+				if(data)
+					kfree(data);
+				return(-1);
+			}
+
+			/*
+				Place filled in buffer header on free anchor.
+			*/
+
+			bufptr->next = cup->free;
+			bufptr->data = data;
+			bufptr->len = 0;
+			cup->free = bufptr;
+
+			if (data == NULL)
+				return(-1);
+
+			memset(data, '\0', C7000_BUFSIZE);
+		}
+
+	}
+
+	CPrintk(1, "c7000: c7000_alloc_buffers: allocated buffers for base unit 0x%lx\n", dev->base_addr);
+	return(0);
+}
+
+/*
+	Free buffers on a chain.
+*/
+
+static void
+c7000_free_chain(struct c7000_buffer *buf)
+{
+	char			*data;
+	struct	c7000_buffer	*bufptr = buf;
+	struct	c7000_buffer	*tmp;
+
+	while (bufptr != NULL) {
+		data = bufptr->data;
+
+		if (data != NULL)
+			kfree(data);
+
+		tmp = bufptr;
+		bufptr = bufptr->next;
+		kfree(tmp);
+	}
+
+	return;
+}
+
+/*
+	Free buffers on all possible chains for all units.
+*/
+
+static void
+c7000_free_buffers(STRUCT_NET_DEVICE *dev)
+{
+	int				i;
+	struct	c7000_controller	*ccp = (struct c7000_controller *) dev->priv;
+	struct	c7000_unit	*cup;
+	
+	for (i = 0; i < NUNITS; i++) {
+		cup = &ccp->cunits[i];
+		c7000_free_chain(cup->free);
+		cup->free = NULL;
+		c7000_free_chain(cup->proc_head);
+		cup->proc_head = cup->proc_tail = NULL;
+		c7000_free_chain(cup->bh_head);
+		cup->bh_head = cup->bh_tail = NULL;
+	}
+
+	CPrintk(1, "c7000: c7000_free_buffers: freed buffers for base unit 0x%lx\n", dev->base_addr);
+	return;
+}
+
+/*
+	Obtain a free buffer.  Return a pointer to the c7000_buffer
+	structure OR NULL.
+*/
+
+struct c7000_buffer *
+c7000_get_buffer(struct c7000_unit *cup) 
+{
+	struct	c7000_buffer	*buf;
+
+	buf = cup->free;
+
+	if (buf == NULL)
+		return(NULL);
+
+	cup->free = buf->next;
+	buf->next = NULL;
+	return(buf);
+}
+
+/*
+	Release a buffer to the free list.
+*/
+
+void
+c7000_release_buffer(struct c7000_unit *cup, struct c7000_buffer *buf)
+{
+	struct	c7000_buffer	*tmp;
+
+	tmp = cup->free;
+	cup->free = buf;
+	buf->next = tmp;
+	return;
+}
+
+/*
+	Queue a buffer on the end of the processing (proc) chain.
+*/
+
+void
+c7000_queue_buffer(struct c7000_unit *cup, struct c7000_buffer *buf)
+{
+	buf->next = NULL;
+
+	if (cup->proc_head == NULL) {
+		cup->proc_head = cup->proc_tail = buf;
+		return;
+	}
+
+	cup->proc_tail->next = buf;
+	cup->proc_tail = buf;
+	return;
+}
+
+/*
+	Dequeue a buffer from the start of the processing (proc) chain.
+*/
+
+struct c7000_buffer *
+c7000_dequeue_buffer(struct c7000_unit *cup)
+{
+	struct	c7000_buffer	*buf = cup->proc_head;
+
+	if (buf == NULL)
+		return(NULL);
+
+	cup->proc_head = buf->next;
+
+	if (cup->proc_head == NULL)
+		cup->proc_tail = NULL;
+
+	buf->next = NULL;
+	return(buf);
+}
+
+/*
+	Queue a buffer on the end of the bh routine chain.
+*/
+
+void
+c7000_queue_bh_buffer(struct c7000_unit *cup, struct c7000_buffer *buf)
+{
+	buf->next = NULL;
+
+	if (cup->bh_head == NULL) {
+		cup->bh_head = cup->bh_tail = buf;
+		return;
+	}
+
+	cup->bh_tail->next = buf;
+	cup->bh_tail = buf;
+	return;
+}
+
+/*
+	Dequeue a buffer from the start of the bh routine chain.
+*/
+
+struct c7000_buffer *
+c7000_dequeue_bh_buffer(struct c7000_unit *cup)
+{
+	struct	c7000_buffer	*buf = cup->bh_head;
+
+	if (buf == NULL)
+		return(NULL);
+
+	cup->bh_head = buf->next;
+
+	if (cup->bh_head == NULL)
+		cup->bh_tail = NULL;
+
+	buf->next = NULL;
+	return(buf);
+}
+
+/*
+	Build up a list of buffers to read.  Each buffer is described
+	by one c7000_buffer structure.  The c7000_buffer structure
+	contains a channel segment that will read that one buffer.
+	The channel program segments are chained together via TIC
+	CCWS.
+*/
+
+static int
+c7000_bld_read_chain(struct c7000_unit *cup)
+{
+	struct	c7000_buffer	*buf, *pbuf = NULL;
+	struct	c7000_rd_header	*head;
+	int			num = 0;
+
+	while (cup->free != NULL) {
+
+		/*
+			Obtain a buffer for a read channel segment.
+		*/
+
+		if ((buf = c7000_get_buffer(cup)) == NULL) {
+			CPrintk(0, "c7000: c7000_bld_read_chain: can not obtain a read buffer for unit 0x%x\n", cup->devno);
+			return(-ENOMEM);
+		}
+
+		num++;
+		buf->len = 0;
+
+		/*
+			Clear out the read header flag.
+		*/
+
+		head = (struct c7000_rd_header *)(buf->data + C7000_DATAL);
+		head->flag = 0x00;
+		c7000_queue_buffer(cup, buf);
+
+		/*
+			Build the read channel program segment.
+		*/
+
+		c7000_bld_read_chpgm(cup, buf);
+
+		/*
+			Chain the prior (if any) channel program segment to
+			this one.
+		*/
+
+		if (pbuf != NULL)
+			pbuf->ccws[3].cda = pbuf->ccws[5].cda = (__u32)virt_to_phys(&buf->ccws[0]);
+
+		pbuf = buf;
+	}
+
+	CPrintk(1, "c7000: c7000_bld_read_chain: chained %d buffers for unit 0x%x\n", num, cup->devno);
+	return(0);
+}
+
+/*
+	Build up a list of buffers to write.  Each buffer is described
+	by one c7000_buffer structure.  The c7000_buffer structure
+	contains a channel segment that will write that one buffer.
+	The channel program segments are chained together via TIC
+	CCWS.
+*/
+
+static void
+c7000_bld_wrt_chain(struct c7000_unit *cup)
+{
+	struct	c7000_buffer	*buf = cup->proc_head, *pbuf = NULL;
+	int			num = 0;
+
+	while (buf != NULL) {
+		c7000_bld_wrt_chpgm(cup, buf);
+
+		/*
+			Chain the channel program segments together.
+		*/
+
+		if (pbuf != NULL)
+			pbuf->ccws[2].cda = (__u32)virt_to_phys(&buf->ccws[0]);
+
+		pbuf = buf;
+		buf = buf->next;
+		num++;
+	}
+
+	CPrintk(1, "c7000: c7000_bld_wrt_chain: chained %d buffers for unit 0x%x\n", num, cup->devno);
+	return;
+}
+
+/*
+	Interrupt handler bottom half (bh) routine.
+	Process all of the buffers on the c7000_unit bh chain.
+	The bh chain is populated by the interrupt routine when
+	a READ channel program completes on a buffer.
+*/
+
+static void
+c7000_irq_bh(struct c7000_unit *cup)
+{
+	struct	c7000_buffer		*buf, *pbuf;
+	struct	c7000_rd_header		*head;	
+	struct	sk_buff			*skb;
+	struct	c7000_controller	*ccp;
+	STRUCT_NET_DEVICE		*dev;
+	int				rc;
+	__u16				data_length;
+	__u32				parm;
+	__u8				flags = 0x00;
+	__u32				saveflags;
+
+	ccp = cup->cntlp;
+	dev = ccp->dev;
+
+	s390irq_spin_lock_irqsave(cup->irq, saveflags);
+
+	/*
+		Process all buffers sent to bh by the interrupt routine.
+	*/
+
+	while (cup->bh_head != NULL) {
+		buf = c7000_dequeue_bh_buffer(cup);
+
+		/*
+			Deference the data as a c7000 header.
+		*/
+
+		head = (struct c7000_rd_header *)(buf->data + C7000_DATAL);
+
+		/*
+			If it is a control message, release the buffer and 
+			continue the loop.
+		*/
+
+		if (C7000_LINKID(head->cmd) == 0) {
+			CPrintk(0, "c7000: c7000_irq_bh: unexpected control command %d on unit 0x%x\n", head->cmd, cup->devno);
+			c7000_release_buffer(cup, buf);
+			continue;
+		}
+			
+		/*
+			Allocate a socket buffer.
+		*/
+
+		data_length = head->len;
+		skb = dev_alloc_skb(data_length);
+
+		/*
+			Copy the data to the skb.
+			Send it to the upper layers.
+		*/
+
+		if (skb != NULL) {
+			memcpy(skb_put(skb, data_length), buf->data, data_length);
+			skb->dev = dev;
+			skb->protocol = htons(ETH_P_IP);
+			skb->pkt_type = PACKET_HOST;
+			skb->mac.raw = skb->data;
+			skb->ip_summed = CHECKSUM_UNNECESSARY;
+			netif_rx(skb);
+			ccp->stats.rx_packets++;
+		} else {
+			CPrintk(0, "c7000: c7000_irq_bh: can not allocate a skb for unit 0x%x\n", cup->devno);
+			ccp->stats.rx_dropped++;
+		}
+
+		/*
+			Rechain the buffer on the processing list.
+		*/
+
+		head->flag = 0x00;
+		buf->len = 0;
+		pbuf = cup->proc_tail;
+		c7000_queue_buffer(cup, buf);
+
+		/*
+			Rechain the buffer on the running channel program.
+		*/
+
+		if (pbuf != NULL)
+			pbuf->ccws[3].cda = pbuf->ccws[5].cda = (__u32)virt_to_phys(&buf->ccws[0]);
+
+	}
+
+	/*
+		Restart the READ channel program if IO_active is 0.
+	*/
+
+	if (test_and_set_bit(0, (void *)&cup->IO_active) == 0) {
+
+		if ((rc = c7000_bld_read_chain(cup)) != 0) {
+			CPrintk(0, "c7000: c7000_irq_bh: can not build read chain for unit 0x%x, return code %d\n", cup->devno, rc);
+			c7000_error(cup->cntlp);
+			clear_bit(C7000_BH_ACTIVE, (void *)&cup->flag_a);
+			s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+			return;
+		}
+
+		parm = (__u32)cup;
+		cup->state = C7000_READ;
+
+		if ((rc = do_IO(cup->irq, &cup->proc_head->ccws[0], parm, 0xff, flags)) != 0) {
+			CPrintk(0, "c7000: c7000_irq_bh: can not start READ IO to unit 0x%x, return code %d\n", cup->devno, rc);
+			c7000_error(cup->cntlp);
+			clear_bit(C7000_BH_ACTIVE, (void *)&cup->flag_a);
+			s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+			return;
+		}
+
+		CPrintk(1, "c7000: c7000_irq_bh: started READ IO to unit 0x%x\n", cup->devno);
+	}
+			
+	/*
+		Clear the bh active indication.
+	*/
+
+	clear_bit(C7000_BH_ACTIVE, (void *)&cup->flag_a);
+	s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+	return;
+}
+
+/*
+	Send a system validate control command to a unit.
+*/
+
+static int
+c7000_send_sysval(struct c7000_unit *cup)
+{
+	int				rc;
+	struct	c7000_controller	*ccp = cup->cntlp;
+	struct	c7000_control_blk	*ctlblkp = &(cup->control_blk);
+
+	CPrintk(1, "c7000: c7000_send_sysval: send sysval for device 0x%x\n", cup->devno);
+
+	/*
+		Build the system validate control message.
+	*/
+
+	memset(ctlblkp, '\0', sizeof(struct c7000_control_blk));
+	ctlblkp->cmd = C7000_SYS_VALIDATE;
+	ctlblkp->correlator = 0;
+	ctlblkp->link_id = ccp->linkid;
+	ctlblkp->ver = ccp->version; 
+	memcpy(ctlblkp->hostname, ccp->lhost, NAMLEN);
+	memcpy(ctlblkp->unitname, ccp->uhost, NAMLEN);
+	ctlblkp->rdsize = C7000_DATAL;
+        ctlblkp->wrtsize = C7000_DATAL;
+
+	/*
+		Build the channel program.
+	*/
+
+	c7000_bld_wrtctl_chpgm(cup);
+
+	/*
+		Do the IO and wait for write to complete.
+	*/
+
+	if ((rc = c7000_doio(cup)) != 0) {
+		CPrintk(0, "c7000: c7000_send_sysval failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+		return(-1);
+	}
+
+	return(0);
+}
+
+/*
+	Send a system validate response control command to a unit.
+*/
+
+static int
+c7000_send_sysval_resp(struct c7000_unit *cup, unsigned char correlator, int ret_code)
+{
+	int				rc;
+	struct	c7000_controller	*ccp = cup->cntlp;
+	struct	c7000_control_blk	*ctlblkp = &(cup->control_blk);
+
+	CPrintk(1, "c7000: c7000_send_sysval_resp: send sysval response for device 0x%x\n", cup->devno);
+
+	/*
+		Build the system validate response control message.
+	*/
+
+	memset(ctlblkp, '\0', sizeof(struct c7000_control_blk));
+	ctlblkp->cmd = C7000_SYS_VALIDATE_RESP;
+	ctlblkp->correlator = correlator;
+	ctlblkp->ret_code = ret_code;
+	ctlblkp->link_id = ccp->linkid;
+	ctlblkp->ver = ccp->version; 
+	memcpy(ctlblkp->hostname, ccp->lhost, NAMLEN);
+	memcpy(ctlblkp->unitname, ccp->uhost, NAMLEN);
+	ctlblkp->rdsize = C7000_DATAL;
+        ctlblkp->wrtsize = C7000_DATAL;
+
+	/*
+		Build the channel program.
+	*/
+
+	c7000_bld_wrtctl_chpgm(cup);
+
+	/*
+		Do the IO and wait for write to complete.
+	*/
+
+	if ((rc = c7000_doio(cup)) != 0) {
+		CPrintk(0, "c7000: c7000_send_sysval_resp failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+		return(-1);
+	}
+
+	return(0);
+}
+
+/*
+	Check the information read in a SYS_VALIDATE control message.
+*/
+
+static int
+c7000_checkinfo(struct c7000_unit *cup)
+{
+	struct	c7000_controller	*ccp = cup->cntlp;
+ 	struct	c7000_control_blk	*ctlblkp = &cup->control_blk;
+	int				ret_code = 0;
+
+	if (memcmp(ccp->lhost, ctlblkp->hostname, NAMLEN) ||
+		memcmp(ccp->uhost, ctlblkp->unitname, NAMLEN))
+		ret_code = Err_Names_not_Matched;
+
+	if (ctlblkp->ver != ccp->version)
+		ret_code = Err_Wrong_Version;
+
+        if ((ctlblkp->rdsize < C7000_DATAL) || (ctlblkp->wrtsize < C7000_DATAL))
+		ret_code = Err_Wrong_Frame_Size;
+
+	if (ret_code != 0)
+		CPrintk(0, "c7000: c7000_checkinfo: ret_code %d for device 0x%x\n", ret_code, cup->devno);
+
+	return(ret_code);
+}
+
+/*
+	Keep reading until a sysval response comes in or an error.
+*/
+
+static int
+c7000_get_sysval_resp(struct c7000_unit *cup)
+{
+	struct	c7000_controller	*ccp = cup->cntlp;
+	int				resp = 1;
+	int				req = 1;
+	int				rc;
+	int				ret_code = 0;
+
+	CPrintk(1, "c7000: c7000_get_sysval_resp: get sysval response for unit 0x%x\n", cup->devno);
+
+	/*
+		Wait for the response to C7000_SYS_VALIDATE and for an
+		inbound C7000_SYS_VALIDATE.
+	*/
+
+	while (resp || req) {
+
+		/*
+			Build the read channel program.
+		*/
+
+		c7000_bld_readctl_chpgm(cup);
+
+		if ((rc = c7000_doio(cup)) != 0) {
+			CPrintk(0, "c7000: c7000_get_sysval_resp: failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+			return(-1);
+		}
+
+		/*
+			Process the control message.
+		*/
+
+		switch (cup->control_blk.cmd) {
+
+			/*
+				Check that response is positive and return
+				with success. Otherwise, return with an
+				error.
+			*/
+
+			case C7000_SYS_VALIDATE_RESP:
+
+				if (cup->control_blk.ret_code == 0)
+					resp = 0;
+				else {
+					CPrintk(0, "c7000: c7000_get_sysval_resp: receive sysval response for device 0x%x, return code %d\n",
+						cup->devno,
+						cup->control_blk.ret_code);
+					return(-1);
+				}
+
+				break;
+
+			/*
+				Check that the request is reasonable and
+				send a SYS_VALIDATE_RESP.  Otherwise,
+				return with an error.
+			*/
+
+			case C7000_SYS_VALIDATE:
+				CPrintk(1, "c7000: c7000_get_sysval_resp: receive sysval for device 0x%x\n", cup->devno);
+				req = 0;
+				ret_code = c7000_checkinfo(cup);
+
+				if (c7000_send_sysval_resp(&ccp->cunits[C7000_WR], cup->control_blk.correlator, ret_code) != 0)
+					return(-1);
+
+				if (ret_code != 0)
+					return(-1);
+
+				break;
+
+			/*
+				Anything else is unexpected and will result
+				in a return with an error.
+			*/
+
+			default:
+				CPrintk(0, "c7000: c7000_get_sysval_resp: receive unexpected command for device 0x%x, command %d\n", cup->devno, cup->control_blk.cmd);
+				return(-1);
+				break;
+		}
+
+	}
+
+	return(0);
+}
+
+/*
+	Send a connection confirm control message.
+*/
+
+static int
+c7000_conn_confrm(struct c7000_unit *cup, unsigned char correlator, int linkid)
+{
+	int				rc;
+	struct	c7000_controller	*ccp = cup->cntlp;
+	struct	c7000_control_blk	*ctlblkp = &(cup->control_blk);
+
+	CPrintk(1, "c7000: c7000_conn_confrm: send the connection confirmation message for unit 0x%x\n", cup->devno);
+
+	/*
+		Build the connection confirm control message.
+	*/
+
+	memset(ctlblkp, '\0', sizeof(struct c7000_control_blk));
+	ctlblkp->cmd = C7000_CONN_CONFRM;
+	ctlblkp->ver = ccp->version;
+	ctlblkp->link_id = linkid;
+	ctlblkp->correlator = correlator;
+	ctlblkp->rdsize = 0;
+	ctlblkp->wrtsize = 0;
+	memcpy(ctlblkp->hostname, ccp->lappl, NAMLEN);
+	memcpy(ctlblkp->unitname, ccp->uappl, NAMLEN);
+
+	/*
+		Build the channel program.
+	*/
+
+	c7000_bld_wrtctl_chpgm(cup);
+
+	/*
+		Do the IO and wait for write to complete.
+	*/
+
+	if ((rc = c7000_doio(cup)) != 0) {
+		CPrintk(0, "c7000: c7000_conn_confrm: failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+		return(-1);
+	}
+
+	return(0);
+}
+
+/*
+	Send a connection request control message.
+*/
+
+static int
+c7000_send_conn(struct c7000_unit *cup)
+{
+	int				rc;
+	struct	c7000_controller	*ccp = cup->cntlp;
+	struct	c7000_control_blk	*ctlblkp = &(cup->control_blk);
+
+	CPrintk(1, "c7000: c7000_send_conn: send the connection request message for unit 0x%x\n", cup->devno);
+
+	/*
+		Build the connection request control message.
+	*/
+
+	memset(ctlblkp, '\0', sizeof(struct c7000_control_blk));
+	ctlblkp->cmd = C7000_CONN_REQ;
+	ctlblkp->ver = ccp->version;
+	ctlblkp->link_id = 0;
+	ctlblkp->correlator = 0;
+	ctlblkp->rdsize = C7000_DATAL;
+	ctlblkp->wrtsize = C7000_DATAL;
+	memcpy(ctlblkp->hostname, ccp->lappl, NAMLEN);
+	memcpy(ctlblkp->unitname, ccp->uappl, NAMLEN);
+
+	/*
+		Build the channel program.
+	*/
+
+	c7000_bld_wrtctl_chpgm(cup);
+
+	/*
+		Do the IO and wait for write to complete.
+	*/
+
+	if ((rc = c7000_doio(cup)) != 0) {
+		CPrintk(0, "c7000: c7000_send_conn: failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+		return(-1);
+	}
+
+	return(0);
+}
+
+/*
+	Send a disconnect control message to the link with the value of
+	linkid.
+*/
+
+static int
+c7000_send_disc(struct c7000_unit *cup, int linkid)
+{
+	int				rc;
+	struct	c7000_controller	*ccp = cup->cntlp;
+	struct	c7000_control_blk	*ctlblkp = &(cup->control_blk);
+
+	CPrintk(1, "c7000: c7000_send_disc: send disconnect message for unit 0x%x\n", cup->devno);
+
+	/*
+		Build the disconnect control message.
+	*/
+
+	memset(ctlblkp, '\0', sizeof(struct c7000_control_blk));
+	ctlblkp->cmd = C7000_DISCONN;
+	ctlblkp->ver = ccp->version;
+	ctlblkp->link_id = linkid;
+	ctlblkp->correlator = 0;
+	ctlblkp->rdsize = C7000_DATAL;
+	ctlblkp->wrtsize = C7000_DATAL;
+	memcpy(ctlblkp->hostname, ccp->lappl, NAMLEN);
+	memcpy(ctlblkp->unitname, ccp->uappl, NAMLEN);
+
+	/*
+		Build the channel program.
+	*/
+
+	c7000_bld_wrtctl_chpgm(cup);
+
+	/*
+		Do the IO and wait for write to complete.
+	*/
+
+	if ((rc = c7000_doio(cup)) != 0) {
+		CPrintk(0, "c7000: c7000_send_disc: failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+		return(-1);
+	}
+
+	return(0);
+}
+
+/*
+	Resolve the race condition based on the link identifier value.
+	The adapter microcode assigns the identifiers.  A higher value implies
+	that the race was lost. A side effect of this function is that
+	ccp->linkid is set to the link identifier to be used for this
+	connection (provided that 0 is returned).
+*/
+
+static int
+c7000_resolve_race(struct c7000_unit *cup, int local_linkid, int remote_linkid)
+{
+	struct c7000_controller 	*ccp = cup->cntlp;
+
+	CPrintk(1, "c7000: c7000_resolve_race: for unit 0x%x, local linkid %d, remote linkid %d\n", cup->devno, local_linkid, remote_linkid);
+
+	/*
+		This local link identifier should not be zero..
+	*/
+
+	if (local_linkid == 0) {
+		CPrintk(0, "c7000: c7000_resolve_race: error for unit 0x%x, local linkid is null\n", cup->devno);
+		return(-1);
+	}
+
+	/*
+		This indicates that there is no race.  Just use our
+		local link identifier.
+	*/
+
+	if (remote_linkid == 0) {
+		ccp->linkid = local_linkid;
+		return(0);
+	}
+
+	/*
+		Send a connection confirm message if we lost the race to
+		the winning link identifier.
+
+		Either way, save the winning link identifier.
+	*/
+
+	if (local_linkid > remote_linkid) {
+
+		if (c7000_conn_confrm(&ccp->cunits[C7000_WR], cup->control_blk.correlator, remote_linkid) != 0) {
+			CPrintk(0, "c7000: c7000_resolve_race: failed for unit 0x%x\n", cup->devno);
+			return(-1);
+		}
+
+		ccp->linkid = remote_linkid;
+	} else {
+		ccp->linkid = local_linkid;
+	}
+
+	return(0);
+}
+
+/*
+	Get connected by processing the connection request/response/confirm
+	control messages.  A connection request has already been sent by
+	calling function c7000_send_conn.
+*/
+
+static int
+c7000_get_conn(struct c7000_unit *cup)
+{
+	struct c7000_controller 	*ccp = cup->cntlp;
+	int				rc;
+	int				cont = 1;
+	int				remote_linkid = 0;
+	int				local_linkid = 0;
+
+	CPrintk(1, "c7000: c7000_get_conn: read the connected message for unit 0x%x\n", cup->devno);
+	ccp->linkid = 0;
+
+	while (cont == 1) {
+
+		/*
+			Build the read channel program.
+		*/
+
+		c7000_bld_readctl_chpgm(cup);
+
+		/*
+			Start the channel program to read a control message.
+		*/
+
+		if ((rc = c7000_doio(cup)) != 0) {
+			CPrintk(0, "c7000: c7000_get_conn: failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+			return(-1);
+		}
+
+		/*
+			Process the control message that was received based
+			on the command code.
+		*/
+
+		CPrintk(1, "c7000: c7000_get_conn: received command %d for unit 0x%x\n", cup->control_blk.cmd, cup->devno);
+
+		switch(cup->control_blk.cmd) {
+
+			/*
+				Save the remote link_id in the message for
+				a check in c7000_resolve_race.
+			*/
+
+			case C7000_CONN_REQ:
+				remote_linkid = cup->control_blk.link_id;
+				break;
+
+			/*
+				A connection response received.  Resolve
+				the network race condition (if any) by
+				comparing the link identifier values.
+			*/
+
+			case C7000_CONN_RESP:
+
+				if (cup->control_blk.ret_code != 0) {
+					CPrintk(0, "c7000: c7000_get_conn: failed for unit 0x%x , connection response return code %d\n",
+						cup->devno, cup->control_blk.ret_code);
+					return(-1);
+				}
+
+				local_linkid = cup->control_blk.link_id;
+
+				if (c7000_resolve_race(cup, local_linkid, remote_linkid) != 0)
+					return(-1);
+
+				break;
+
+			/*
+				Got a confirmation to our connection request.
+				Disconnect the remote link identifier (if any).
+				Break out of the loop.
+			*/
+
+			case C7000_CONN_CONFRM:
+
+				if (remote_linkid != 0) {
+
+					if (c7000_send_disc(&ccp->cunits[C7000_WR], remote_linkid) != 0) {
+						CPrintk(0, "c7000: c7000_get_conn: send disconnect failed for unit 0x%x\n", cup->devno);
+						return(-1);
+					}
+
+				}
+
+				cont = 0;
+				break;
+
+			/*
+				Got a disconnect to our connection request.
+				Break out of the loop.
+			*/
+
+			case C7000_DISCONN:
+				cont = 0;
+				break;
+
+			/*
+				Anything else must be an error.
+				Return with an error immediately.
+			*/
+
+			default:
+				CPrintk(0, "c7000: c7000_get_conn: failed for unit 0x%x unexpected command %d\n",
+					cup->devno, cup->control_blk.cmd);
+				return(-1);
+		}
+
+	}
+
+	/*
+		Be sure that we now have a link identifier.
+	*/
+
+	if (ccp->linkid == 0)
+		return(-1);
+
+	return(0);
+}
+
+/*
+	Get statistics method.
+*/
+
+struct net_device_stats *
+c7000_stats(STRUCT_NET_DEVICE *dev)
+{
+	struct	c7000_controller	*ccp = (struct c7000_controller *)dev->priv;
+
+	return(&ccp->stats);
+}
+
+/*
+	Open method.
+*/
+
+static int
+c7000_open(STRUCT_NET_DEVICE *dev)
+{
+	int				i;
+	struct	c7000_controller	*ccp = (struct c7000_controller *)dev->priv;
+	struct	c7000_unit		*cup;
+	int				rc;
+	__u32				parm;
+	__u8				flags = 0x00;
+
+	c7000_set_busy(dev);
+
+	/*
+		Allocate space for the unit buffers.
+	*/
+
+	if (c7000_alloc_buffers(dev) == -1) {
+		CPrintk(0, "c7000: c7000_open: can not allocate buffer space for base unit 0x%lx\n", dev->base_addr);
+		c7000_free_buffers(dev);  /* free partially allocated buffers */
+		c7000_clear_busy(dev);
+		return(-ENOMEM);
+	}
+	
+	/*
+		Perform the initialization for all units.
+	*/
+
+	for (i = 0; i < NUNITS; i++) {
+		cup = &ccp->cunits[i];
+
+		/*
+			Initialize task queue structure used for the bottom
+			half routine.
+		*/
+
+#ifndef NEWSTUFF
+		cup->tq.next = NULL;
+#endif
+		cup->tq.sync = 0;
+		cup->tq.routine = (void *)(void *)c7000_irq_bh;
+		cup->tq.data = cup;
+		cup->state = C7000_HALT;
+#ifdef NEWSTUFF
+		init_waitqueue_head(&cup->wait);
+#endif
+		CPrintk(1, "c7000: c7000_open: issuing halt to unit 0x%x\n", cup->devno);
+
+		/*
+			Issue a halt I/O to the unit
+		*/
+
+		if ((rc = c7000_haltio(cup)) != 0) {
+			CPrintk(0, "c7000: c7000_open: halt_IO failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+			continue;
+		}
+
+		cup->IO_active = 0;
+		cup->flag_a = 0;
+		cup->sigsmod = 0x00;
+
+		CPrintk(1, "c7000: c7000_open: halt complete for unit 0x%x\n", cup->devno);
+	}
+
+	/*
+		On each subchannel send a sense id.
+	*/
+
+	for (i = 0; i < NUNITS; i++) {
+		cup = &ccp->cunits[i];
+
+		/*
+			Build SENSE ID channel program.
+		*/
+
+		c7000_bld_senseid_chpgm(cup);
+	
+		/*
+			Issue the start I/O for SENSE ID channel program.
+		*/
+
+		CPrintk(1, "c7000: c7000_open: issuing SENSEID to unit 0x%x\n", cup->devno);
+
+		if ((rc = c7000_doio(cup)) != 0) {
+			CPrintk(0, "c7000: c7000_open: SENSEID failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+			c7000_clear_busy(dev);
+			return(-EIO);
+		}
+	
+		CPrintk(1, "c7000: c7000_open: SENSEID complete for unit 0x%x\n", cup->devno);
+	}
+
+	/*
+		Send the system validation control message.
+	*/
+
+	cup = &ccp->cunits[C7000_WR];
+
+	if (c7000_send_sysval(cup) != 0) {
+		CPrintk(0, "c7000: c7000_open: can not send sysval for unit 0x%x\n", cup->devno);
+		c7000_clear_busy(dev);
+		return(-EIO);
+	}
+
+	CPrintk(1, "c7000: c7000_open: successfully sent sysval for unit 0x%x\n", cup->devno);
+	/*
+		Get the system validation response message.
+	*/
+
+	cup = &ccp->cunits[C7000_RD];
+
+	if (c7000_get_sysval_resp(cup) != 0) {
+		CPrintk(0, "c7000: c7000_open: can not read sysval response for unit 0x%x\n", cup->devno);
+		c7000_clear_busy(dev);
+		return(-EIO);
+	}
+
+	CPrintk(1, "c7000: c7000_open: successfully received sysval reply for unit 0x%x\n", cup->devno);
+	ccp->cunits[C7000_RD].state = ccp->cunits[C7000_WR].state = C7000_CONNECT;
+
+	cup = &ccp->cunits[C7000_WR];
+
+	/*
+		Send a connection request.
+	*/
+
+	if (c7000_send_conn(cup) != 0) {
+		CPrintk(0, "c7000: c7000_open: connection failed for unit 0x%x\n", cup->devno);
+		c7000_clear_busy(dev);
+		return(-EIO);
+	}
+
+	cup = &ccp->cunits[C7000_RD];
+
+	/*
+		Get the response to our connection request Note that a
+		network race may occur.  This is handled in c7000_get_conn.
+	*/
+
+	if (c7000_get_conn(cup) != 0) {
+		CPrintk(0, "c7000: c7000_open: unit 0x%x has connected\n", cup->devno);
+		c7000_clear_busy(dev);
+		return(-EIO);
+	}
+
+	CPrintk(1, "c7000: c7000_open: successfully received connection request for unit 0x%x\n", cup->devno);
+	ccp->cunits[C7000_RD].state = ccp->cunits[C7000_WR].state = C7000_READY;
+
+	/*
+		Clear the interface statistics.
+	*/
+
+	memset(&ccp->stats, '\0', sizeof(struct net_device_stats));
+
+	if ((rc = c7000_bld_read_chain(cup)) != 0) {
+		c7000_clear_busy(dev);
+		return(rc);
+	}
+
+	/*
+		Start the C7000_READ channel program but do not wait for it's
+		completion.
+	*/
+
+	cup->state = C7000_READ;
+	parm = (__u32) cup;
+	set_bit(0, (void *)&cup->IO_active);
+
+	if ((rc = do_IO(cup->irq, &cup->proc_head->ccws[0], parm, 0xff, flags)) != 0) {
+		CPrintk(0, "c7000: c7000_open: READ failed with return code %d for unit 0x%x\n", rc, cup->devno);
+		c7000_error(cup->cntlp);
+		clear_bit(0, (void *)&cup->IO_active);
+		c7000_clear_busy(dev);
+		return(-EIO);
+	}
+
+#ifdef NEWSTUFF
+	netif_start_queue(dev);
+#else
+	dev->start = 1;
+#endif
+	CPrintk(0, "c7000: c7000_open: base unit 0x%lx is opened\n", dev->base_addr);
+	c7000_clear_busy(dev);
+	MOD_INC_USE_COUNT;	/* increment module usage count */
+	return(0);
+}
+
+/*
+	Stop method.
+*/
+
+static int
+c7000_stop(STRUCT_NET_DEVICE *dev)
+{
+	int				i;
+	struct	c7000_controller	*ccp = (struct c7000_controller *)dev->priv;
+	struct	c7000_unit		*cup;
+	int				rc;
+
+#ifdef NEWSTUFF
+/* nothing? */
+#else
+	dev->start = 0;
+#endif
+	c7000_set_busy(dev);
+
+	/*
+		Send a disconnect message.
+	*/
+
+	ccp->cunits[C7000_RD].state = ccp->cunits[C7000_WR].state = C7000_DISC;
+	cup = &ccp->cunits[C7000_WR];
+
+	if (c7000_send_disc(cup, ccp->linkid) != 0) {
+		CPrintk(0, "c7000: c7000_stop: send of disconnect message failed for unit 0x%x\n", cup->devno);
+	}
+
+	CPrintk(1, "c7000: c7000_stop: successfully sent disconnect message to unit 0x%x\n", cup->devno);
+
+	/*
+		Issue a halt I/O to all units.
+	*/
+
+	for (i = 0; i < NUNITS; i++) {
+		cup = &ccp->cunits[i];
+		cup->state = C7000_STOP;
+		CPrintk(1, "c7000: c7000_stop: issuing halt to unit 0x%x\n", cup->devno);
+
+		if ((rc = c7000_haltio(cup)) != 0) {
+			CPrintk(0, "c7000: c7000_stop: halt_IO failed with rc = %d for unit 0x%x\n", rc, cup->devno);
+			continue;
+		}
+
+		CPrintk(1, "c7000: c7000_stop: halt complete for unit 0x%x\n", cup->devno);
+	}
+
+	c7000_free_buffers(dev);
+	CPrintk(0, "c7000: c7000_stop: base unit 0x%lx is stopped\n", dev->base_addr);
+	MOD_DEC_USE_COUNT;	/* Decrement module usage count */
+	return(0);
+}
+
+/*
+	Configure the interface.
+*/
+
+static int
+c7000_config(STRUCT_NET_DEVICE *dev, struct ifmap *map)
+{
+	CPrintk(1, "c7000: c7000_config: entered for base unit 0x%lx\n", dev->base_addr);
+	return(0);
+}
+
+/*
+	Transmit a packet.
+*/
+
+static int
+c7000_xmit(struct sk_buff *skb, STRUCT_NET_DEVICE *dev)
+{
+	struct	c7000_controller	*ccp = (struct c7000_controller *)dev->priv;
+	struct	c7000_unit		*cup;
+	__u32				saveflags;
+	__u32				parm;
+	__u8				flags = 0x00;
+	struct	c7000_buffer		*buf, *pbuf;
+	int				rc;
+
+	CPrintk(1, "c7000: c7000_xmit: entered for base unit 0x%lx\n", dev->base_addr);
+
+	/*
+		When the skb pointer is NULL return.
+	*/
+
+	if (skb == NULL) {
+		CPrintk(0, "c7000: c7000_xmit: skb pointer is null for base unit 0x%lx\n", dev->base_addr);
+		ccp->stats.tx_dropped++;
+		return(-EIO);
+	}
+
+	cup = &ccp->cunits[C7000_WR];
+
+	/*
+		Lock the irq.
+	*/
+
+	s390irq_spin_lock_irqsave(cup->irq, saveflags);
+	
+	/*
+		When the device transmission busy flag is on , no data
+		can be sent.  Unlock the irq and return EBUSY.
+	*/
+
+	if (c7000_check_busy(dev)) {
+		CPrintk(1, "c7000: c7000_xmit: c7000_check_busy returns true for base unit 0x%lx\n", dev->base_addr);
+		s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+		return(-EBUSY);
+	}
+
+	/*
+		Set the device transmission busy flag on atomically.
+	*/
+
+	if (c7000_ts_busy(TB_TX, dev)) {
+		CPrintk(1, "c7000: c7000_xmit: c7000_ts_busy returns true for base unit 0x%lx\n", dev->base_addr);
+		s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+		return(-EBUSY);
+	}
+
+	CPrintk(1, "c7000: c7000_xmit: set TB_TX for unit 0x%x\n", cup->devno);
+
+	/*
+		Obtain a free buffer.  If none are free then mark tbusy
+		with TB_NOBUFFER and return EBUSY.
+	*/
+
+	if ((buf = c7000_get_buffer(cup)) == NULL) {
+		CPrintk(1, "c7000: c7000_xmit: setting TB_NOBUFFER for base unit 0x%lx\n", dev->base_addr);
+		c7000_setbit_busy(TB_NOBUFFER, dev);
+		c7000_clearbit_busy(TB_TX, dev);
+		s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+		return(-EBUSY);
+	}
+
+	CPrintk(1, "c7000: c7000_xmit: Got buffer for unit 0x%x\n", cup->devno);
+
+	/*
+		Save the length of the skb data and then copy it to the
+		buffer.  Queue the buffer on the processing list.
+	*/
+
+	buf->len = skb->len;
+	memcpy(buf->data, skb->data, skb->len);
+	memset(buf->data + C7000_DATAL + C7000_READHDRL, '\0', C7000_READFFL);
+	pbuf = cup->proc_tail;
+	c7000_queue_buffer(cup, buf);
+
+	/*
+		Chain the buffer to the running channel program.
+	*/
+
+	if (test_bit(0, (void *)&cup->IO_active) && pbuf != NULL) {
+		c7000_bld_wrt_chpgm(cup, buf);
+		pbuf->ccws[2].cda = (__u32)virt_to_phys(&buf->ccws[0]);
+	}
+
+	/*
+		Free the socket buffer.
+	*/
+
+	dev_kfree_skb(skb);
+
+	/*
+		If the unit is not currently doing IO, build a channel
+		program and start the IO for the buffers on the processing
+		chain.
+	*/
+
+	if (test_and_set_bit(0, (void *)&cup->IO_active) == 0) {
+		CPrintk(1, "c7000: c7000_xmit: start IO for unit 0x%x\n", cup->devno);
+		c7000_bld_wrt_chain(cup);
+		parm = (__u32) cup;
+		cup->state = C7000_WRITE;
+
+		if ((rc = do_IO(cup->irq, &cup->proc_head->ccws[0], parm, 0xff, flags)) != 0) {
+			CPrintk(0, "c7000: c7000_xmit: do_IO failed with return code %d for unit 0x%x\n", rc, cup->devno);
+			c7000_error(cup->cntlp);
+			c7000_clearbit_busy(TB_TX, dev);
+			s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+			return(-EIO);
+		}
+
+		dev->trans_start = jiffies;
+		CPrintk(1, "c7000: c7000_xmit: do_IO succeeds for unit 0x%x\n", cup->devno);
+	}
+		
+	/*
+		If the free chain is now NULL, set the TB_NOBUFFER flag.
+	*/
+
+	if (cup->free == NULL) {
+		CPrintk(1, "c7000: c7000_xmit: setting TB_NOBUFFER for base unit 0x%lx\n", dev->base_addr);
+		c7000_setbit_busy(TB_NOBUFFER, dev);
+	}
+
+	c7000_clearbit_busy(TB_TX, dev);
+	s390irq_spin_unlock_irqrestore(cup->irq, saveflags);
+	CPrintk(1, "c7000: c7000_xmit: exits for unit 0x%x\n", cup->devno);
+	return(0);
+}
+
+/*
+	Handle an ioctl from a user process.
+*/
+
+static int
+c7000_ioctl(STRUCT_NET_DEVICE *dev, struct ifreq *ifr, int cmd)
+{
+	CPrintk(1, "c7000: c7000_ioctl: entered for base unit 0x%lx with cmd %d\n", dev->base_addr, cmd);
+	return(0);
+}
+
+/*
+	Analyze the interrupt status and return a value
+	that identifies the type.
+*/
+
+static enum c7000_rupt
+c7000_check_csw(devstat_t *devstat)
+{
+
+	/*
+		Check for channel detected conditions (except PCI).
+	*/
+
+	if ((devstat->cstat & ~SCHN_STAT_PCI) != 0) {
+		CPrintk(0, "c7000: c7000_check_csw: channel status 0x%x for unit 0x%x\n", devstat->cstat, devstat->devno);
+		return(C7000_CHANERR);
+	}
+
+	/*
+		Fast path the normal cases.
+	*/
+
+	if (devstat->dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+		return(C7000_NORMAL);
+
+	if (devstat->cstat == SCHN_STAT_PCI)
+		return(C7000_NORMAL);
+
+	/*
+		Check for exceptions.
+	*/
+
+	if (devstat->dstat & DEV_STAT_UNIT_CHECK) {
+		CPrintk(0, "c7000: c7000_check_csw: unit check for unit 0x%x, sense byte0 0x%2.2x\n", devstat->devno, devstat->ii.sense.data[0]);
+
+		if (devstat->ii.sense.data[0] == C7000_BOX_RESET)
+			return(C7000_UCK_RESET);
+		else
+			return(C7000_UCK);
+
+	} else if (devstat->dstat & DEV_STAT_UNIT_EXCEP) {
+		CPrintk(0, "c7000: c7000_check_csw: unit exception for unit 0x%x\n", devstat->devno);
+		return(C7000_UE);
+
+	} else if (devstat->dstat & DEV_STAT_ATTENTION) {
+		CPrintk(0, "c7000: c7000_check_csw: attention for unit 0x%x\n", devstat->devno);
+		return(C7000_ATTN);
+
+	} else if (devstat->dstat & DEV_STAT_BUSY) {
+		CPrintk(0, "c7000: c7000_check_csw: busy for unit 0x%x\n", devstat->devno);
+		return(C7000_BUSY);
+
+	} else {
+		CPrintk(0, "c7000: c7000_check_csw: channel status 0x%2.2x , device status 0x%2.2x, devstat flags 0x%8.8x for unit 0x%x\n",
+			devstat->cstat, devstat->dstat, devstat->flag, devstat->devno);
+		return(C7000_OTHER);
+	}
+
+	/* NOT REACHED */
+
+}
+
+/*
+	Retry the last CCW chain to the unit.
+*/
+
+static void
+c7000_retry_io(struct c7000_unit *cup)
+{
+	int	rc;
+	__u32	parm;
+	__u8	flags = 0x00;
+	ccw1_t	*ccwp;
+
+	if (++cup->retries > C7000_MAX_RETRIES) {
+		c7000_error(cup->cntlp);
+		CPrintk(0, "c7000: c7000_retry_io: retry IO for unit 0x%x exceeds maximum retry count\n", cup->devno);
+		return;
+	}
+
+	set_bit(0, (void *)&cup->IO_active);
+	parm = (__u32)cup;
+
+	if (cup->state == C7000_READ || cup->state == C7000_WRITE)
+		ccwp = &cup->proc_head->ccws[0];
+	else
+		ccwp = &cup->ccws[0];
+
+	if ((rc = do_IO(cup->irq, ccwp, parm, 0xff, flags)) != 0) {
+		CPrintk(0, "c7000: c7000_retry_io: can not retry IO for unit 0x%x, return code %d\n", cup->devno, rc);
+		clear_bit(0, (void *)&cup->IO_active);
+		c7000_error(cup->cntlp);
+	}
+
+	CPrintk(1, "c7000: c7000_retry_io: retry IO for unit 0x%x, retry count %d\n", cup->devno, cup->retries);
+	return;
+}
+
+/*
+	Process a read interrupt by scanning the list of buffers
+	for ones that have completed and queue them for the bottom
+	half to process.
+*/
+
+static void
+c7000_proc_rintr(struct c7000_unit *cup)
+{
+	struct	c7000_buffer	*buf;
+	struct	c7000_rd_header	*head;
+	int			num_read = 0;
+
+	while (cup->proc_head != NULL) {
+		head = (struct c7000_rd_header *)(cup->proc_head->data + C7000_DATAL);
+
+		/*
+			The flag byte in the read header will be set to
+			FLAG_FF when the buffer has been read.
+		*/
+
+		if (head->flag != FLAG_FF)
+			break;
+		
+		/*
+			Dequeue the buffer from the proc chain
+			and enqueue it on the bh chain for
+			the bh routine to process.
+		*/
+
+		buf = c7000_dequeue_buffer(cup);
+		c7000_queue_bh_buffer(cup, buf);
+		num_read++;
+	}
+
+	CPrintk(1, "c7000: c7000_proc_rintr: %d buffers read for unit 0x%x\n", num_read, cup->devno);
+	return;
+}
+
+/*
+	Process all completed buffers on the proc chain.
+	A buffer is completed if it's READFF flag is FLAG_FF.
+*/
+
+static int
+c7000_proc_wintr(struct c7000_unit *cup)
+{
+	struct	c7000_controller	*ccp = cup->cntlp;
+	struct	c7000_buffer		*buf;
+	int				num_write = 0;
+
+	if (cup->proc_head == NULL) {
+		CPrintk(0, "c7000: c7000_proc_wintr: unexpected NULL processing chain pointer for unit 0x%x\n", cup->devno);
+		return(num_write);
+	}
+
+	while (cup->proc_head != NULL) {
+
+		/*
+			Check if the buffer has completed.
+		*/
+
+		if (*(cup->proc_head->data + C7000_DATAL + C7000_READHDRL) != FLAG_FF)
+			break;
+
+		/*
+			Remove buffer from top of processing chain.
+			Place it on free list.
+		*/
+
+		buf = c7000_dequeue_buffer(cup);
+		c7000_release_buffer(cup, buf);
+		num_write++;
+
+		/*
+			Update transmitted packets statistic.
+		*/
+
+		ccp->stats.tx_packets++;
+	}
+
+	CPrintk(1, "c7000: c7000_proc_wintr: %d buffers written for unit 0x%x\n", num_write, cup->devno);
+	return(num_write);
+}
+
+/*
+	Interrupt handler.
+*/
+
+static void
+c7000_intr(int irq, void *initparm, struct pt_regs *regs)
+{
+	devstat_t			*devstat = ((devstat_t *) initparm);
+	struct	c7000_unit		*cup = NULL;
+	struct	c7000_controller	*ccp = NULL;
+	STRUCT_NET_DEVICE		*dev = NULL;
+	__u32				parm;
+	__u8				flags = 0x00;
+	int				rc;
+
+	/*
+		Discard unsolicited interrupts
+	*/
+
+	if (devstat->intparm == 0) {
+		CPrintk(0, "c7000: c7000_intr: unsolicited interrupt for device 0x%x, cstat = 0x%2.2x, dstat = 0x%2.2x, flag = 0x%8.8x\n",
+			devstat->devno, devstat->cstat, devstat->dstat, devstat->flag);
+		return;
+	}
+
+	/*
+		Obtain the c7000_unit structure pointer.
+	*/
+
+	cup = (struct c7000_unit *)(devstat->intparm);
+
+	/*
+		Obtain the c7000_controller structure and device structure
+		pointers.
+	*/
+
+	if (cup == NULL) {
+		CPrintk(0, "c7000: c7000_intr: c7000_unit pointer is NULL in devstat\n");
+		return;
+	}
+
+	ccp = cup->cntlp;
+
+	if (ccp == NULL) {
+		CPrintk(0, "c7000: c7000_intr: c7000_cntlp pointer is NULL in c7000_unit structure 0x%x for unit 0x%x\n", (int)cup, cup->devno);
+		return;
+	}
+
+	dev = ccp->dev;
+
+	if (dev == NULL) {
+		CPrintk(0, "c7000: c7000_intr: device pointer is NULL in c7000_controller structure 0x%x for unit 0x%x\n", (int)ccp, cup->devno);
+		return;
+	}
+
+	/*
+		Finite state machine (fsm) handling.
+	*/
+
+	CPrintk(1, "c7000: c7000_intr: entered with state %d flag 0x%8.8x for unit 0x%x\n", cup->state, devstat->flag, cup->devno);
+
+	switch(cup->state) {
+
+		/*
+			Not expected to be here when in INIT state.
+		*/
+
+		case C7000_INIT:
+
+			break;
+
+		/*
+			Enter state C7000_SID and wakeup the sleeping
+			process in c7000_open.
+		*/
+
+		case C7000_HALT:
+
+			if ((devstat->flag & DEVSTAT_FINAL_STATUS) == 0)
+				break;
+
+			cup->state = C7000_SID;
+			wake_up(&cup->wait);
+			break;
+		
+		/*
+			Enter state C7000_SYSVAL and wakeup the sleeping
+			process in c7000_open.
+		*/
+			
+		case C7000_SID:
+
+			if ((devstat->flag & DEVSTAT_FINAL_STATUS) == 0)
+				break;
+
+			if (c7000_check_csw(devstat) != 0) {
+				c7000_retry_io(cup);
+
+				if (cup->state == C7000_ERROR)
+					wake_up(&cup->wait);
+
+				break;
+			}
+
+			cup->retries = 0;
+			cup->state = C7000_SYSVAL;
+			wake_up(&cup->wait);
+			break;
+
+		/*
+			Wakeup the sleeping process in c7000_open.
+		*/
+
+		case C7000_SYSVAL:
+
+			if ((devstat->flag & DEVSTAT_FINAL_STATUS) == 0)
+				break;
+
+			if (c7000_check_csw(devstat) != 0) {
+				c7000_retry_io(cup);
+
+				if (cup->state == C7000_ERROR)
+					wake_up(&cup->wait);
+
+				break;
+			}
+
+			cup->retries = 0;
+			wake_up(&cup->wait);
+			break;
+
+		/*
+			Wakeup the sleeping process in c7000_open.
+		*/
+
+		case C7000_CONNECT:
+
+			if ((devstat->flag & DEVSTAT_FINAL_STATUS) == 0)
+				break;
+
+			if (c7000_check_csw(devstat) != 0) {
+				c7000_retry_io(cup);
+
+				if (cup->state == C7000_ERROR)
+					wake_up(&cup->wait);
+
+				break;
+			}
+
+			cup->retries = 0;
+			wake_up(&cup->wait);
+			break;
+
+		/*
+			Not expected to be entered here.
+		*/
+
+		case C7000_READY:
+			break;
+
+		/*
+			Process the data that was just read.
+		*/
+
+		case C7000_READ:
+
+			if ((devstat->flag & (DEVSTAT_PCI | DEVSTAT_FINAL_STATUS)) == 0)
+				break;
+
+			CPrintk(1, "c7000: c7000_intr: process read interrupt for unit 0x%x , devstat flag = 0x%8.8x\n", cup->devno, devstat->flag);
+
+			/*
+				Check for serious errors.
+			*/
+
+			if (c7000_check_csw(devstat) != 0) {
+				ccp->stats.rx_errors++;
+				c7000_error(cup->cntlp);
+				break;
+			}
+
+			/*
+				Build the bottom half buffer list.
+			*/
+
+			c7000_proc_rintr(cup);
+
+			/*
+				When final status is received clear
+				the IO active bit.
+			*/
+
+			if (devstat->flag & DEVSTAT_FINAL_STATUS) {
+				clear_bit(0, (void *)&cup->IO_active);
+			}
+
+			/*
+				If there are free buffers redrive the IO.
+			*/
+
+			if ((devstat->flag & DEVSTAT_FINAL_STATUS) &&
+			    (cup->free != NULL)) {
+				c7000_bld_read_chain(cup);
+				parm = (__u32)cup;
+				set_bit(0, (void *)&cup->IO_active);
+
+				if ((rc = do_IO(cup->irq, &cup->proc_head->ccws[0], parm, 0xff, flags)) != 0) {
+					clear_bit(0, (void *)&cup->IO_active);
+					CPrintk(0, "c7000: c7000_intr: do_IO failed with return code %d for unit 0x%x\n", rc, cup->devno);
+					c7000_error(cup->cntlp);
+					break;
+				}
+
+				CPrintk(1, "c7000: c7000_intr: started read io for unit 0x%x\n", cup->devno);
+			}
+
+			/*
+				Initiate bottom half routine to process
+				data that was read.
+			*/
+
+			if (test_and_set_bit(C7000_BH_ACTIVE, (void *)&cup->flag_a) == 0) {
+				queue_task(&cup->tq, &tq_immediate);
+				mark_bh(IMMEDIATE_BH);
+			}
+
+			break;
+
+		/*
+			Free the transmitted buffers and restart the channel
+			process (if necessary).
+		*/
+
+		case C7000_WRITE:
+
+			if ((devstat->flag & DEVSTAT_FINAL_STATUS) == 0)
+				break;
+
+			if (c7000_check_csw(devstat) != 0) {
+				ccp->stats.tx_errors++;
+				c7000_error(cup->cntlp);
+				break;
+			}
+
+			/*
+				If at least one buffer was freed, clear
+				the NOBUFFER indication.
+			*/
+
+			if (c7000_proc_wintr(cup) != 0) {
+				c7000_clearbit_busy(TB_NOBUFFER, dev);
+			}
+
+			/*
+				Restart the channel program if there are more
+				buffers on the processing chain.
+			*/
+
+			if (cup->proc_head != NULL) {
+				c7000_bld_wrt_chain(cup);
+				parm = (__u32)cup;
+				set_bit(0, (void *)&cup->IO_active);
+
+				if ((rc = do_IO(cup->irq, &cup->proc_head->ccws[0], parm, 0xff, flags)) != 0) {
+					CPrintk(0, "c7000: c7000_intr: do_IO failed with return code %d for unit 0x%x\n", rc, cup->devno);
+					clear_bit(0, (void *)&cup->IO_active);
+					c7000_error(cup->cntlp);
+					break;
+				}
+
+				dev->trans_start = jiffies;
+			} else {
+				clear_bit(0, (void *)&cup->IO_active);
+				cup->state = C7000_READY;
+			}
+
+			break;
+
+		/*
+			Disconnect message completed.  Wakeup the
+			sleeping process in c7000_stop.
+		*/
+
+		case C7000_DISC:
+			if ((devstat->flag & DEVSTAT_FINAL_STATUS) == 0)
+				break;
+
+			if (c7000_check_csw(devstat) != 0) {
+				c7000_retry_io(cup);
+
+				if (cup->state == C7000_ERROR)
+					wake_up(&cup->wait);
+
+				break;
+			}
+
+			cup->retries = 0;
+			wake_up(&cup->wait);
+			break;
+
+		/*
+			Subchannel is now halted.  Wakeup the sleeping
+			process in c7000_stop.  Set the state to C7000_STOPPED.
+		*/
+
+		case C7000_STOP:
+
+			cup->state = C7000_STOPPED;
+			wake_up(&cup->wait);
+			break;
+
+		/*
+			When in error state, stay there until the
+			interface is recycled.
+		*/
+
+		case C7000_ERROR:
+
+			break;
+
+		/*
+			Should not reach here
+		*/
+
+		default:
+			CPrintk(0, "c7000: c7000_intr: entered default case for unit 0x%x, state %d\n", cup->devno, cup->state);
+			break;
+
+	}
+
+	CPrintk(1, "c7000: c7000_intr: exited with state %d for unit 0x%x\n", cup->state, cup->devno);
+	return;
+}
+
+/*
+	Fill in system validation name padding it with blanks.
+*/
+
+static void
+c7000_fill_name(char *dst, char *src)
+{
+	char	*tmp = dst;
+	int	i;
+
+	for (i = 0; i < NAMLEN; i++, tmp++) 
+		*tmp = ' ';
+
+	for (i = 0; i < NAMLEN && *src != '\0'; i++)
+		*dst++ = *src++;
+
+	return;
+}
+
+/*
+	Initialization routine called when the device is registered.
+*/
+
+static int
+c7000_init(STRUCT_NET_DEVICE *dev)
+{
+	struct	c7000_controller	*ccp;
+	int				i;
+	int				unitaddr;
+	int				irq;
+
+	/*
+		Find the position of base_addr in the bases array.
+	*/
+
+	for (i = 0; i < MAX_C7000; i++) 
+		if (bases[i] == dev->base_addr)
+			break;
+
+	if (i == MAX_C7000)
+		return(-ENODEV);
+
+	/*
+		Make sure it is a C7000 type of device.
+	*/
+
+	if (c7000_check_devices(dev->base_addr) != 0) {
+		CPrintk(0, "c7000: c7000_init: base unit 0x%lx is not the right type\n", dev->base_addr);
+		return(-ENODEV);
+	}
+
+	/*
+		Initialize the device structure functions.
+		Note that ARP is not done on the CLAW interface.
+		There is no ethernet header.
+	*/
+
+	dev->mtu = C7000_DATAL;
+	dev->hard_header_len = 0;
+        dev->addr_len = 0;
+        dev->type = ARPHRD_SLIP;
+        dev->tx_queue_len = C7000_TXQUEUE_LEN;
+	dev->flags = IFF_NOARP;   
+	dev->open = c7000_open;
+	dev->stop = c7000_stop;
+	dev->set_config = c7000_config;
+	dev->hard_start_xmit = c7000_xmit;
+	dev->do_ioctl = c7000_ioctl;
+	dev->get_stats = c7000_stats;
+
+	/*
+		Allocate space for a private data structure.
+	*/
+
+	if ((ccp = dev->priv = kmalloc(sizeof(struct c7000_controller), GFP_KERNEL)) == NULL) {
+		CPrintk(0, "c7000: c7000_init: base unit 0x%lx can not be initialized\n", dev->base_addr);
+		return(-ENOMEM);
+	}
+
+	CPrintk(1, "c7000: c7000_init: allocated a c7000_controller structure at address 0x%x\n", (int)ccp);
+	memset(ccp, '\0', sizeof(struct c7000_controller));
+	ccp->dev = dev;
+	ccp->base_addr = dev->base_addr;
+
+	/*
+		Populate the system validation name with default values.
+	*/
+
+	c7000_fill_name(ccp->lappl, C7000_DFLT_LAPPL);
+	c7000_fill_name(ccp->lhost, C7000_DFLT_LHOST);
+	c7000_fill_name(ccp->uappl, C7000_DFLT_UAPPL);
+	c7000_fill_name(ccp->uhost, C7000_DFLT_UHOST);
+
+	/*
+		When values have been supplied, replace the prior defaults.
+	*/
+
+	if (i == 0) {
+
+		if (lappl0 != NULL)
+			c7000_fill_name(ccp->lappl, lappl0);
+
+		if (lhost0 != NULL)
+			c7000_fill_name(ccp->lhost, lhost0);
+
+		if (uappl0 != NULL)
+			c7000_fill_name(ccp->uappl, uappl0);
+
+		if (uhost0 != NULL)
+			c7000_fill_name(ccp->uhost, uhost0);
+
+	} else if (i == 1) {
+
+		if (lappl1 != NULL)
+			c7000_fill_name(ccp->lappl, lappl1);
+
+		if (lhost1 != NULL)
+			c7000_fill_name(ccp->lhost, lhost1);
+
+		if (uappl1 != NULL)
+			c7000_fill_name(ccp->uappl, uappl1);
+
+		if (uhost1 != NULL)
+			c7000_fill_name(ccp->uhost, uhost1);
+
+	} else if (i == 2) {
+
+		if (lappl2 != NULL)
+			c7000_fill_name(ccp->lappl, lappl2);
+
+		if (lhost2 != NULL)
+			c7000_fill_name(ccp->lhost, lhost2);
+
+		if (uappl2 != NULL)
+			c7000_fill_name(ccp->uappl, uappl2);
+
+		if (uhost2 != NULL)
+			c7000_fill_name(ccp->uhost, uhost2);
+
+	} else {
+
+		if (lappl3 != NULL)
+			c7000_fill_name(ccp->lappl, lappl3);
+
+		if (lhost3 != NULL)
+			c7000_fill_name(ccp->lhost, lhost3);
+
+		if (uappl3 != NULL)
+			c7000_fill_name(ccp->uappl, uappl3);
+
+		if (uhost3 != NULL)
+			c7000_fill_name(ccp->uhost, uhost3);
+
+	}
+
+	CPrintk(1, "c7000: c7000_init: lappl = %8.8s lhost = %8.8s uappl = %8.8s uhost = %8.8s for base unit 0x%x\n", ccp->lappl, ccp->lhost, ccp->uappl, ccp->uhost, ccp->base_addr);
+	ccp->version = 2;
+	ccp->linkid = 0;
+
+	/*
+		Initialize the fields in the embedded cunits
+		array.  This type of controller occupies a range
+		of three contiguous device numbers.
+	*/
+
+	for (i = 0; i < NUNITS; i++) {
+		unitaddr = dev->base_addr + i;
+
+		/*
+			Get the subchannel number.
+		*/
+
+		if ((irq = ccp->cunits[i].irq = get_irq_by_devno(unitaddr)) == -1) {
+			CPrintk(0, "c7000: c7000_init: can not get subchannel for unit 0x%x\n", unitaddr);
+			return(-ENODEV);
+		}
+
+		/*
+			Get control of the subchannel.
+		*/
+
+		if (request_irq(irq, c7000_intr, SA_INTERRUPT, dev->name, &ccp->cunits[i].devstat) != 0) {
+			CPrintk(0, "c7000: c7000_init: can not get control of subchannel 0x%x for unit 0x%x\n", irq, unitaddr);
+			return(-EBUSY);
+		}
+
+		CPrintk(1, "c7000: c7000_init: obtained control of subchannel 0x%x for unit 0x%x\n", irq, unitaddr);
+		ccp->cunits[i].devno = unitaddr;
+		ccp->cunits[i].IO_active = 0;
+		ccp->cunits[i].state = C7000_INIT;
+		ccp->cunits[i].cntlp = ccp;
+		CPrintk(1, "c7000: c7000_init: initialized unit 0x%x on subchannel 0x%x\n", unitaddr, irq);
+	}
+		
+	return(0);
+}
+
+/*
+	Probe for the Cisco 7000 unit base addresses.
+*/
+
+static void
+c7000_probe(void)
+{
+	s390_dev_info_t	d;
+	int		i;
+	int		j;
+	int		idx;
+
+	/*
+		Probe for up to MAX_C7000 devices.
+		Get the first irq into variable idx.
+	*/
+	
+	idx = get_irq_first();
+
+	for (j = 0; j < MAX_C7000; j++) {
+
+		if (idx < 0)
+			break;
+
+		/*
+			Continue scanning the irq's.  Variable idx
+			maintains the location from the prior scan.
+		*/
+
+		for (i = idx; i >= 0; i = get_irq_next(i)) {
+
+			/*
+				Ignore invalid irq's.
+			*/
+
+			if (get_dev_info_by_irq(i, &d) < 0)
+				continue;
+
+			/*
+				A Cisco 7000 is defined as a 3088 model
+				type 0x61.
+			*/
+
+			if (d.sid_data.cu_type == C7000_CU_TYPE &&
+			    d.sid_data.cu_model == C7000_CU_MODEL) {
+				CPrintk(0, "c7000_probe: unit probe found 0x%x\n", d.devno);
+				bases[j] = d.devno;
+
+				/*
+					Skip the write irq and setup idx
+					to probe for the next box.
+				*/
+
+				idx = get_irq_next(i + 1);
+				break;
+			}
+
+		}
+
+	}
+
+	return;
+}
+
+/*
+	Module loading.  Register each C7000 interface found via probing
+	or insmod command parameters.
+*/
+
+int
+init_module(void)
+{
+	int		result;
+	int		i;
+
+	for (i = 0 ; i < MAX_C7000; i++)
+		bases[i] = -1;
+
+	/*
+		Perform automatic detection provided it has not been disabled
+		by the noauto parameter. 
+	*/
+
+	if (noauto == 0)
+		c7000_probe();
+
+	/*
+		Populate bases array from the module basex parameters replacing
+		what probing found above.
+	*/
+
+	if (base0 != -1)
+		bases[0] = base0;
+
+	if (base1 != -1)
+		bases[1] = base1;
+
+	if (base2 != -1)
+		bases[2] = base2;
+
+	if (base3 != -1)
+		bases[3] = base3;
+
+	for (i = 0; i < MAX_C7000; i++) {
+
+		if (bases[i] == -1)
+			continue;
+		
+		/*
+			Initialize the device structure.
+		*/
+
+		memset(&c7000_devices[i], '\0', sizeof(STRUCT_NET_DEVICE));
+#ifdef NEWSTUFF
+		strcpy(c7000_devices[i].name, ifnames[i]);
+#else
+		c7000_devices[i].name = &ifnames[i][0];
+#endif
+		c7000_devices[i].base_addr = bases[i];
+		c7000_devices[i].init = c7000_init;
+
+		/*
+			Register the device.  This creates the interface
+			such as ci0.
+		*/
+
+		if ((result = register_netdev(&c7000_devices[i])) != 0)  {
+			CPrintk(0, "c7000: init_module: error %d registering base unit 0x%x\n",
+				result, bases[i]);
+			c7000_devices[i].base_addr = -1;
+		} else {
+			CPrintk(1, "c7000: init_module: registered base unit 0x%x on interface %s\n", bases[i], ifnames[i]);
+		}
+
+	}
+
+	CPrintk(0, "c7000: init_module: module loaded\n");
+	return(0);
+}
+
+/*
+	Module unloading.  Unregister the interface and free kernel
+	allocated memory.
+*/
+
+void
+cleanup_module(void)
+{
+	int				i;
+	int				j;
+	struct	c7000_controller	*ccp;
+
+	for (i = 0; i < MAX_C7000; i++) {
+
+		if (bases[i] == -1)
+			continue;
+
+		/*
+			If the device was registered, it must be unregistered
+			prior to unloading the module.
+		*/
+
+		if (c7000_devices[i].base_addr != -1) {
+
+			ccp = (struct c7000_controller *) c7000_devices[i].priv;
+
+			if (ccp != NULL) {
+				
+				for (j = 0; j < NUNITS ; j++) {
+					CPrintk(1, "c7000: clean_module: free subchannel 0x%x for unit 0x%x\n", ccp->cunits[j].irq, ccp->cunits[j].devno);
+					free_irq(ccp->cunits[j].irq, &ccp->cunits[j].devstat);
+				}
+
+				CPrintk(1, "c7000: clean_module: free a c7000_controller structure at address 0x%x\n", (int)ccp);
+				kfree(ccp);
+			}
+
+			unregister_netdev(&c7000_devices[i]);
+		}
+
+		bases[i] = -1;
+	}
+
+	CPrintk(0, "c7000: clean_module: module unloaded\n");
+	return;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)