Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > fc11cd6e1c513a17304da94a5390f3cd > files > 3160

kernel-2.6.18-194.11.1.el5.src.rpm

Date: Fri, 11 Aug 2006 18:07:45 +0200
From: Jan Glauber <jglauber@redhat.com>
Subject: [RHEL5 s390 patch] add ctcmpc driver

This patch adds the ctcmpc network driver which is currently not
upstream but is in RHEL4 and a customer requirement. The driver is
completely self-contained, that means it doesn't touch any other code.
It needs the CONFIG_MPC option to be set.

BZ 184608. The driver is tested by IBM and of course we will maintain
it if there is any problem since it isn't upstream.

Jan

-- 
jglauber@redhat.com
jang@de.ibm.com

diff -purN linux-2.6.17.s390/drivers/s390/net/ctcmpc.c linux-2.6.17.s390_xxx/drivers/s390/net/ctcmpc.c
--- linux-2.6.17.s390/drivers/s390/net/ctcmpc.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.17.s390_xxx/drivers/s390/net/ctcmpc.c	2006-08-11 14:02:43.000000000 +0200
@@ -0,0 +1,7339 @@
+/*
+ * CTC MPC/ ESCON network driver
+ *
+ * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Fritz Elfert (elfert@de.ibm.com, felfert@millenux.com)
+ * Fixes by : Jochen Röhrig (roehrig@de.ibm.com)
+ *            Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * Driver Model stuff by : Cornelia Huck <cornelia.huck@de.ibm.com>
+ * MPC additions: Belinda Thompson  (belindat@us.ibm.com)
+ *		  Andy Richter  (richtera@us.ibm.com)
+ *
+ * Documentation used:
+ *  - Principles of Operation (IBM doc#: SA22-7201-06)
+ *  - Common IO/-Device Commands and Self Description (IBM doc#: SA22-7204-02)
+ *  - Common IO/-Device Commands and Self Description (IBM doc#: SN22-5535)
+ *  - ESCON Channel-to-Channel Adapter (IBM doc#: SA22-7203-00)
+ *  - ESCON I/O Interface (IBM doc#: SA22-7202-029
+ *
+ * and the source of the original CTC driver by:
+ *  Dieter Wellerdiek (wel@de.ibm.com)
+ *  Martin Schwidefsky (schwidefsky@de.ibm.com)
+ *  Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
+ *  Jochen Röhrig (roehrig@de.ibm.com)
+ *
+ * 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.
+ *
+ */
+
+#undef DEBUG
+#undef DEBUGDATA
+#undef DEBUGCCW
+
+#include <linux/version.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/proc_fs.h>
+
+#include <linux/ip.h>
+#include <linux/if_arp.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/ctype.h>
+#include <linux/netdevice.h>
+#include <net/dst.h>
+
+#include <asm/io.h>
+#include <asm/ccwdev.h>
+#include <asm/ccwgroup.h>
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <asm/idals.h>
+
+#include "ctcmpc.h"
+#include "fsm.h"
+#include "cu3088.h"
+//#include "/usr/src/linux/drivers/s390/cio/css.h"
+
+MODULE_AUTHOR("(C) 2000 IBM Corp. by Fritz Elfert (felfert@millenux.com)");
+MODULE_DESCRIPTION("Linux for S/390 CTC/SNA MPC Driver");
+MODULE_LICENSE("GPL");
+
+static char *mpc = NULL;
+module_param(mpc, charp, 0);
+MODULE_PARM_DESC(mpc,
+                 "One or more definitions in the same format like the kernel"
+                 " param for mpc.\n"
+                 "E.g.: ctcmpc0:0x700:0x701:4:mpc1:0x702:0x703:4\n");
+
+#define ETH_P_SNA_DIX	       0x80D5
+/**
+CCW commands, used in this driver.
+ */
+#define CCW_CMD_WRITE		    0x01
+#define CCW_CMD_READ		    0x02
+#define CCW_CMD_NOOP		    0x03
+#define CCW_CMD_TIC                0x08
+#define CCW_CMD_SENSE_CMD	    0x14
+#define CCW_CMD_WRITE_CTL	    0x17
+#define CCW_CMD_SET_EXTENDED	    0xc3
+#define CCW_CMD_PREPARE	    0xe3
+
+#define CTC_PROTO_S390          0
+#define CTC_PROTO_LINUX         1
+#define CTC_PROTO_LINUX_TTY     2
+#define CTC_PROTO_OS390         3
+#define CTC_PROTO_MPC           4
+#define CTC_PROTO_MAX           4
+
+#define CTC_BUFSIZE_LIMIT       65535
+#define CTC_BUFSIZE_DEFAULT     32768
+#define MPC_BUFSIZE_DEFAULT	 65535
+
+#define CTC_TIMEOUT_5SEC        5000
+#define CTC_TIMEOUT_1SEC        1000
+#define CTC_BUSYWAIT_10SEC      10000
+
+#define CTC_INITIAL_BLOCKLEN    2
+
+#define READ			        0
+#define WRITE			        1
+
+#define CTC_ID_SIZE             BUS_ID_SIZE+3
+
+
+struct ctc_profile
+{
+        unsigned long maxmulti;
+        unsigned long maxcqueue;
+        unsigned long doios_single;
+        unsigned long doios_multi;
+        unsigned long txlen;
+        unsigned long tx_time;
+        struct timespec send_stamp;
+};
+
+/**
+ * Definition of an XID2
+ *
+ */
+#define ALLZEROS 0x0000000000000000
+
+#define XID_FM2         0x20
+#define XID2_0          0x00
+#define XID2_7          0x07
+#define XID2_MAX_READ   (2**16-1)
+#define XID2_WRITE_SIDE 0x04
+#define XID2_READ_SIDE	 0x05
+
+struct xid2
+{
+        __u8    xid2_type_id;
+        __u8    xid2_len;
+        __u32   xid2_adj_id;
+        __u8    xid2_rlen;
+        __u8    xid2_resv1;
+        __u8    xid2_flag1;
+        __u8    xid2_fmtt;
+        __u8    xid2_flag4;
+        __u16   xid2_resv2;
+        __u8    xid2_tgnum;
+        __u32   xid2_sender_id;
+        __u8    xid2_flag2;
+        __u8    xid2_option;
+        char  xid2_resv3[8];
+        __u16   xid2_resv4;
+        __u8    xid2_dlc_type;
+        __u16   xid2_resv5;
+        __u8    xid2_mpc_flag;
+        __u8    xid2_resv6;
+        __u16   xid2_buf_len;
+        char xid2_buffer[255-(sizeof(__u8)*13)-(sizeof(__u32)*2)-
+                         (sizeof(__u16)*4)-(sizeof(char)*8)];
+}__attribute__ ((packed));
+
+#define XID2_LENGTH  (sizeof(struct xid2))
+
+static const struct xid2 init_xid = {
+        xid2_type_id:   XID_FM2,
+        xid2_len:       0x45,
+        xid2_adj_id:    0,
+        xid2_rlen:      0x31,
+        xid2_resv1:     0,
+        xid2_flag1:     0,
+        xid2_fmtt:      0,
+        xid2_flag4:     0x80,
+        xid2_resv2:     0,
+        xid2_tgnum:     0,
+        xid2_sender_id: 0,
+        xid2_flag2:     0,
+        xid2_option:    XID2_0,
+        xid2_resv3:     "\x00",
+        xid2_resv4:     0,
+        xid2_dlc_type:      XID2_READ_SIDE,
+        xid2_resv5:     0,
+        xid2_mpc_flag:  0,
+        xid2_resv6:     0,
+        xid2_buf_len:   (MPC_BUFSIZE_DEFAULT - 35),
+};
+
+struct th_header
+{
+        __u8    th_seg;
+        __u8    th_ch_flag;
+#define TH_HAS_PDU	0xf0
+#define TH_IS_XID	0x01
+#define TH_SWEEP_REQ	0xfe
+#define TH_SWEEP_RESP	0xff
+        __u8    th_blk_flag;
+#define TH_DATA_IS_XID	0x80
+#define TH_RETRY	0x40
+#define TH_DISCONTACT	0xc0
+#define TH_SEG_BLK	0x20
+#define TH_LAST_SEG	0x10
+#define TH_PDU_PART	0x08
+        __u8    th_is_xid;      /* is 0x01 if this is XID  */
+        __u32   th_seq_num;
+}__attribute__ ((packed));
+
+static const struct th_header thnorm = {
+        th_seg:     0x00,
+        th_ch_flag: TH_IS_XID,
+        th_blk_flag:TH_DATA_IS_XID,
+        th_is_xid:  0x01,
+        th_seq_num: 0x00000000,
+};
+
+static const struct th_header thdummy = {
+        th_seg:     0x00,
+        th_ch_flag: 0x00,
+        th_blk_flag:TH_DATA_IS_XID,
+        th_is_xid:  0x01,
+        th_seq_num: 0x00000000,
+};
+
+
+struct th_addon
+{
+        __u32   th_last_seq;
+        __u32   th_resvd;
+}__attribute__ ((packed));
+
+struct th_sweep
+{
+        struct th_header        th;
+        struct th_addon sw;
+}__attribute__ ((packed));
+
+#define TH_HEADER_LENGTH (sizeof(struct th_header))
+#define TH_SWEEP_LENGTH (sizeof(struct th_sweep))
+
+#define PDU_LAST	0x80
+#define PDU_CNTL	0x40
+#define PDU_FIRST	0x20
+
+struct pdu
+{
+        __u32   pdu_offset;
+        __u8    pdu_flag;
+        __u8    pdu_proto;   /*  0x01 is APPN SNA  */
+        __u16   pdu_seq;
+}__attribute__ ((packed));
+#define PDU_HEADER_LENGTH  (sizeof(struct pdu))
+
+struct qllc
+{
+        __u8    qllc_address;
+#define QLLC_REQ        0xFF
+#define QLLC_RESP       0x00
+        __u8    qllc_commands;
+#define QLLC_DISCONNECT 0x53
+#define QLLC_UNSEQACK   0x73
+#define QLLC_SETMODE	0x93
+#define QLLC_EXCHID	0xBF
+}__attribute__ ((packed));
+
+
+static void ctcmpc_bh(unsigned long);
+/**
+ * Definition of one channel
+ */
+struct channel
+{
+        /**
+         * Pointer to next channel in list.
+         */
+        struct channel *next;
+        char id[CTC_ID_SIZE];
+        struct ccw_device *cdev;
+
+        /**
+          * Type of this channel.
+          * CTC/A or Escon for valid channels.
+         */
+        enum channel_types type;
+
+        /**
+          * Misc. flags. See CHANNEL_FLAGS_... below
+         */
+        __u32 flags;
+
+        /**
+         * The protocol of this channel
+         */
+        __u16 protocol;
+
+        /**
+         * I/O and irq related stuff
+         */
+        struct ccw1 *ccw;
+        struct irb *irb;
+
+        /**
+         * RX/TX buffer size
+         */
+        int max_bufsize;
+
+        /**
+         * Transmit/Receive buffer.
+         */
+        struct sk_buff *trans_skb;
+
+        /**
+         * Universal I/O queue.
+         */
+        struct sk_buff_head io_queue;
+        struct tasklet_struct ch_tasklet;
+
+        /**
+         * TX queue for collecting skb's during busy.
+         */
+        struct sk_buff_head collect_queue;
+
+        /**
+         * Amount of data in collect_queue.
+         */
+        int collect_len;
+
+        /**
+         * spinlock for collect_queue and collect_len
+         */
+        spinlock_t collect_lock;
+
+        /**
+         * Timer for detecting unresposive
+         * I/O operations.
+         */
+        fsm_timer timer;
+
+        /**
+         * Retry counter for misc. operations.
+         */
+        int retry;
+        /**
+         * spinlock for serializing inbound SNA Segments
+         */
+        spinlock_t          segment_lock;
+        /**
+          * SNA TH Seq Number
+          */
+        __u32 th_seq_num;
+        __u8 th_seg;
+        __u32 pdu_seq;
+
+        struct sk_buff *xid_skb;
+        char *xid_skb_data;
+        struct th_header *xid_th;
+        struct xid2 *xid;
+        char *xid_id;
+
+        struct th_header *rcvd_xid_th;
+        struct xid2 *rcvd_xid;
+        char *rcvd_xid_id;
+        __u8 in_mpcgroup;
+        fsm_timer sweep_timer;
+        struct sk_buff_head sweep_queue;
+        struct th_header *discontact_th;
+        struct tasklet_struct ch_disc_tasklet;
+        /**
+         * The finite state machine of this channel
+         */
+        fsm_instance *fsm;
+
+        /**
+         * The corresponding net_device this channel
+         * belongs to.
+         */
+        struct net_device *netdev;
+
+        struct ctc_profile prof;
+
+        unsigned char *trans_skb_data;
+
+        __u16 logflags;
+};
+
+#define CHANNEL_FLAGS_READ            0
+#define CHANNEL_FLAGS_WRITE           1
+#define CHANNEL_FLAGS_INUSE           2
+#define CHANNEL_FLAGS_BUFSIZE_CHANGED 4
+#define CHANNEL_FLAGS_FAILED          8
+#define CHANNEL_FLAGS_WAITIRQ        16
+#define CHANNEL_FLAGS_RWMASK 1
+#define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK)
+
+#define LOG_FLAG_ILLEGALPKT  1
+#define LOG_FLAG_ILLEGALSIZE 2
+#define LOG_FLAG_OVERRUN     4
+#define LOG_FLAG_NOMEM       8
+
+#define CTC_LOGLEVEL_INFO     1
+#define CTC_LOGLEVEL_NOTICE   2
+#define CTC_LOGLEVEL_WARN     4
+#define CTC_LOGLEVEL_EMERG    8
+#define CTC_LOGLEVEL_ERR     16
+#define CTC_LOGLEVEL_DEBUG   32
+#define CTC_LOGLEVEL_CRIT    64
+
+#define CTC_LOGLEVEL_DEFAULT \
+(CTC_LOGLEVEL_INFO | CTC_LOGLEVEL_NOTICE | CTC_LOGLEVEL_WARN | CTC_LOGLEVEL_CRIT)
+
+#define CTC_LOGLEVEL_MAX     ((CTC_LOGLEVEL_CRIT<<1)-1)
+
+static int loglevel = CTC_LOGLEVEL_DEFAULT;
+
+#define ctcmpc_pr_debug(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_DEBUG) printk(KERN_DEBUG fmt,##arg); } while(0)
+
+#define ctcmpc_pr_info(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_INFO) printk(KERN_INFO fmt,##arg); } while(0)
+
+#define ctcmpc_pr_notice(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_NOTICE) printk(KERN_NOTICE fmt,##arg); } while(0)
+
+#define ctcmpc_pr_warn(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_WARN) printk(KERN_WARNING fmt,##arg); } while(0)
+
+#define ctcmpc_pr_emerg(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_EMERG) printk(KERN_EMERG fmt,##arg); } while(0)
+
+#define ctcmpc_pr_err(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_ERR) printk(KERN_ERR fmt,##arg); } while(0)
+
+#define ctcmpc_pr_crit(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_CRIT) printk(KERN_CRIT fmt,##arg); } while(0)
+
+#define ctcmpc_pr_debugdata(fmt, arg...) \
+do { if(loglevel & CTC_LOGLEVEL_DEBUG) printk(KERN_DEBUG fmt,##arg); } while(0)
+/**
+ * Linked list of all detected channels.
+ */
+static struct channel *channels = NULL;
+
+/***
+  * Definition of one MPC group
+  */
+
+#define MAX_MPCGCHAN                    10
+#define MPC_XID_TIMEOUT_VALUE           10000
+#define MPC_CHANNEL_TIMEOUT_1SEC        1000
+#define MPC_CHANNEL_ADD                 0
+#define MPC_CHANNEL_REMOVE              1
+#define MPC_CHANNEL_ATTN                2
+#define XSIDE                           1
+#define YSIDE                           0
+
+
+
+struct mpcg_info
+{
+        struct sk_buff      *skb;
+        struct channel      *ch;
+        struct xid2         *xid;
+        struct th_sweep     *sweep;
+        struct th_header    *th;
+};
+
+struct mpc_group
+{
+        struct tasklet_struct mpc_tasklet;
+        struct tasklet_struct mpc_tasklet2;
+        int     changed_side;
+        int     saved_state;
+        int     channels_terminating;
+        int     out_of_sequence;
+        int     flow_off_called;
+        int     port_num;
+        int     port_persist;
+        int     alloc_called;
+        __u32   xid2_adj_id;
+        __u8    xid2_tgnum;
+        __u32   xid2_sender_id;
+        int     num_channel_paths;
+        int     active_channels[2];
+        __u16   group_max_buflen;
+        int     outstanding_xid2;
+        int     outstanding_xid7;
+        int     outstanding_xid7_p2;
+        int     sweep_req_pend_num;
+        int     sweep_rsp_pend_num;
+        sk_buff *xid_skb;
+        char    *xid_skb_data;
+        struct th_header *xid_th;
+        struct xid2    *xid;
+        char    *xid_id;
+        struct th_header *rcvd_xid_th;
+        sk_buff *rcvd_xid_skb;
+        char    *rcvd_xid_data;
+        __u8    in_sweep;
+        __u8    roll;
+        struct xid2    *saved_xid2;
+        callbacktypei2  allochanfunc;
+        int     allocchan_callback_retries;
+        callbacktypei3  estconnfunc;
+        int     estconn_callback_retries;
+        int     estconn_called;
+        int     xidnogood;
+        int     send_qllc_disc;
+        fsm_timer timer;
+        fsm_instance    *fsm; /* group xid fsm */
+};
+struct ctc_priv
+{
+        struct net_device_stats stats;
+        unsigned long tbusy;
+        /**The MPC group struct of this interface
+        */
+        struct mpc_group *mpcg;
+        struct xid2            *xid;
+        /**
+         * The finite state machine of this interface.
+         */
+        fsm_instance *fsm;
+        /**
+         * The protocol of this device
+         */
+        __u16 protocol;
+        /**
+         * Timer for restarting after I/O Errors
+         */
+        fsm_timer               restart_timer;
+        struct channel *channel[2];
+};
+
+//static void dumpit (char *buf, int len);
+static void mpc_action_send_discontact(unsigned long);
+/**
+ * Dummy NOP action for statemachines
+ */
+static void
+fsm_action_nop(fsm_instance * fi, int event, void *arg)
+{
+}
+
+/**
+ * Compatibility macros for busy handling
+ * of network devices.
+ */
+static __inline__ void
+ctcmpc_clear_busy(struct net_device *dev)
+{
+        if(((struct ctc_priv *)dev->priv)->mpcg->in_sweep == 0)
+        {
+                clear_bit(0, &(((struct ctc_priv *)dev->priv)->tbusy));
+                netif_wake_queue(dev);
+        }
+}
+
+static __inline__ int
+ctcmpc_test_and_set_busy(struct net_device *dev)
+{
+        netif_stop_queue(dev);
+        return test_and_set_bit(0, &(((struct ctc_priv *)dev->priv)->tbusy));
+}
+
+static __inline__ int ctcmpc_checkalloc_buffer(struct channel *,int);
+static void ctcmpc_purge_skb_queue(struct sk_buff_head *);
+/**
+ * Print Banner.
+ */
+static void
+print_banner(void)
+{
+        static int printed = 0;
+
+        if(printed)
+                return;
+        printk(KERN_INFO "CTC MPC driver Version initialized");
+        printed = 1;
+}
+
+static inline int
+gfp_type(void)
+{
+        return in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
+}
+
+/**
+ * Return type of a detected device.
+ */
+static enum channel_types
+get_channel_type(struct ccw_device_id *id)
+{
+        enum channel_types type = (enum channel_types) id->driver_info;
+
+        if(type == channel_type_ficon)
+                type = channel_type_escon;
+
+        return type;
+}
+
+/**
+ * States of the interface statemachine.
+ */
+enum dev_states
+{
+        DEV_STATE_STOPPED,
+        DEV_STATE_STARTWAIT_RXTX,
+        DEV_STATE_STARTWAIT_RX,
+        DEV_STATE_STARTWAIT_TX,
+        DEV_STATE_STOPWAIT_RXTX,
+        DEV_STATE_STOPWAIT_RX,
+        DEV_STATE_STOPWAIT_TX,
+        DEV_STATE_RUNNING,
+        /**
+         * MUST be always the last element!!
+         */
+        NR_DEV_STATES
+};
+
+static const char *dev_state_names[] = {
+        "Stopped",
+        "StartWait RXTX",
+        "StartWait RX",
+        "StartWait TX",
+        "StopWait RXTX",
+        "StopWait RX",
+        "StopWait TX",
+        "Running",
+};
+
+/**
+ * Events of the interface statemachine.
+ */
+enum dev_events
+{
+        DEV_EVENT_START,
+        DEV_EVENT_STOP,
+        DEV_EVENT_RXUP,
+        DEV_EVENT_TXUP,
+        DEV_EVENT_RXDOWN,
+        DEV_EVENT_TXDOWN,
+        DEV_EVENT_RESTART,
+        /**
+         * MUST be always the last element!!
+         */
+        NR_DEV_EVENTS
+};
+
+static const char *dev_event_names[] = {
+        "Start",
+        "Stop",
+        "RX up",
+        "TX up",
+        "RX down",
+        "TX down",
+        "Restart",
+};
+
+/**
+ * Events of the channel statemachine
+ */
+enum ch_events
+{
+        /**
+         * Events, representing return code of
+         * I/O operations (ccw_device_start, ccw_device_halt et al.)
+         */
+        CH_EVENT_IO_SUCCESS,
+        CH_EVENT_IO_EBUSY,
+        CH_EVENT_IO_ENODEV,
+        CH_EVENT_IO_EIO,
+        CH_EVENT_IO_UNKNOWN,
+
+        CH_EVENT_ATTNBUSY,
+        CH_EVENT_ATTN,
+        CH_EVENT_BUSY,
+
+        /**
+         * Events, representing unit-check
+         */
+        CH_EVENT_UC_RCRESET,
+        CH_EVENT_UC_RSRESET,
+        CH_EVENT_UC_TXTIMEOUT,
+        CH_EVENT_UC_TXPARITY,
+        CH_EVENT_UC_HWFAIL,
+        CH_EVENT_UC_RXPARITY,
+        CH_EVENT_UC_ZERO,
+        CH_EVENT_UC_UNKNOWN,
+
+        /**
+         * Events, representing subchannel-check
+         */
+        CH_EVENT_SC_UNKNOWN,
+
+        /**
+         * Events, representing machine checks
+         */
+        CH_EVENT_MC_FAIL,
+        CH_EVENT_MC_GOOD,
+
+        /**
+         * Event, representing normal IRQ
+         */
+        CH_EVENT_IRQ,
+        CH_EVENT_FINSTAT,
+
+        /**
+         * Event, representing timer expiry.
+         */
+        CH_EVENT_TIMER,
+
+        /**
+         * Events, representing commands from upper levels.
+         */
+        CH_EVENT_START,
+        CH_EVENT_STOP,
+        CH_EVENT_SEND_XID,
+
+        CH_EVENT_RSWEEP1_TIMER,
+        /**
+         * MUST be always the last element!!
+         */
+        NR_CH_EVENTS,
+};
+
+static const char *ch_event_names[] = {
+        "ccw_device success",
+        "ccw_device busy",
+        "ccw_device enodev",
+        "ccw_device ioerr",
+        "ccw_device unknown",
+
+        "Status ATTN & BUSY",
+        "Status ATTN",
+        "Status BUSY",
+
+        "Unit check remote reset",
+        "Unit check remote system reset",
+        "Unit check TX timeout",
+        "Unit check TX parity",
+        "Unit check Hardware failure",
+        "Unit check RX parity",
+        "Unit check ZERO",
+        "Unit check Unknown",
+
+        "SubChannel check Unknown",
+
+        "Machine check failure",
+        "Machine check operational",
+
+        "IRQ normal",
+        "IRQ final",
+
+        "Timer",
+
+        "Start",
+        "Stop",
+        "XID Exchange",
+        "MPC Group Sweep Timer",
+};
+
+/**
+ * States of the channel statemachine.
+ */
+enum ch_states
+{
+        /**
+         * Channel not assigned to any device,
+         * initial state, direction invalid
+         */
+        CH_STATE_IDLE,
+
+        /**
+         * Channel assigned but not operating
+         */
+        CH_STATE_STOPPED,
+        CH_STATE_STARTWAIT,
+        CH_STATE_STARTRETRY,
+        CH_STATE_SETUPWAIT,
+        CH_STATE_RXINIT,
+        CH_STATE_TXINIT,
+        CH_STATE_RX,
+        CH_STATE_TX,
+        CH_STATE_RXIDLE,
+        CH_STATE_TXIDLE,
+        CH_STATE_RXERR,
+        CH_STATE_TXERR,
+        CH_STATE_TERM,
+        CH_STATE_DTERM,
+        CH_STATE_NOTOP,
+        CH_XID0_PENDING,
+        CH_XID0_INPROGRESS,
+        CH_XID7_PENDING,
+        CH_XID7_PENDING1,
+        CH_XID7_PENDING2,
+        CH_XID7_PENDING3,
+        CH_XID7_PENDING4,
+
+        /**
+         * MUST be always the last element!!
+         */
+        NR_CH_STATES,
+};
+
+static const char *ch_state_names[] = {
+        "Idle",
+        "Stopped",
+        "StartWait",
+        "StartRetry",
+        "SetupWait",
+        "RX init",
+        "TX init",
+        "RX",
+        "TX",
+        "RX idle",
+        "TX idle",
+        "RX error",
+        "TX error",
+        "Terminating",
+        "Restarting",
+        "Not operational",
+        "Pending XID0 Start",
+        "In XID0 Negotiations ",
+        "Pending XID7 P1 Start",
+        "Active XID7 P1 Exchange ",
+        "Pending XID7 P2 Start ",
+        "Active XID7 P2 Exchange ",
+        "XID7 Complete - Pending READY ",
+};
+
+static int transmit_skb(struct channel *, struct sk_buff *);
+
+#ifdef DEBUGDATA
+/*-------------------------------------------------------------------*
+* Dump buffer format                                                 *
+*                                                                    *
+*--------------------------------------------------------------------*/
+static void
+dumpit(char* buf, int len)
+{
+
+        __u32      ct, sw, rm, dup;
+        char       *ptr, *rptr;
+        char       tbuf[82], tdup[82];
+        #if (UTS_MACHINE == s390x)
+        char       addr[22];
+        #else
+        char       addr[12];
+        #endif
+        char       boff[12];
+        char       bhex[82], duphex[82];
+        char       basc[40];
+
+        sw  = 0;
+        rptr =ptr=buf;
+        rm  = 16;
+        duphex[0]  = 0x00;
+        dup = 0;
+
+        for( ct=0; ct < len; ct++, ptr++, rptr++ )
+        {
+                if(sw == 0)
+                {
+        #if (UTS_MACHINE == s390x)
+                        sprintf(addr, "%16.16lx",(unsigned long)rptr);
+        #else
+                        sprintf(addr, "%8.8X",(__u32)rptr);
+        #endif
+                        sprintf(boff, "%4.4X", (__u32)ct);
+                        bhex[0] = '\0';
+                        basc[0] = '\0';
+                }
+                if((sw == 4) || (sw == 12))
+                {
+                        strcat(bhex, " ");
+                }
+                if(sw == 8)
+                {
+                        strcat(bhex, "  ");
+                }
+        #if (UTS_MACHINE == s390x)
+                sprintf(tbuf,"%2.2lX", (unsigned long)*ptr);
+        #else
+                sprintf(tbuf,"%2.2X", (__u32)*ptr);
+        #endif
+                tbuf[2] = '\0';
+                strcat(bhex, tbuf);
+                if((0!=isprint(*ptr)) && (*ptr >= 0x20))
+                {
+                        basc[sw] = *ptr;
+                } else
+                {
+                        basc[sw] = '.';
+                }
+                basc[sw+1] = '\0';
+                sw++;
+                rm--;
+                if(sw==16)
+                {
+                        if((strcmp(duphex, bhex)) !=0)
+                        {
+                                if(dup !=0)
+                                {
+                                        sprintf(tdup,"Duplicate as above "
+                                                "to %s", addr);
+                                        printk( KERN_INFO "               "
+                                                "     --- %s ---\n",tdup);
+                                }
+                                printk( KERN_INFO "   %s (+%s) : %s  [%s]\n",
+                                        addr, boff, bhex, basc);
+                                dup = 0;
+                                strcpy(duphex, bhex);
+                        } else
+                        {
+                                dup++;
+                        }
+                        sw = 0;
+                        rm = 16;
+                }
+        }  /* endfor */
+
+        if(sw != 0)
+        {
+                for( ; rm > 0; rm--, sw++ )
+                {
+                        if((sw==4) || (sw==12)) strcat(bhex, " ");
+                        if(sw==8)               strcat(bhex, "  ");
+                        strcat(bhex, "  ");
+                        strcat(basc, " ");
+                }
+                if(dup !=0)
+                {
+                        sprintf(tdup,"Duplicate as above to %s", addr);
+                        printk( KERN_INFO "               "
+                                "     --- %s ---\n",tdup);
+                }
+                printk( KERN_INFO "   %s (+%s) : %s  [%s]\n",
+                        addr, boff, bhex, basc);
+        } else
+        {
+                if(dup >=1)
+                {
+                        sprintf(tdup,"Duplicate as above to %s", addr);
+                        printk( KERN_INFO "               "
+                                "     --- %s ---\n",tdup);
+                }
+                if(dup !=0)
+                {
+                        printk( KERN_INFO "   %s (+%s) : %s  [%s]\n",
+                                addr, boff, bhex, basc);
+                }
+        }
+
+        return;
+
+}   /*   end of dumpit  */
+#else
+static void
+dumpit(char* buf, int len)
+{
+}
+#endif
+#ifdef DEBUGDATA
+/**
+ * Dump header and first 16 bytes of an sk_buff for debugging purposes.
+ *
+ * @param skb    The sk_buff to dump.
+ * @param offset Offset relative to skb-data, where to start the dump.
+ */
+static void
+ctcmpc_dump_skb(struct sk_buff *skb, int offset)
+{
+        unsigned char *p = skb->data;
+        struct th_header *header;
+        struct pdu *pheader;
+        int bl = skb->len;
+        int i;
+
+        if(p == NULL) return;
+        p += offset;
+        header = (struct th_header *)p;
+
+        printk(KERN_INFO "dump:\n");
+        printk(KERN_INFO "skb len=%d \n", skb->len);
+        if(skb->len > 2)
+        {
+                switch(header->th_ch_flag)
+                {
+                        case TH_HAS_PDU:
+                                break;
+                        case 0x00:
+                        case TH_IS_XID:
+                                if((header->th_blk_flag == TH_DATA_IS_XID) &&
+                                   (header->th_is_xid == 0x01) )
+                                        goto dumpth;
+                        case TH_SWEEP_REQ:
+                                goto dumpth;
+                        case TH_SWEEP_RESP:
+                                goto dumpth;
+                        default:
+                                break;
+
+                }
+
+                pheader = (struct pdu *)p;
+                printk(KERN_INFO "pdu->offset: %d hex: %04x\n",
+                       pheader->pdu_offset,pheader->pdu_offset);
+                printk(KERN_INFO "pdu->flag  : %02x\n",pheader->pdu_flag);
+                printk(KERN_INFO "pdu->proto : %02x\n",pheader->pdu_proto);
+                printk(KERN_INFO "pdu->seq   : %02x\n",pheader->pdu_seq);
+                goto dumpdata;
+
+                dumpth:
+                printk(KERN_INFO "th->seg     : %02x\n", header->th_seg);
+                printk(KERN_INFO "th->ch      : %02x\n", header->th_ch_flag);
+                printk(KERN_INFO "th->blk_flag: %02x\n", header->th_blk_flag);
+                printk(KERN_INFO "th->type    : %s\n",
+                       (header->th_is_xid) ? "DATA" : "XID");
+                printk(KERN_INFO "th->seqnum  : %04x\n", header->th_seq_num);
+
+        }  /* only dump the data if the length is not greater than 2 */
+        dumpdata:
+        if(bl > 32)
+                bl = 32;
+        printk(KERN_INFO "data: ");
+        for(i = 0; i < bl; i++)
+                printk("%02x%s", *p++, (i % 16) ? " " : "\n<7>");
+        printk("\n");
+}
+#else
+static inline void
+ctcmpc_dump_skb(struct sk_buff *skb, int offset)
+{
+}
+#endif
+
+
+static int ctcmpc_open(struct net_device *);
+static void ctcmpc_ch_action_rxidle(fsm_instance *fi, int event, void *arg);
+static void ctcmpc_ch_action_txidle(fsm_instance *fi, int event, void *arg);
+static void inline ccw_check_return_code(struct channel *,int ,char *);
+
+
+/*
+      ctc_mpc_alloc_channel
+      Device Initialization :
+            ACTPATH  driven IO operations
+*/
+int
+ctc_mpc_alloc_channel(int port_num,callbacktypei2  callback)
+{
+        char device[20];
+        char *devnam = "mpc";
+        struct net_device *dev = NULL;
+        struct mpc_group *grpptr;
+        struct ctc_priv *privptr;
+
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+
+        sprintf(device, "%s%i",devnam,port_num);
+        dev = __dev_get_by_name(device);
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "ctc_mpc_alloc_channel %s dev=NULL\n",device);
+                return(1);
+        }
+
+
+        privptr = (struct ctc_priv *)dev->priv;
+        grpptr = privptr->mpcg;
+        if(!grpptr)
+                return(1);
+
+        grpptr->allochanfunc = callback;
+        grpptr->port_num = port_num;
+        grpptr->port_persist = 1;
+
+        ctcmpc_pr_debug("ctcmpc: %s called for device %s state=%s\n",
+                       __FUNCTION__,
+                       dev->name,
+                       fsm_getstate_str(grpptr->fsm));
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_INOP:
+                        /* Group is in the process of terminating */
+                        grpptr->alloc_called = 1;
+                        break;
+                case MPCG_STATE_RESET:
+                        /* MPC Group will transition to state             */
+                        /* MPCG_STATE_XID2INITW iff the minimum number    */
+                        /* of 1 read and 1 write channel have successfully*/
+                        /* activated                                      */
+                        /*fsm_newstate(grpptr->fsm, MPCG_STATE_XID2INITW);*/
+                        if(callback)
+                                grpptr->send_qllc_disc = 1;
+                case MPCG_STATE_XID0IOWAIT:
+                        fsm_deltimer(&grpptr->timer);
+                        grpptr->outstanding_xid2 = 0;
+                        grpptr->outstanding_xid7 = 0;
+                        grpptr->outstanding_xid7_p2 = 0;
+                        grpptr->saved_xid2 = NULL;
+                        if(callback)
+                                ctcmpc_open(dev);
+                        fsm_event(((struct ctc_priv *)dev->priv)->fsm,
+                                  DEV_EVENT_START, dev);
+                        break;;
+                case MPCG_STATE_READY:
+                        /* XID exchanges completed after PORT was activated */
+                        /* Link station already active                      */
+                        /* Maybe timing issue...retry callback              */
+                        grpptr->allocchan_callback_retries++;
+                        if(grpptr->allocchan_callback_retries < 4)
+                        {
+                                if(grpptr->allochanfunc)
+                                        grpptr->allochanfunc(grpptr->port_num,
+                                                      grpptr->group_max_buflen);
+                        } else
+                        {
+                                /* there are problems...bail out            */
+                                /* there may be a state mismatch so restart */
+                                grpptr->port_persist = 1;
+                                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                                grpptr->allocchan_callback_retries = 0;
+                        }
+                        break;
+                default:
+                        return(0);
+
+        }
+
+
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+        return(0);
+}
+
+
+void
+ctc_mpc_establish_connectivity(int port_num, callbacktypei3 callback)
+{
+
+        char device[20];
+        char *devnam = "mpc";
+        struct net_device *dev = NULL;
+        struct mpc_group *grpptr;
+        struct ctc_priv *privptr;
+        struct channel *rch,*wch;
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+
+        sprintf(device,"%s%i",devnam,port_num);
+        dev = __dev_get_by_name(device);
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "ctc_mpc_establish_connectivity %s dev=NULL\n",
+                       device);
+                return;
+        }
+        privptr = (struct ctc_priv *)dev->priv;
+        rch = privptr->channel[READ];
+        wch = privptr->channel[WRITE];
+
+        grpptr = privptr->mpcg;
+
+        ctcmpc_pr_debug("ctcmpc: %s() called for device %s state=%s\n",
+                       __FUNCTION__,
+                       dev->name,
+                       fsm_getstate_str(grpptr->fsm));
+
+        grpptr->estconnfunc = callback;
+        grpptr->port_num = port_num;
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_READY:
+                        /* XID exchanges completed after PORT was activated */
+                        /* Link station already active                      */
+                        /* Maybe timing issue...retry callback              */
+                        fsm_deltimer(&grpptr->timer);
+                        grpptr->estconn_callback_retries++;
+                        if(grpptr->estconn_callback_retries < 4)
+                        {
+                                if(grpptr->estconnfunc)
+                                {
+                                        grpptr->estconnfunc(grpptr->port_num,0,
+                                                      grpptr->group_max_buflen);
+                                        grpptr->estconnfunc = NULL;
+                                }
+                        } else
+                        {
+                                /* there are problems...bail out         */
+                                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                                grpptr->estconn_callback_retries = 0;
+                        }
+                        break;
+                case MPCG_STATE_INOP:
+                case MPCG_STATE_RESET:
+                        /* MPC Group is not ready to start XID - min num of */
+                        /* 1 read and 1 write channel have not been acquired*/
+                        printk(KERN_WARNING "ctcmpc: %s() REJECTED ACTIVE XID"
+                               " Request - Channel Pair is not Active\n",
+                               __FUNCTION__);
+                        if(grpptr->estconnfunc)
+                        {
+                                grpptr->estconnfunc(grpptr->port_num,-1,0);
+                                grpptr->estconnfunc = NULL;
+                        }
+                        break;
+                case MPCG_STATE_XID2INITW:
+                        /* alloc channel was called but no XID exchange    */
+                        /* has occurred. initiate xside XID exchange       */
+                        /* make sure yside XID0 processing has not started */
+                        if((fsm_getstate(rch->fsm) > CH_XID0_PENDING) ||
+                           (fsm_getstate(wch->fsm) > CH_XID0_PENDING))
+                        {
+                                printk(KERN_WARNING "mpc: %s() ABORT ACTIVE XID"
+                                       " Request- PASSIVE XID in process\n"
+                                       , __FUNCTION__);
+                                break;
+                        }
+                        grpptr->send_qllc_disc = 1;
+                        fsm_newstate(grpptr->fsm, MPCG_STATE_XID0IOWAIT);
+                        fsm_deltimer(&grpptr->timer);
+                        fsm_addtimer(&grpptr->timer,
+                                     MPC_XID_TIMEOUT_VALUE,
+                                     MPCG_EVENT_TIMER, dev);
+                        grpptr->outstanding_xid7 = 0;
+                        grpptr->outstanding_xid7_p2 = 0;
+                        grpptr->saved_xid2 = NULL;
+                        if((rch->in_mpcgroup) &&
+                           (fsm_getstate(rch->fsm) == CH_XID0_PENDING))
+                                fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, rch);
+                        else
+                        {
+                                printk(KERN_WARNING "mpc: %s() Unable to start"
+                                       " ACTIVE XID0 on read channel\n",
+                                       __FUNCTION__);
+                                if(grpptr->estconnfunc)
+                                {
+                                        grpptr->estconnfunc(grpptr->port_num,
+                                                            -1,0);
+                                        grpptr->estconnfunc = NULL;
+                                }
+                                fsm_deltimer(&grpptr->timer);
+                                goto done;
+                        }
+                        if((wch->in_mpcgroup) &&
+                           (fsm_getstate(wch->fsm) == CH_XID0_PENDING))
+                                fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, wch);
+                        else
+                        {
+                                printk(KERN_WARNING "mpc: %s() Unable to start"
+                                       " ACTIVE XID0 on write channel\n",
+                                       __FUNCTION__);
+                                if(grpptr->estconnfunc)
+                                {
+                                        grpptr->estconnfunc(grpptr->port_num,
+                                                            -1,0);
+                                        grpptr->estconnfunc = NULL;
+                                }
+                                fsm_deltimer(&grpptr->timer);
+                                goto done;
+
+                        }
+                        break;
+                case MPCG_STATE_XID0IOWAIT:
+                        /* already in active XID negotiations */
+                default:
+                        break;
+        }
+
+        done:
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+        return;
+}
+
+static int ctcmpc_close(struct net_device *);
+
+void
+ctc_mpc_dealloc_ch(int port_num)
+{
+        struct net_device *dev;
+        char device[20];
+        char *devnam = "mpc";
+        struct ctc_priv *privptr;
+        struct mpc_group *grpptr;
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+        sprintf(device,"%s%i",devnam,port_num);
+        dev = __dev_get_by_name(device);
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s() %s dev=NULL\n",__FUNCTION__,device);
+                goto done;
+        }
+
+        ctcmpc_pr_debug("ctcmpc:%s %s() called for device %s refcount=%d\n",
+                       dev->name,
+                       __FUNCTION__,
+                       dev->name,
+                       atomic_read(&dev->refcnt));
+
+        privptr  = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "%s() %s privptr=NULL\n",__FUNCTION__,device);
+                goto done;
+        }
+        fsm_deltimer(&privptr->restart_timer);
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "%s() %s dev=NULL\n",__FUNCTION__,device);
+                goto done;
+        }
+        grpptr->channels_terminating = 0;
+
+        fsm_deltimer(&grpptr->timer);
+
+        grpptr->allochanfunc = NULL;
+        grpptr->estconnfunc = NULL;
+        grpptr->port_persist = 0;
+        grpptr->send_qllc_disc = 0;
+        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+
+        ctcmpc_close(dev);
+        done:
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+        return;
+}
+
+void
+ctc_mpc_flow_control(int port_num,int flowc)
+{
+        char device[20];
+        char *devnam = "mpc";
+        struct ctc_priv *privptr;
+        struct mpc_group *grpptr;
+        struct net_device *dev;
+        struct channel *rch = NULL;
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s() %i\n", __FUNCTION__,flowc);
+
+        sprintf(device,"%s%i",devnam,port_num);
+        dev = __dev_get_by_name(device);
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "ctc_mpc_flow_control %s dev=NULL\n",device);
+                return;
+        }
+
+        ctcmpc_pr_debug("ctcmpc: %s %s called \n",dev->name,__FUNCTION__);
+
+        privptr  = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "ctcmpc:%s() %s privptr=NULL\n",
+                       __FUNCTION__,
+                       device);
+                return;
+        }
+        grpptr = privptr->mpcg;
+        rch = privptr->channel[READ];
+
+        switch(flowc)
+        {
+                case 1:
+                        if(fsm_getstate(grpptr->fsm) == MPCG_STATE_FLOWC)
+                                break;
+                        if(fsm_getstate(grpptr->fsm) == MPCG_STATE_READY)
+                        {
+                                if(grpptr->flow_off_called == 1)
+                                        grpptr->flow_off_called = 0;
+                                else
+                                        fsm_newstate(grpptr->fsm,
+                                                     MPCG_STATE_FLOWC);
+                                break;
+                        }
+                        break;
+                case 0:
+                        if(fsm_getstate(grpptr->fsm) == MPCG_STATE_FLOWC)
+                        {
+                                fsm_newstate(grpptr->fsm, MPCG_STATE_READY);
+                                /* ensure any data that has accumulated */
+                                /* on the io_queue will now be sent     */
+                                tasklet_schedule(&rch->ch_tasklet);
+                        }
+                        /* possible race condition                      */
+                        if(fsm_getstate(grpptr->fsm) == MPCG_STATE_READY)
+                        {
+                                grpptr->flow_off_called = 1;
+                                break;
+                        }
+                        break;
+        }
+
+        ctcmpc_pr_debug("ctcmpc exit:  %s() %i\n", __FUNCTION__,flowc);
+}
+
+static int mpc_send_qllc_discontact(struct net_device *);
+/*********************************************************************/
+/*
+        invoked when the device transitions to dev_stopped
+        MPC will stop each individual channel if a single XID failure
+        occurs, or will intitiate all channels be stopped if a GROUP
+        level failure occurs.
+*/
+/*********************************************************************/
+
+static void
+mpc_action_go_inop(fsm_instance *fi, int event, void *arg)
+{
+        struct net_device  *dev = (struct net_device *)arg;
+        struct ctc_priv    *privptr;
+        struct mpc_group *grpptr;
+        int rc = 0;
+        struct channel *wch,*rch;
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        ctcmpc_pr_debug("ctcmpc enter: %s  %s()\n", dev->name,__FUNCTION__);
+
+        privptr  = (struct ctc_priv *)dev->priv;
+        grpptr =  privptr->mpcg;
+        grpptr->flow_off_called = 0;
+
+        fsm_deltimer(&grpptr->timer);
+
+        if(grpptr->channels_terminating)
+                goto done;
+
+        grpptr->channels_terminating = 1;
+
+        grpptr->saved_state = fsm_getstate(grpptr->fsm);
+        fsm_newstate(grpptr->fsm,MPCG_STATE_INOP);
+        if(grpptr->saved_state > MPCG_STATE_XID7INITF)
+                printk(KERN_NOTICE "%s:MPC GROUP INOPERATIVE\n", dev->name);
+        if((grpptr->saved_state != MPCG_STATE_RESET) ||
+           /* dealloc_channel has been called */
+           ((grpptr->saved_state == MPCG_STATE_RESET) &&
+            (grpptr->port_persist == 0)))
+                fsm_deltimer(&privptr->restart_timer);
+
+        wch = privptr->channel[WRITE];
+        rch = privptr->channel[READ];
+
+        switch(grpptr->saved_state)
+        {
+                case MPCG_STATE_RESET:
+                case MPCG_STATE_INOP:
+                case MPCG_STATE_XID2INITW:
+                case MPCG_STATE_XID0IOWAIT:
+                case MPCG_STATE_XID2INITX:
+                case MPCG_STATE_XID7INITW:
+                case MPCG_STATE_XID7INITX:
+                case MPCG_STATE_XID0IOWAIX:
+                case MPCG_STATE_XID7INITI:
+                case MPCG_STATE_XID7INITZ:
+                case MPCG_STATE_XID7INITF:
+                        break;
+                case MPCG_STATE_FLOWC:
+                case MPCG_STATE_READY:
+                default:
+                        tasklet_hi_schedule(&wch->ch_disc_tasklet);
+        }
+
+        grpptr->xid2_tgnum = 0;
+        grpptr->group_max_buflen = 0;  /*min of all received */
+        grpptr->outstanding_xid2 = 0;
+        grpptr->outstanding_xid7 = 0;
+        grpptr->outstanding_xid7_p2 = 0;
+        grpptr->saved_xid2 = NULL;
+        grpptr->xidnogood = 0;
+        grpptr->changed_side = 0;
+
+        grpptr->rcvd_xid_skb->data = grpptr->rcvd_xid_skb->tail =
+                                     grpptr->rcvd_xid_data;
+        grpptr->rcvd_xid_skb->len = 0;
+        grpptr->rcvd_xid_th = (struct th_header *)grpptr->rcvd_xid_skb->data;
+        memcpy(skb_put(grpptr->rcvd_xid_skb,TH_HEADER_LENGTH),&thnorm,
+               TH_HEADER_LENGTH);
+
+        if(grpptr->send_qllc_disc == 1)
+        {
+                grpptr->send_qllc_disc = 0;
+                rc = mpc_send_qllc_discontact(dev);
+        }
+
+        /* DO NOT issue DEV_EVENT_STOP directly out of this code */
+        /* This can result in INOP of VTAM PU due to halting of  */
+        /* outstanding IO which causes a sense to be returned    */
+        /* Only about 3 senses are allowed and then IOS/VTAM will*/
+        /* ebcome unreachable without manual intervention        */
+        if((grpptr->port_persist == 1)  || (grpptr->alloc_called))
+        {
+                grpptr->alloc_called = 0;
+                fsm_deltimer(&privptr->restart_timer);
+                fsm_addtimer(&privptr->restart_timer,
+                             500,
+                             DEV_EVENT_RESTART,
+                             dev);
+                fsm_newstate(grpptr->fsm, MPCG_STATE_RESET);
+                if(grpptr->saved_state > MPCG_STATE_XID7INITF)
+                        printk(KERN_NOTICE "%s:MPC GROUP RECOVERY SCHEDULED\n",
+                               dev->name);
+        } else
+        {
+                fsm_deltimer(&privptr->restart_timer);
+                fsm_addtimer(&privptr->restart_timer,500,DEV_EVENT_STOP, dev);
+                fsm_newstate(grpptr->fsm, MPCG_STATE_RESET);
+                printk(KERN_NOTICE "%s:MPC GROUP RECOVERY NOT ATTEMPTED\n",
+                       dev->name);
+        }
+
+        done:
+        ctcmpc_pr_debug("ctcmpc exit:%s  %s()\n", dev->name,__FUNCTION__);
+        return;
+}
+
+
+
+static void
+mpc_action_timeout(fsm_instance *fi, int event, void *arg)
+{
+        struct net_device *dev = (struct net_device *)arg;
+        struct ctc_priv *privptr;
+        struct mpc_group *grpptr;
+        struct channel *wch;
+        struct channel *rch;
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        ctcmpc_pr_debug("ctcmpc enter: %s  %s()\n", dev->name,__FUNCTION__);
+
+        privptr = (struct ctc_priv *)dev->priv;
+        grpptr = privptr->mpcg;
+        wch = privptr->channel[WRITE];
+        rch = privptr->channel[READ];
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_XID2INITW:
+                        /* Unless there is outstanding IO on the  */
+                        /* channel just return and wait for ATTN  */
+                        /* interrupt to begin XID negotiations    */
+                        if((fsm_getstate(rch->fsm) == CH_XID0_PENDING) &&
+                           (fsm_getstate(wch->fsm) == CH_XID0_PENDING))
+                                break;
+                default:
+                        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+        }
+
+        ctcmpc_pr_debug("ctcmpc exit:%s  %s()\n", dev->name,__FUNCTION__);
+        return;
+}
+
+
+static void
+mpc_action_discontact(fsm_instance *fi, int event, void *arg)
+{
+        struct mpcg_info   *mpcginfo   = (struct mpcg_info *)arg;
+        struct channel     *ch         = mpcginfo->ch;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+
+
+        if(ch == NULL)
+        {
+                printk(KERN_INFO "%s() ch=NULL\n",__FUNCTION__);
+                return;
+        }
+        if(ch->netdev == NULL)
+        {
+                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        ctcmpc_pr_debug("ctcmpc enter: %s  %s()\n", dev->name,__FUNCTION__);
+
+        grpptr->send_qllc_disc = 1;
+        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+
+        ctcmpc_pr_debug("ctcmpc exit: %s  %s()\n", dev->name,__FUNCTION__);
+        return;
+}
+
+static void
+mpc_action_send_discontact(unsigned long thischan)
+{
+        struct channel     *ch         = (struct channel *)thischan;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+        int rc = 0;
+        unsigned long     saveflags;
+
+
+        ctcmpc_pr_info("ctcmpc: %s cp:%i enter: %s() GrpState:%s ChState:%s\n",
+                       dev->name,
+                       smp_processor_id(),
+                       __FUNCTION__,
+                       fsm_getstate_str(grpptr->fsm),
+                       fsm_getstate_str(ch->fsm));
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+        rc = ccw_device_start(ch->cdev, &ch->ccw[15],(unsigned long)ch, 0xff,0);
+        spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+
+        if(rc != 0)
+        {
+                ctcmpc_pr_info("ctcmpc: %s() ch:%s IO failed \n",
+                               __FUNCTION__,
+                               ch->id);
+                ccw_check_return_code(ch, rc,"send discontact");
+                /* Not checking return code value here */
+                /* Making best effort to notify partner*/
+                /* that MPC Group is going down        */
+        }
+
+        ctcmpc_pr_debug("ctcmpc exit: %s  %s()\n", dev->name,__FUNCTION__);
+        return;
+}
+
+
+
+static int
+mpc_validate_xid(struct mpcg_info *mpcginfo)
+{
+        struct channel     *ch         = mpcginfo->ch;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+        struct xid2        *xid        = mpcginfo->xid;
+        int         failed      = 0;
+        int         rc          = 0;
+        __u64       our_id,their_id = 0;
+        int         len;
+
+        len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH;
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+
+        if(mpcginfo->xid == NULL)
+        {
+                printk(KERN_INFO "%s() xid=NULL\n",__FUNCTION__);
+                rc = 1;
+                goto done;
+        }
+
+        ctcmpc_pr_debug("ctcmpc :  %s  xid received()\n",
+                       __FUNCTION__);
+        dumpit((char *)mpcginfo->xid,XID2_LENGTH);
+
+        /*the received direction should be the opposite of ours  */
+        if(((CHANNEL_DIRECTION(ch->flags) ==
+             READ) ? XID2_WRITE_SIDE : XID2_READ_SIDE )
+           != xid->xid2_dlc_type )
+        {
+                failed = 1;
+                printk(KERN_INFO "ctcmpc:%s() XID REJECTED - READ-WRITE CH "
+                       "Pairing Invalid \n",
+                       __FUNCTION__);
+        }
+
+        if(xid->xid2_dlc_type == XID2_READ_SIDE)
+        {
+                ctcmpc_pr_debug("ctcmpc: %s(): grpmaxbuf:%d xid2buflen:%d\n",
+                               __FUNCTION__,grpptr->group_max_buflen,
+                               xid->xid2_buf_len);
+
+                if(grpptr->group_max_buflen == 0)
+                        grpptr->group_max_buflen = xid->xid2_buf_len - len;
+                else
+                {
+                        if((xid->xid2_buf_len - len) <
+                           grpptr->group_max_buflen)
+                        {
+                                grpptr->group_max_buflen =
+                                xid->xid2_buf_len - len;
+                        }
+                }
+
+        }
+
+
+        if(grpptr->saved_xid2 == NULL)
+        {
+                grpptr->saved_xid2 = (struct xid2 *)grpptr->rcvd_xid_skb->tail;
+                memcpy(skb_put(grpptr->rcvd_xid_skb,XID2_LENGTH), xid,
+                       XID2_LENGTH);
+                grpptr->rcvd_xid_skb->data = grpptr->rcvd_xid_skb->tail =
+                                             grpptr->rcvd_xid_data;
+                grpptr->rcvd_xid_skb->len = 0;
+
+                /* convert two 32 bit numbers into 1 64 bit for id compare */
+                our_id = (__u64)privptr->xid->xid2_adj_id;
+                our_id = our_id << 32;
+                our_id = our_id + privptr->xid->xid2_sender_id;
+                their_id = (__u64)xid->xid2_adj_id;
+                their_id = their_id << 32;
+                their_id = their_id + xid->xid2_sender_id;
+                /* lower id assume the xside role */
+                if(our_id < their_id)
+                {
+                        grpptr->roll = XSIDE;
+                        ctcmpc_pr_debug("ctcmpc :%s() WE HAVE LOW ID-"
+                                       "TAKE XSIDE\n", __FUNCTION__);
+                } else
+                {
+                        grpptr->roll = YSIDE;
+                        ctcmpc_pr_debug("ctcmpc :%s() WE HAVE HIGH ID-"
+                                       "TAKE YSIDE\n", __FUNCTION__);
+                }
+
+        } else
+        {
+
+                if(xid->xid2_flag4 != grpptr->saved_xid2->xid2_flag4)
+                {
+                        failed = 1;
+                        printk(KERN_INFO "%s XID REJECTED - XID Flag Byte4\n",
+                               __FUNCTION__);
+                }
+                if(xid->xid2_flag2 == 0x40)
+                {
+                        failed = 1;
+                        printk(KERN_INFO "%s XID REJECTED - XID NOGOOD\n",
+                               __FUNCTION__);
+                }
+                if(xid->xid2_adj_id != grpptr->saved_xid2->xid2_adj_id)
+                {
+                        failed = 1;
+                        printk(KERN_INFO "%s XID REJECTED - "
+                               "Adjacent Station ID Mismatch\n",
+                               __FUNCTION__);
+                }
+                if(xid->xid2_sender_id != grpptr->saved_xid2->xid2_sender_id)
+                {
+                        failed = 1;
+                        printk(KERN_INFO "%s XID REJECTED - "
+                               "Sender Address Mismatch\n",
+                               __FUNCTION__);
+
+                }
+        }
+
+        if(failed)
+        {
+                ctcmpc_pr_info("ctcmpc     :  %s() failed\n", __FUNCTION__);
+                privptr->xid->xid2_flag2 = 0x40;
+                grpptr->saved_xid2->xid2_flag2 = 0x40;
+                rc = 1;
+                goto done;
+        }
+
+        done:
+
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+        return(rc);
+}
+
+
+static void
+mpc_action_yside_xid(fsm_instance *fsm,int event, void *arg)
+{
+        struct channel           *ch         = (struct channel *)arg;
+        struct ctc_priv          *privptr;
+        struct mpc_group         *grpptr     = NULL;
+        struct net_device *dev = NULL;
+        int               rc          = 0;
+        unsigned long     saveflags;
+        int               gotlock     = 0;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        if(ch == NULL)
+        {
+                printk(KERN_INFO "%s ch=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        dev = ch->netdev;
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s dev=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "%s privptr=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "%s grpptr=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        if(ctcmpc_checkalloc_buffer(ch, 0))
+        {
+                rc = -ENOMEM;
+                goto done;
+        }
+
+
+        ch->trans_skb->data =  ch->trans_skb->tail = ch->trans_skb_data;
+        ch->trans_skb->len = 0;
+        memset(ch->trans_skb->data, 0, 16);
+        ch->rcvd_xid_th =  (struct th_header *)ch->trans_skb->data;
+        skb_put(ch->trans_skb,TH_HEADER_LENGTH);
+        ch->rcvd_xid = (struct xid2 *)ch->trans_skb->tail;
+        skb_put(ch->trans_skb,XID2_LENGTH);
+        ch->rcvd_xid_id = ch->trans_skb->tail;
+        ch->trans_skb->data =  ch->trans_skb->tail = ch->trans_skb_data;
+        ch->trans_skb->len = 0;
+
+        ch->ccw[8].flags        = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[8].count        = 0;
+        ch->ccw[8].cda          = 0x00;
+
+        if(ch->rcvd_xid_th == NULL)
+        {
+                printk(KERN_INFO "%s ch->rcvd_xid_th=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[9].cmd_code     = CCW_CMD_READ;
+        ch->ccw[9].flags        = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[9].count        = TH_HEADER_LENGTH;
+        ch->ccw[9].cda          = virt_to_phys(ch->rcvd_xid_th);
+
+        if(ch->rcvd_xid == NULL)
+        {
+                printk(KERN_INFO "%s ch->rcvd_xid=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[10].cmd_code    = CCW_CMD_READ;
+        ch->ccw[10].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[10].count       = XID2_LENGTH;
+        ch->ccw[10].cda         = virt_to_phys(ch->rcvd_xid);
+
+        if(ch->xid_th == NULL)
+        {
+                printk(KERN_INFO "%s ch->xid_th=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[11].cmd_code    = CCW_CMD_WRITE;
+        ch->ccw[11].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[11].count       = TH_HEADER_LENGTH;
+        ch->ccw[11].cda         = virt_to_phys(ch->xid_th);
+
+        if(ch->xid == NULL)
+        {
+                printk(KERN_INFO "%s ch->xid=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        ch->ccw[12].cmd_code    = CCW_CMD_WRITE;
+        ch->ccw[12].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[12].count       = XID2_LENGTH;
+        ch->ccw[12].cda         = virt_to_phys(ch->xid);
+
+        if(ch->xid_id == NULL)
+        {
+                printk(KERN_INFO "%s ch->xid_id=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[13].cmd_code    = CCW_CMD_WRITE;
+        ch->ccw[13].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[13].count       = 4;
+        ch->ccw[13].cda         = virt_to_phys(ch->xid_id);
+
+        ch->ccw[14].cmd_code    = CCW_CMD_NOOP;
+        ch->ccw[14].flags       = CCW_FLAG_SLI;
+        ch->ccw[14].count       = 0;
+        ch->ccw[14].cda         = 0;
+
+#ifdef DEBUGDATA
+        dumpit((char *)&ch->ccw[8],sizeof(struct ccw1) * 7);
+#endif
+        dumpit((char *)ch->xid_th,TH_HEADER_LENGTH);
+        dumpit((char *)ch->xid,XID2_LENGTH);
+        dumpit((char *)ch->xid_id,4);
+        if(!in_irq())
+        {
+                spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+                gotlock = 1;
+        }
+
+        fsm_addtimer(&ch->timer, 5000 , CH_EVENT_TIMER, ch);
+        rc = ccw_device_start(ch->cdev, &ch->ccw[8],
+                              (unsigned long) ch, 0xff, 0);
+
+        if(gotlock)
+                spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+
+        if(rc != 0)
+        {
+                ctcmpc_pr_info("ctcmpc: %s() ch:%s IO failed \n",
+                               __FUNCTION__,ch->id);
+                ccw_check_return_code(ch, rc,"y-side XID");
+                goto done;
+        }
+
+        done:
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+        return;
+
+}
+
+static void
+mpc_action_doxid0(fsm_instance *fsm,int event, void *arg)
+{
+        struct channel     *ch         = (struct channel *)arg;
+        struct ctc_priv    *privptr;
+        struct mpc_group   *grpptr     = NULL;
+        struct net_device *dev = NULL;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+        if(ch == NULL)
+        {
+                printk(KERN_WARNING "%s ch=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        dev = ch->netdev;
+        if(dev == NULL)
+        {
+                printk(KERN_WARNING "%s dev=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_WARNING "%s privptr=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_WARNING "%s grpptr=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+
+        if(ch->xid == NULL)
+        {
+                printk(KERN_WARNING "%s ch-xid=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        fsm_newstate(ch->fsm, CH_XID0_INPROGRESS);
+
+        ch->xid->xid2_option =  XID2_0;
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_XID2INITW:
+                case MPCG_STATE_XID2INITX:
+                        ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD;
+                        break;
+                case MPCG_STATE_XID0IOWAIT:
+                case MPCG_STATE_XID0IOWAIX:
+                        ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL;
+                        break;
+        }
+
+        fsm_event(grpptr->fsm,MPCG_EVENT_DOIO,ch);
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return;
+
+}
+
+
+
+static void
+mpc_action_doxid7(fsm_instance *fsm,int event, void *arg)
+{
+        struct net_device *dev = (struct net_device *)arg;
+        struct ctc_priv   *privptr = NULL;
+        struct mpc_group  *grpptr = NULL;
+        int direction;
+        int rc = 0;
+        int send = 0;
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s() \n", __FUNCTION__);
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s dev=NULL \n",__FUNCTION__);
+                rc = 1;
+                goto done;
+        }
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "%s privptr=NULL \n",__FUNCTION__);
+                rc = 1;
+                goto done;
+        }
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "%s grpptr=NULL \n",__FUNCTION__);
+                rc = 1;
+                goto done;
+        }
+
+        for(direction = READ; direction <= WRITE; direction++)
+        {
+                struct channel *ch = privptr->channel[direction];
+                struct xid2 *thisxid = ch->xid;
+                ch->xid_skb->data = ch->xid_skb->tail = ch->xid_skb_data;
+                ch->xid_skb->len = 0;
+                thisxid->xid2_option = XID2_7;
+                send = 0;
+
+                /* xid7 phase 1 */
+                if(grpptr->outstanding_xid7_p2 > 0)
+                {
+                        if(grpptr->roll == YSIDE)
+                        {
+                                if(fsm_getstate(ch->fsm) == CH_XID7_PENDING1)
+                                {
+                                        fsm_newstate(ch->fsm,CH_XID7_PENDING2);
+                                        ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD;
+                                        memcpy(skb_put(ch->xid_skb,
+                                                       TH_HEADER_LENGTH),
+                                               &thdummy,TH_HEADER_LENGTH);
+                                        send = 1;
+                                }
+                        } else
+                        {
+                                if(fsm_getstate(ch->fsm) < CH_XID7_PENDING2)
+                                {
+                                        fsm_newstate(ch->fsm,CH_XID7_PENDING2);
+                                        ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL;
+                                        memcpy(skb_put(ch->xid_skb,
+                                                       TH_HEADER_LENGTH),
+                                               &thnorm,TH_HEADER_LENGTH);
+                                        send = 1;
+                                }
+                        }
+                }
+                /* xid7 phase 2 */
+                else
+                {
+                        if(grpptr->roll == YSIDE)
+                        {
+                                if(fsm_getstate(ch->fsm) < CH_XID7_PENDING4)
+                                {
+                                        fsm_newstate(ch->fsm,CH_XID7_PENDING4);
+                                        memcpy(skb_put(ch->xid_skb,
+                                                       TH_HEADER_LENGTH),
+                                               &thnorm,TH_HEADER_LENGTH);
+                                        ch->ccw[8].cmd_code = CCW_CMD_WRITE_CTL;
+                                        send = 1;
+                                }
+                        } else
+                        {
+                                if(fsm_getstate(ch->fsm) == CH_XID7_PENDING3)
+                                {
+                                        fsm_newstate(ch->fsm,CH_XID7_PENDING4);
+                                        ch->ccw[8].cmd_code = CCW_CMD_SENSE_CMD;
+                                        memcpy(skb_put(ch->xid_skb,
+                                                       TH_HEADER_LENGTH),
+                                               &thdummy,TH_HEADER_LENGTH);
+                                        send = 1;
+                                }
+                        }
+                }
+
+                if(send)
+                        fsm_event(grpptr->fsm,MPCG_EVENT_DOIO,ch);
+        }
+
+        done:
+
+        if(rc != 0)
+                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+        return;
+}
+
+
+
+static void
+mpc_action_xside_xid(fsm_instance *fsm,int event, void *arg)
+{
+        struct channel    *ch = (struct channel *)arg;
+        struct ctc_priv   *privptr;
+        struct mpc_group  *grpptr = NULL;
+        struct net_device *dev = NULL;
+        int rc = 0;
+        unsigned long saveflags;
+        int gotlock     = 0;
+
+        if(ch == NULL)
+        {
+                printk(KERN_INFO "%s ch=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        dev = ch->netdev;
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s dev=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "%s privptr=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "%s grpptr=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        if(ctcmpc_checkalloc_buffer(ch, 0))
+        {
+                rc = -ENOMEM;
+                goto done;
+        }
+
+        ch->trans_skb->data =  ch->trans_skb->tail = ch->trans_skb_data;
+        ch->trans_skb->len = 0;
+        memset(ch->trans_skb->data, 0, 16);
+        ch->rcvd_xid_th =  (struct th_header *)ch->trans_skb->data;
+        skb_put(ch->trans_skb,TH_HEADER_LENGTH);
+        ch->rcvd_xid = (struct xid2 *)ch->trans_skb->tail;
+        skb_put(ch->trans_skb,XID2_LENGTH);
+        ch->rcvd_xid_id = ch->trans_skb->tail;
+        ch->trans_skb->data =  ch->trans_skb->tail = ch->trans_skb_data;
+        ch->trans_skb->len = 0;
+
+        ch->ccw[8].flags        = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[8].count        = 0;
+        ch->ccw[8].cda          = 0x00;  /* null   */
+
+        if(ch->xid_th == NULL)
+        {
+                printk(KERN_INFO "%s ch->xid_th=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[9].cmd_code     = CCW_CMD_WRITE;
+        ch->ccw[9].flags        = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[9].count        = TH_HEADER_LENGTH;
+        ch->ccw[9].cda          = virt_to_phys(ch->xid_th);
+
+        if(ch->xid == NULL)
+        {
+                printk(KERN_INFO "%s ch->xid=NULL\n",__FUNCTION__);
+                goto done;
+        }
+
+        ch->ccw[10].cmd_code    = CCW_CMD_WRITE;
+        ch->ccw[10].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[10].count       = XID2_LENGTH;
+        ch->ccw[10].cda         = virt_to_phys(ch->xid);
+
+        if(ch->rcvd_xid_th == NULL)
+        {
+                printk(KERN_INFO "%s ch->rcvd_xid_th=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[11].cmd_code    = CCW_CMD_READ;
+        ch->ccw[11].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[11].count       = TH_HEADER_LENGTH;
+        ch->ccw[11].cda         = virt_to_phys(ch->rcvd_xid_th);
+
+        if(ch->rcvd_xid == NULL)
+        {
+                printk(KERN_INFO "%s ch->rcvd_xid=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[12].cmd_code    = CCW_CMD_READ;
+        ch->ccw[12].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[12].count       = XID2_LENGTH;
+        ch->ccw[12].cda         = virt_to_phys(ch->rcvd_xid);
+
+        if(ch->xid_id == NULL)
+        {
+                printk(KERN_INFO "%s ch->xid_id=NULL\n",__FUNCTION__);
+                goto done;
+        }
+        ch->ccw[13].cmd_code    = CCW_CMD_READ;
+        ch->ccw[13].flags       = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[13].count       = 4;
+        ch->ccw[13].cda         = virt_to_phys(ch->rcvd_xid_id);
+
+        ch->ccw[14].cmd_code    = CCW_CMD_NOOP;
+        ch->ccw[14].flags       = CCW_FLAG_SLI;
+        ch->ccw[14].count       = 0;
+        ch->ccw[14].cda         = 0;
+
+#ifdef DEBUGCCW
+        dumpit((char *)&ch->ccw[8],sizeof(struct ccw1) * 7);
+#endif
+        dumpit((char *)ch->xid_th,TH_HEADER_LENGTH);
+        dumpit((char *)ch->xid,XID2_LENGTH);
+
+        if(!in_irq())
+        {
+                spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+                gotlock = 1;
+        }
+
+        fsm_addtimer(&ch->timer, 5000 , CH_EVENT_TIMER, ch);
+        rc = ccw_device_start(ch->cdev, &ch->ccw[8],(unsigned long) ch,
+                              0xff, 0);
+
+        if(gotlock)
+                spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+
+        if(rc != 0)
+        {
+                ctcmpc_pr_info("ctcmpc: %s() %s IO failed \n",
+                               __FUNCTION__,ch->id);
+                ccw_check_return_code(ch, rc,"x-side XID");
+                goto done;
+        }
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return;
+}
+
+
+
+static void
+mpc_action_rcvd_xid0(fsm_instance *fsm,int event, void *arg)
+{
+
+        struct mpcg_info   *mpcginfo   = (struct mpcg_info *)arg;
+        struct channel     *ch         = mpcginfo->ch;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr;
+        struct mpc_group   *grpptr;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+
+        privptr = (struct ctc_priv *)dev->priv;
+        grpptr = privptr->mpcg;
+
+        ctcmpc_pr_debug("ctcmpc in:%s() %s xid2:%i xid7:%i xidt_p2:%i \n",
+                       __FUNCTION__,ch->id,
+                       grpptr->outstanding_xid2,
+                       grpptr->outstanding_xid7,
+                       grpptr->outstanding_xid7_p2);
+
+        if(fsm_getstate(ch->fsm) < CH_XID7_PENDING)
+                fsm_newstate(ch->fsm,CH_XID7_PENDING);
+
+        grpptr->outstanding_xid2--;
+        grpptr->outstanding_xid7++;
+        grpptr->outstanding_xid7_p2++;
+
+        /* must change state before validating xid to */
+        /* properly handle interim interrupts received*/
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_XID2INITW:
+                        fsm_newstate(grpptr->fsm,MPCG_STATE_XID2INITX);
+                        mpc_validate_xid(mpcginfo);
+                        break;
+                case MPCG_STATE_XID0IOWAIT:
+                        fsm_newstate(grpptr->fsm,MPCG_STATE_XID0IOWAIX);
+                        mpc_validate_xid(mpcginfo);
+                        break;
+                case MPCG_STATE_XID2INITX:
+                        if(grpptr->outstanding_xid2 == 0)
+                        {
+                                fsm_newstate(grpptr->fsm,
+                                             MPCG_STATE_XID7INITW);
+                                mpc_validate_xid(mpcginfo);
+                                fsm_event(grpptr->fsm,
+                                          MPCG_EVENT_XID2DONE,dev);
+                        }
+                        break;
+                case MPCG_STATE_XID0IOWAIX:
+                        if(grpptr->outstanding_xid2 == 0)
+                        {
+                                fsm_newstate(grpptr->fsm,
+                                             MPCG_STATE_XID7INITI);
+                                mpc_validate_xid(mpcginfo);
+                                fsm_event(grpptr->fsm,
+                                          MPCG_EVENT_XID2DONE,dev);
+                        }
+                        break;
+        }
+        kfree(mpcginfo);
+
+        ctcmpc_pr_debug("ctcmpc:%s() %s xid2:%i xid7:%i xidt_p2:%i \n",
+                       __FUNCTION__,ch->id,
+                       grpptr->outstanding_xid2,
+                       grpptr->outstanding_xid7,
+                       grpptr->outstanding_xid7_p2);
+        ctcmpc_pr_debug("ctcmpc:%s() %s grpstate: %s chanstate: %s \n",
+                       __FUNCTION__,ch->id,
+                       fsm_getstate_str(grpptr->fsm),
+                       fsm_getstate_str(ch->fsm));
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+        return;
+
+}
+
+
+static void
+mpc_action_rcvd_xid7(fsm_instance *fsm,int event, void *arg)
+{
+
+        struct mpcg_info   *mpcginfo   = (struct mpcg_info *)arg;
+        struct channel     *ch         = mpcginfo->ch;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+
+        ctcmpc_pr_debug("ctcmpc:  outstanding_xid7: %i,"
+                       " outstanding_xid7_p2: %i\n",
+                       grpptr->outstanding_xid7,
+                       grpptr->outstanding_xid7_p2);
+
+
+        grpptr->outstanding_xid7--;
+
+        ch->xid_skb->data = ch->xid_skb->tail = ch->xid_skb_data;
+        ch->xid_skb->len = 0;
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_XID7INITI:
+                        fsm_newstate(grpptr->fsm,MPCG_STATE_XID7INITZ);
+                        mpc_validate_xid(mpcginfo);
+                        break;
+                case MPCG_STATE_XID7INITW:
+                        fsm_newstate(grpptr->fsm,MPCG_STATE_XID7INITX);
+                        mpc_validate_xid(mpcginfo);
+                        break;
+                case MPCG_STATE_XID7INITZ:
+                case MPCG_STATE_XID7INITX:
+                        if(grpptr->outstanding_xid7 == 0)
+                        {
+                                if(grpptr->outstanding_xid7_p2 > 0)
+                                {
+                                        grpptr->outstanding_xid7 =
+                                        grpptr->outstanding_xid7_p2;
+                                        grpptr->outstanding_xid7_p2 = 0;
+                                } else
+                                        fsm_newstate(grpptr->fsm,
+                                                     MPCG_STATE_XID7INITF);
+                                mpc_validate_xid(mpcginfo);
+                                fsm_event(grpptr->fsm,MPCG_EVENT_XID7DONE,dev);
+                                break;
+                        }
+                        mpc_validate_xid(mpcginfo);
+                        break;
+        }
+
+        kfree(mpcginfo);
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+        return;
+
+}
+
+static void
+ctcmpc_action_attn(fsm_instance *fsm,int event, void *arg)
+{
+        struct channel     *ch         = (struct channel *)arg;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s"
+                        "GrpState:%s ChState:%s\n",
+                        __func__,
+                        smp_processor_id(),
+                        ch,
+                        ch->id,
+        fsm_getstate_str(grpptr->fsm),
+        fsm_getstate_str(ch->fsm));
+#endif
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_XID2INITW:
+                        /* ok..start yside xid exchanges */
+                        if(ch->in_mpcgroup)
+                        {
+                                if(fsm_getstate(ch->fsm) ==  CH_XID0_PENDING)
+                                {
+                                        fsm_deltimer(&grpptr->timer);
+                                        fsm_addtimer(&grpptr->timer,
+                                                     MPC_XID_TIMEOUT_VALUE,
+                                                     MPCG_EVENT_TIMER,
+                                                     dev);
+                                        fsm_event(grpptr->fsm,
+                                                  MPCG_EVENT_XID0DO,
+                                                  ch);
+                                } else
+                                {/* attn rcvd before xid0 processed via bh */
+                                        if(fsm_getstate(ch->fsm) <
+                                           CH_XID7_PENDING1)
+                                                fsm_newstate(ch->fsm,
+                                                             CH_XID7_PENDING1);
+                                }
+                        }
+                        break;
+                case MPCG_STATE_XID2INITX:
+                case MPCG_STATE_XID0IOWAIT:
+                case MPCG_STATE_XID0IOWAIX:
+                        /* attn rcvd before xid0 processed on ch
+                        but mid-xid0 processing for group    */
+                        if(fsm_getstate(ch->fsm) < CH_XID7_PENDING1)
+                                fsm_newstate(ch->fsm,CH_XID7_PENDING1);
+                        break;
+                case MPCG_STATE_XID7INITW:
+                case MPCG_STATE_XID7INITX:
+                case MPCG_STATE_XID7INITI:
+                case MPCG_STATE_XID7INITZ:
+                        switch(fsm_getstate(ch->fsm))
+                        {
+                                case CH_XID7_PENDING:
+                                        fsm_newstate(ch->fsm,CH_XID7_PENDING1);
+                                        break;
+                                case CH_XID7_PENDING2:
+                                        fsm_newstate(ch->fsm,CH_XID7_PENDING3);
+                                        break;
+                        }
+                        fsm_event(grpptr->fsm,MPCG_EVENT_XID7DONE,dev);
+                        break;
+        }
+
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+        return;
+
+}
+
+static void
+ctcmpc_action_attnbusy(fsm_instance *fsm,int event, void *arg)
+{
+        struct channel     *ch         = (struct channel *)arg;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+
+        ctcmpc_pr_debug("ctcmpc enter: %s  %s() %s  \nGrpState:%s ChState:%s\n",
+                       dev->name,
+                       __FUNCTION__,ch->id,
+                       fsm_getstate_str(grpptr->fsm),
+                       fsm_getstate_str(ch->fsm));
+
+        fsm_deltimer(&ch->timer);
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_XID0IOWAIT:
+                        /* vtam wants to be primary.start yside xid exchanges*/
+                        /* only receive one attn-busy at a time so must not  */
+                        /* change state each time                            */
+                        grpptr->changed_side = 1;
+                        fsm_newstate(grpptr->fsm,MPCG_STATE_XID2INITW);
+                        break;
+                case MPCG_STATE_XID2INITW:
+                        if(grpptr->changed_side == 1)
+                        {
+                                grpptr->changed_side = 2;
+                                break;
+                        }
+                        /* process began via call to establish_conn      */
+                        /* so must report failure instead of reverting   */
+                        /* back to ready-for-xid passive state           */
+                        if(grpptr->estconnfunc)
+                                goto done;
+                        /* this attnbusy is NOT the result of xside xid  */
+                        /* collisions so yside must have been triggered  */
+                        /* by an ATTN that was not intended to start XID */
+                        /* processing. Revert back to ready-for-xid and  */
+                        /* wait for ATTN interrupt to signal xid start   */
+                        if(fsm_getstate(ch->fsm) == CH_XID0_INPROGRESS)
+                        {
+                                fsm_newstate(ch->fsm,CH_XID0_PENDING) ;
+                                fsm_deltimer(&grpptr->timer);
+                                goto done;
+                        }
+                        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                        goto done;
+                case MPCG_STATE_XID2INITX:
+                        /* XID2 was received before ATTN Busy for second
+                           channel.Send yside xid for second channel.
+                        */
+                        if(grpptr->changed_side == 1)
+                        {
+                                grpptr->changed_side = 2;
+                                break;
+                        }
+                case MPCG_STATE_XID0IOWAIX:
+                case MPCG_STATE_XID7INITW:
+                case MPCG_STATE_XID7INITX:
+                case MPCG_STATE_XID7INITI:
+                case MPCG_STATE_XID7INITZ:
+                default:
+                        /* multiple attn-busy indicates too out-of-sync      */
+                        /* and they are certainly not being received as part */
+                        /* of valid mpc group negotiations..                 */
+                        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                        goto done;
+        }
+
+        if(grpptr->changed_side == 1)
+        {
+                fsm_deltimer(&grpptr->timer);
+                fsm_addtimer(&grpptr->timer, MPC_XID_TIMEOUT_VALUE,
+                             MPCG_EVENT_TIMER, dev);
+        }
+        if(ch->in_mpcgroup)
+                fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch);
+        else
+                printk( KERN_WARNING "ctcmpc: %s() Not all channels have"
+                        " been added to group\n",
+                        __FUNCTION__);
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s()%s ch=0x%p id=%s\n",
+                        __func__, dev->name,ch, ch->id);
+#endif
+        return;
+
+}
+
+static void
+ctcmpc_action_resend(fsm_instance *fsm,int event, void *arg)
+{
+        struct channel     *ch         = (struct channel *)arg;
+        struct net_device  *dev        = ch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+
+        ctcmpc_pr_debug("ctcmpc enter: %s  %s() %s  \nGrpState:%s ChState:%s\n",
+                       dev->name,__FUNCTION__,ch->id,
+                       fsm_getstate_str(grpptr->fsm),
+                       fsm_getstate_str(ch->fsm));
+
+        fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch);
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): %s ch=0x%p id=%s\n",
+                        __func__, dev->name,ch, ch->id);
+#endif
+        return;
+}
+
+
+static int
+mpc_send_qllc_discontact(struct net_device *dev)
+{
+        int rc = 0, space = 0;
+        __u32 new_len = 0;
+        struct sk_buff    *skb;
+        struct qllc              *qllcptr;
+        struct ctc_priv *privptr = NULL;
+        struct mpc_group *grpptr = NULL;
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n",__FUNCTION__);
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
+                rc = 1;
+                goto done;
+        }
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "%s() privptr=NULL\n",__FUNCTION__);
+                rc = 1;
+                goto done;
+        }
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "%s() grpptr=NULL\n",__FUNCTION__);
+                rc = 1;
+                goto done;
+        }
+        ctcmpc_pr_info("ctcmpc: %s() GROUP STATE: %s\n",
+                       __FUNCTION__,mpcg_state_names[grpptr->saved_state]);
+
+
+        switch(grpptr->saved_state)
+        {       /* establish conn callback function is */
+                /* preferred method to report failure  */
+                case MPCG_STATE_XID0IOWAIT:
+                case MPCG_STATE_XID0IOWAIX:
+                case MPCG_STATE_XID7INITI:
+                case MPCG_STATE_XID7INITZ:
+                case MPCG_STATE_XID2INITW:
+                case MPCG_STATE_XID2INITX:
+                case MPCG_STATE_XID7INITW:
+                case MPCG_STATE_XID7INITX:
+                        if(grpptr->estconnfunc)
+                        {
+                                grpptr->estconnfunc(grpptr->port_num,-1,0);
+                                grpptr->estconnfunc = NULL;
+                                break;
+                        }
+                case MPCG_STATE_FLOWC:
+                case MPCG_STATE_READY:
+                        grpptr->send_qllc_disc = 2;
+                        new_len = sizeof(struct qllc);
+                        if((qllcptr =
+                            (struct qllc *)kmalloc(sizeof(struct qllc),
+                                                   gfp_type() | GFP_DMA))
+                           == NULL)
+                        {
+                                printk(KERN_INFO
+                                       "ctcmpc: Out of memory in %s()\n",
+                                       dev->name);
+                                rc = 1;
+                                goto done;
+                        }
+
+                        memset(qllcptr, 0, new_len);
+                        qllcptr->qllc_address = 0xcc;
+                        qllcptr->qllc_commands = 0x03;
+
+                        skb = __dev_alloc_skb(new_len,GFP_ATOMIC);
+
+                        if(skb == NULL)
+                        {
+                                printk(KERN_INFO
+                                       "%s Out of memory in mpc_send_qllc\n",
+                                       dev->name);
+                                privptr->stats.rx_dropped++;
+                                rc = 1;
+                                kfree(qllcptr);
+                                goto done;
+                        }
+
+                        memcpy(skb_put(skb, new_len), qllcptr, new_len);
+                        kfree(qllcptr);
+
+                        space = skb_headroom(skb);
+                        if(space < 4)
+                        {
+                                printk(KERN_INFO "ctcmpc: %s() Unable to"
+                                       " build discontact for %s\n",
+                                       __FUNCTION__,dev->name);
+                                rc = 1;
+                                dev_kfree_skb_any(skb);
+                                goto done;
+                        }
+
+                        *((__u32 *) skb_push(skb, 4)) =
+                        privptr->channel[READ]->pdu_seq;
+                        privptr->channel[READ]->pdu_seq++;
+#ifdef DEBUGDATA
+                        ctcmpc_pr_debug("ctcmpc: %s ToDCM_pdu_seq= %08x\n" ,
+                                       __FUNCTION__,
+                                       privptr->channel[READ]->pdu_seq);
+#endif
+                        /* receipt of CC03 resets anticipated sequence number on
+                              receiving side */
+                        privptr->channel[READ]->pdu_seq = 0x00;
+
+                        skb->mac.raw = skb->data;
+                        skb->dev = dev;
+                        skb->protocol = htons(ETH_P_SNAP);
+                        skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+                        dumpit((char *)skb->data,
+                               (sizeof(struct qllc)+4));
+
+                        netif_rx(skb);
+                        break;
+                default: break;
+
+        }
+
+        done:
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+        return(rc);
+}
+
+static void
+ctcmpc_send_sweep(fsm_instance *fsm,int event, void *arg)
+{
+        struct channel *ach = (struct channel *)arg;
+        struct net_device *dev = ach->netdev;
+        struct ctc_priv *privptr = (struct ctc_priv *)dev->priv;
+        struct mpc_group *grpptr = privptr->mpcg;
+        int rc = 0;
+        struct sk_buff *skb;
+        unsigned long saveflags;
+        struct channel *wch = privptr->channel[WRITE];
+        struct channel *rch = privptr->channel[READ];
+        struct th_sweep *header = NULL;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ach, ach->id);
+#endif
+
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        if(grpptr->in_sweep == 0)
+                goto done;
+
+#ifdef DEBUGDATA
+        ctcmpc_pr_debug("ctcmpc: %s() 1: ToVTAM_th_seq= %08x\n" ,
+                       __FUNCTION__,wch->th_seq_num);
+        ctcmpc_pr_debug("ctcmpc: %s() 1: FromVTAM_th_seq= %08x\n" ,
+                       __FUNCTION__,rch->th_seq_num);
+#endif
+
+
+        if(fsm_getstate(wch->fsm) != CH_STATE_TXIDLE)
+        {
+                /* give the previous IO time to complete */
+                fsm_addtimer(&wch->sweep_timer,200,CH_EVENT_RSWEEP1_TIMER,wch);
+                goto done;
+        }
+
+        skb = skb_dequeue(&wch->sweep_queue);
+        if(!skb)
+                goto done;
+
+        if(set_normalized_cda(&wch->ccw[4], skb->data))
+        {
+                grpptr->in_sweep = 0;
+                ctcmpc_clear_busy(dev);
+		    dev_kfree_skb_any(skb);
+                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                goto done;
+        } else{
+		  atomic_inc(&skb->users);
+		  skb_queue_tail(&wch->io_queue, skb);
+	  }
+
+        /* send out the sweep */
+        wch->ccw[4].count = skb->len;
+
+        header = (struct th_sweep *)skb->data;
+        switch(header->th.th_ch_flag)
+        {
+                case TH_SWEEP_REQ:      grpptr->sweep_req_pend_num--;
+                        break;
+                case TH_SWEEP_RESP:     grpptr->sweep_rsp_pend_num--;
+                        break;
+        }
+
+        header->sw.th_last_seq  = wch->th_seq_num;
+
+#ifdef DEBUGCCW
+        dumpit((char *)&wch->ccw[3],sizeof(struct ccw1) * 3);
+#endif
+        ctcmpc_pr_debug("ctcmpc: %s() sweep packet\n", __FUNCTION__);
+        dumpit((char *)header,TH_SWEEP_LENGTH);
+
+
+        fsm_addtimer(&wch->timer,CTC_TIMEOUT_5SEC,CH_EVENT_TIMER, wch);
+        fsm_newstate(wch->fsm, CH_STATE_TX);
+
+        spin_lock_irqsave(get_ccwdev_lock(wch->cdev), saveflags);
+        wch->prof.send_stamp = xtime;
+        rc = ccw_device_start(wch->cdev, &wch->ccw[3],
+                              (unsigned long) wch, 0xff, 0);
+        spin_unlock_irqrestore(get_ccwdev_lock(wch->cdev), saveflags);
+
+        if((grpptr->sweep_req_pend_num == 0) &&
+           (grpptr->sweep_rsp_pend_num == 0))
+        {
+                grpptr->in_sweep = 0;
+                rch->th_seq_num = 0x00;
+                wch->th_seq_num = 0x00;
+                ctcmpc_clear_busy(dev);
+        }
+
+#ifdef DEBUGDATA
+        ctcmpc_pr_debug("ctcmpc: %s()2: ToVTAM_th_seq= %08x\n" ,
+                       __FUNCTION__,wch->th_seq_num);
+        ctcmpc_pr_debug("ctcmpc: %s()2: FromVTAM_th_seq= %08x\n" ,
+                       __FUNCTION__,rch->th_seq_num);
+#endif
+
+        if(rc != 0)
+                ccw_check_return_code(wch, rc,"send sweep");
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s() %s\n",
+                        __FUNCTION__,
+                        ach->id);
+#endif
+        return;
+
+}
+
+
+static void
+mpc_rcvd_sweep_resp(struct mpcg_info *mpcginfo)
+{
+        struct channel    *rch = mpcginfo->ch;
+        struct net_device *dev = rch->netdev;
+        struct ctc_priv   *privptr = (struct ctc_priv *)dev->priv;
+        struct mpc_group  *grpptr = privptr->mpcg;
+        struct channel    *ch = privptr->channel[WRITE];
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+
+#ifdef DEBUGDATA
+        dumpit((char *)mpcginfo->sweep,TH_SWEEP_LENGTH);
+#endif
+
+
+        grpptr->sweep_rsp_pend_num--;
+
+        if((grpptr->sweep_req_pend_num == 0) &&
+           (grpptr->sweep_rsp_pend_num == 0))
+        {
+                fsm_deltimer(&ch->sweep_timer);
+                grpptr->in_sweep = 0;
+                rch->th_seq_num = 0x00;
+                ch->th_seq_num = 0x00;
+                ctcmpc_clear_busy(dev);
+        }
+
+        kfree(mpcginfo);
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return;
+
+}
+
+
+static void
+ctcmpc_send_sweep_req(struct channel *rch)
+{
+        struct net_device *dev = rch->netdev;
+        struct ctc_priv *privptr = (struct ctc_priv *)dev->priv;
+        struct mpc_group *grpptr = privptr->mpcg;
+        struct th_sweep *header;
+        struct sk_buff *sweep_skb;
+        int rc = 0;
+        struct channel *ch = privptr->channel[WRITE];
+
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+
+        /* sweep processing is not complete until response and request */
+        /* has completed for all read channels in group                */
+        if(grpptr->in_sweep == 0)
+        {
+                grpptr->in_sweep = 1;
+                grpptr->sweep_rsp_pend_num = grpptr->active_channels[READ];
+                grpptr->sweep_req_pend_num = grpptr->active_channels[READ];
+        }
+
+
+        sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT,
+                                    GFP_ATOMIC|GFP_DMA);
+
+        if(sweep_skb == NULL)
+        {
+                printk(KERN_INFO "Couldn't alloc sweep_skb\n");
+                rc = -ENOMEM;
+                goto done;
+        }
+
+        header = (struct th_sweep *)kmalloc(TH_SWEEP_LENGTH, gfp_type());
+
+        if(!header)
+        {
+                dev_kfree_skb_any(sweep_skb);
+                rc = -ENOMEM;
+                goto done;
+        }
+
+        header->th.th_seg       = 0x00 ;
+        header->th.th_ch_flag   = TH_SWEEP_REQ;  /* 0x0f */
+        header->th.th_blk_flag  = 0x00;
+        header->th.th_is_xid    = 0x00;
+        header->th.th_seq_num   = 0x00;
+        header->sw.th_last_seq  = ch->th_seq_num;
+
+        memcpy(skb_put(sweep_skb,TH_SWEEP_LENGTH),header,TH_SWEEP_LENGTH);
+
+        kfree(header);
+
+        dev->trans_start = jiffies;
+        skb_queue_tail(&ch->sweep_queue,sweep_skb);
+
+        fsm_addtimer(&ch->sweep_timer,100,CH_EVENT_RSWEEP1_TIMER,ch);
+
+        return;
+
+
+        done:
+        if(rc != 0)
+        {
+                grpptr->in_sweep = 0;
+                ctcmpc_clear_busy(dev);
+                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+        }
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return;
+}
+
+static void
+ctcmpc_send_sweep_resp(struct channel *rch)
+{
+        struct net_device *dev = rch->netdev;
+        struct ctc_priv *privptr = (struct ctc_priv *)dev->priv;
+        struct mpc_group *grpptr = privptr->mpcg;
+        int rc = 0;
+        struct th_sweep *header;
+        struct sk_buff *sweep_skb;
+        struct channel *ch  = privptr->channel[WRITE];
+
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, rch, rch->id);
+#endif
+        sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT,
+                                    GFP_ATOMIC|GFP_DMA);
+        if(sweep_skb == NULL)
+        {
+                printk(KERN_INFO
+                       "Couldn't alloc sweep_skb\n");
+                rc = -ENOMEM;
+                goto done;
+        }
+
+        header = (struct th_sweep *)kmalloc(sizeof(struct th_sweep),
+                                            gfp_type());
+
+        if(!header)
+        {
+                dev_kfree_skb_any(sweep_skb);
+                rc = -ENOMEM;
+                goto done;
+        }
+
+        header->th.th_seg       = 0x00 ;
+        header->th.th_ch_flag   = TH_SWEEP_RESP;
+        header->th.th_blk_flag  = 0x00;
+        header->th.th_is_xid    = 0x00;
+        header->th.th_seq_num   = 0x00;
+        header->sw.th_last_seq  = ch->th_seq_num;
+
+        memcpy(skb_put(sweep_skb,TH_SWEEP_LENGTH),header,TH_SWEEP_LENGTH);
+
+        kfree(header);
+
+        dev->trans_start = jiffies;
+        skb_queue_tail(&ch->sweep_queue,sweep_skb);
+
+        fsm_addtimer(&ch->sweep_timer,100,CH_EVENT_RSWEEP1_TIMER,ch);
+
+        return;
+
+        done:
+        if(rc != 0)
+        {
+                grpptr->in_sweep = 0;
+                ctcmpc_clear_busy(dev);
+                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+        }
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return;
+
+}
+
+static void
+mpc_rcvd_sweep_req(struct mpcg_info *mpcginfo)
+{
+        struct channel     *rch        = mpcginfo->ch;
+        struct net_device  *dev        = rch->netdev;
+        struct ctc_priv    *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group   *grpptr     = privptr->mpcg;
+        struct channel     *ch         = privptr->channel[WRITE];
+
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        if(grpptr->in_sweep == 0)
+        {
+                grpptr->in_sweep = 1;
+                ctcmpc_test_and_set_busy(dev);
+                grpptr->sweep_req_pend_num = grpptr->active_channels[READ];
+                grpptr->sweep_rsp_pend_num = grpptr->active_channels[READ];
+        }
+
+#ifdef DEBUGDATA
+        dumpit((char *)mpcginfo->sweep,TH_SWEEP_LENGTH);
+#endif
+
+
+        grpptr->sweep_req_pend_num --;
+
+        ctcmpc_send_sweep_resp(ch);
+
+        kfree(mpcginfo);
+
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+        return;
+}
+
+
+static void
+mpc_action_go_ready(fsm_instance *fsm,int event, void *arg)
+{
+        struct net_device *dev = (struct net_device *)arg;
+        struct ctc_priv *privptr = NULL;
+        struct mpc_group *grpptr = NULL;
+
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        ctcmpc_pr_debug("ctcmpc enter: %s  %s()\n", dev->name,__FUNCTION__);
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "%s() privptr=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "%s() grpptr=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        fsm_deltimer(&grpptr->timer);
+
+        if(grpptr->saved_xid2->xid2_flag2 == 0x40)
+        {
+                privptr->xid->xid2_flag2 = 0x00;
+                if(grpptr->estconnfunc)
+                {
+                        grpptr->estconnfunc(grpptr->port_num,1,
+                                            grpptr->group_max_buflen);
+                        grpptr->estconnfunc = NULL;
+                } else
+                        if(grpptr->allochanfunc)
+                        grpptr->send_qllc_disc = 1;
+                goto done;
+        }
+
+        grpptr->port_persist = 1;
+        grpptr->out_of_sequence = 0;
+        grpptr->estconn_called = 0;
+
+        tasklet_hi_schedule(&grpptr->mpc_tasklet2);
+
+        ctcmpc_pr_debug("ctcmpc exit: %s  %s()\n", dev->name,__FUNCTION__);
+        return;
+
+        done:
+        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+
+
+        ctcmpc_pr_info("ctcmpc: %s()failure occurred\n", __FUNCTION__);
+}
+
+
+static void
+ctc_mpc_group_ready(unsigned long adev)
+{
+        struct net_device *dev = (struct net_device *)adev;
+        struct ctc_priv *privptr = NULL;
+        struct mpc_group  *grpptr = NULL;
+        struct channel *ch = NULL;
+
+
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO "%s() privptr=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        grpptr = privptr->mpcg;
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "ctcmpc:%s() grpptr=NULL\n",__FUNCTION__);
+                return;
+        }
+
+        printk(KERN_NOTICE "ctcmpc: %s GROUP TRANSITIONED TO READY"
+               "  maxbuf:%d\n",
+               dev->name,grpptr->group_max_buflen);
+
+        fsm_newstate(grpptr->fsm, MPCG_STATE_READY);
+
+        /* Put up a read on the channel */
+        ch = privptr->channel[READ];
+        ch->pdu_seq = 0;
+#ifdef DEBUGDATA
+        ctcmpc_pr_debug("ctcmpc: %s() ToDCM_pdu_seq= %08x\n" ,
+                       __FUNCTION__,ch->pdu_seq);
+#endif
+
+        ctcmpc_ch_action_rxidle(ch->fsm, CH_EVENT_START, ch);
+        /* Put the write channel in idle state */
+        ch = privptr->channel[WRITE];
+        if(ch->collect_len > 0)
+        {
+                spin_lock(&ch->collect_lock);
+                ctcmpc_purge_skb_queue(&ch->collect_queue);
+                ch->collect_len = 0;
+                spin_unlock(&ch->collect_lock);
+        }
+        ctcmpc_ch_action_txidle(ch->fsm, CH_EVENT_START, ch);
+
+        ctcmpc_clear_busy(dev);
+
+        if(grpptr->estconnfunc)
+        {
+                grpptr->estconnfunc(grpptr->port_num,0,
+                                    grpptr->group_max_buflen);
+                grpptr->estconnfunc = NULL;
+        } else
+                if(grpptr->allochanfunc)
+                grpptr->allochanfunc(grpptr->port_num,
+                                     grpptr->group_max_buflen);
+
+        grpptr->send_qllc_disc = 1;
+        grpptr->changed_side = 0;
+
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+        return;
+
+}
+
+/****************************************************************/
+/* Increment the MPC Group Active Channel Counts                */
+/****************************************************************/
+static int
+mpc_channel_action(struct channel *ch, int direction, int action)
+{
+        struct net_device  *dev     = ch->netdev;
+        struct ctc_priv    *privptr;
+        struct mpc_group   *grpptr  = NULL;
+        int         rc = 0;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        if(dev == NULL)
+        {
+                printk(KERN_INFO "ctcmpc_channel_action %i dev=NULL\n",
+                       action);
+                rc = 1;
+                goto done;
+        }
+
+        privptr = (struct ctc_priv *)dev->priv;
+        if(privptr == NULL)
+        {
+                printk(KERN_INFO
+                       "ctcmpc_channel_action%i privptr=NULL, dev=%s\n",
+                       action,dev->name);
+                rc = 2;
+                goto done;
+        }
+
+        grpptr = privptr->mpcg;
+
+        if(grpptr == NULL)
+        {
+                printk(KERN_INFO "ctcmpc: %s()%i mpcgroup=NULL, dev=%s\n",
+                       __FUNCTION__,action,dev->name);
+                rc = 3;
+                goto done;
+        }
+
+        ctcmpc_pr_info(
+                      "ctcmpc: %s() %i(): Grp:%s total_channel_paths=%i "
+                      "active_channels read=%i,write=%i\n",
+                      __FUNCTION__,
+                      action,
+                      fsm_getstate_str(grpptr->fsm),
+                      grpptr->num_channel_paths,
+                      grpptr->active_channels[READ],
+                      grpptr->active_channels[WRITE]);
+
+        switch(action)
+        {
+                case MPC_CHANNEL_ADD:
+                        if(ch->in_mpcgroup == 0)
+                        {
+                                grpptr->num_channel_paths++;
+                                grpptr->active_channels[direction]++;
+                                grpptr->outstanding_xid2++;
+                                ch->in_mpcgroup = 1;
+
+                                if(ch->xid_skb != NULL)
+                                        dev_kfree_skb_any(ch->xid_skb);
+                                ch->xid_skb =
+                                        __dev_alloc_skb(MPC_BUFSIZE_DEFAULT,
+                                                        GFP_ATOMIC|GFP_DMA);
+                                if(ch->xid_skb == NULL)
+                                {
+                                        printk(KERN_INFO "ctcmpc: %s()"
+                                               "Couldn't alloc ch xid_skb\n",
+                                               __FUNCTION__);
+                                        fsm_event(grpptr->fsm,
+                                                  MPCG_EVENT_INOP,dev);
+                                        return 1;
+                                }
+
+                                ch->xid_skb_data = ch->xid_skb->data;
+                                ch->xid_th =
+                                        (struct th_header *)ch->xid_skb->data;
+                                skb_put(ch->xid_skb,TH_HEADER_LENGTH);
+                                ch->xid = (struct xid2 *)ch->xid_skb->tail;
+                                skb_put(ch->xid_skb,XID2_LENGTH);
+                                ch->xid_id = ch->xid_skb->tail;
+                                ch->xid_skb->data =  ch->xid_skb->tail =
+                                                     ch->xid_skb_data;
+                                ch->xid_skb->len = 0;
+
+
+                                memcpy(skb_put(ch->xid_skb,
+                                               grpptr->xid_skb->len),
+                                       grpptr->xid_skb->data,
+                                       grpptr->xid_skb->len);
+
+                                ch->xid->xid2_dlc_type =
+                                ((CHANNEL_DIRECTION(ch->flags) == READ)
+                                 ? XID2_READ_SIDE : XID2_WRITE_SIDE );
+
+                                if(CHANNEL_DIRECTION(ch->flags) == WRITE)
+                                        ch->xid->xid2_buf_len = 0x00;
+
+
+                                ch->xid_skb->data = ch->xid_skb->tail =
+                                                    ch->xid_skb_data;
+                                ch->xid_skb->len = 0;
+
+                                fsm_newstate(ch->fsm,CH_XID0_PENDING);
+                                if((grpptr->active_channels[READ]  > 0) &&
+                                   (grpptr->active_channels[WRITE] > 0) &&
+                                   (fsm_getstate(grpptr->fsm) <
+                                    MPCG_STATE_XID2INITW))
+                                {
+                                        fsm_newstate(grpptr->fsm,
+                                                     MPCG_STATE_XID2INITW);
+                                        printk(KERN_NOTICE
+                                               "ctcmpc: %s MPC GROUP "
+                                               "CHANNELS ACTIVE\n",dev->name);
+                                }
+
+
+                        }
+                        break;
+                case MPC_CHANNEL_REMOVE:
+                        if(ch->in_mpcgroup == 1)
+                        {
+                                ch->in_mpcgroup = 0;
+                                grpptr->num_channel_paths--;
+                                grpptr->active_channels[direction]--;
+
+                                if(ch->xid_skb != NULL)
+                                        dev_kfree_skb_any(ch->xid_skb);
+                                ch->xid_skb = NULL;
+
+                                if(grpptr->channels_terminating)
+                                        break;
+
+                                if( ((grpptr->active_channels[READ]  == 0) &&
+                                     (grpptr->active_channels[WRITE] > 0)) ||
+                                    ((grpptr->active_channels[WRITE] == 0) &&
+                                     (grpptr->active_channels[READ]  > 0)) )
+                                        fsm_event(grpptr->fsm,
+                                                  MPCG_EVENT_INOP,
+                                                  dev);
+                        }
+                        break;
+        }
+
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug(
+                       "ctcmpc: %s() %i Grp:%s ttl_chan_paths=%i "
+                       "active_chans read=%i,write=%i\n",
+                       __FUNCTION__,
+                       action,
+                       fsm_getstate_str(grpptr->fsm),
+                       grpptr->num_channel_paths,
+                       grpptr->active_channels[READ],
+                       grpptr->active_channels[WRITE]);
+
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return(rc);
+
+}
+
+
+/**
+ * Unpack a just received skb and hand it over to
+ * upper layers.
+ *
+ * @param ch The channel where this skb has been received.
+ * @param pskb The received skb.
+ */
+static __inline__ void
+ctcmpc_unpack_skb(struct channel *ch, struct sk_buff *pskb)
+{
+        struct net_device *dev  = ch->netdev;
+        struct ctc_priv *privptr = (struct ctc_priv *)dev->priv;
+        struct mpc_group *grpptr = privptr->mpcg;
+        struct pdu *curr_pdu;
+        struct mpcg_info *mpcginfo;
+        struct th_header *header = NULL;
+        struct th_sweep *sweep = NULL;
+        int pdu_last_seen = 0;
+        __u32 new_len;
+        struct sk_buff *skb;
+        int sendrc = 0;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s() %s cp:%i ch:%s\n",
+                       __FUNCTION__,
+                       dev->name,
+                       smp_processor_id(),
+                       ch->id);
+#endif
+
+        header = (struct th_header *)pskb->data;
+        if((header->th_seg == 0) &&
+           (header->th_ch_flag == 0) &&
+           (header->th_blk_flag == 0) &&
+           (header->th_seq_num == 0) )
+                goto done;  /* nothing for us */
+
+#ifdef DEBUGDATA
+        ctcmpc_pr_debug("ctcmpc: %s() th_header\n", __FUNCTION__);
+        dumpit((char *)header,TH_HEADER_LENGTH);
+        ctcmpc_pr_debug("ctcmpc: %s() pskb len: %04x \n",
+                       __FUNCTION__,
+                       pskb->len);
+#endif
+
+        pskb->dev = dev;
+        pskb->ip_summed = CHECKSUM_UNNECESSARY;
+        spin_lock(&ch->segment_lock);  /* make sure we are alone here */
+
+        skb_pull(pskb,TH_HEADER_LENGTH);
+
+        if(likely(header->th_ch_flag == TH_HAS_PDU))
+        {
+
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("ctcmpc: %s() came into th_has_pdu\n",
+                               __FUNCTION__);
+#endif
+
+                if((fsm_getstate(grpptr->fsm) == MPCG_STATE_FLOWC) ||
+                   ((fsm_getstate(grpptr->fsm) == MPCG_STATE_READY) &&
+                    (header->th_seq_num != ch->th_seq_num + 1) &&
+                    (ch->th_seq_num != 0)))
+                {
+                        /* This isn't the next segment          *
+                         * we are not the correct race winner   *
+                         * go away and let someone else win     *
+                         * BUT..this only applies if xid negot  *
+                         * is done                              *
+                        */
+                        grpptr->out_of_sequence +=1;
+                        __skb_push(pskb,TH_HEADER_LENGTH);
+                        spin_unlock(&ch->segment_lock);
+                        skb_queue_tail(&ch->io_queue, pskb);
+#ifdef DEBUGDATA
+                        ctcmpc_pr_debug("ctcmpc: %s() th_seq_num "
+                                       "expect:%08x got:%08x\n",
+                                       __FUNCTION__,
+                                       ch->th_seq_num + 1,
+                                       header->th_seq_num);
+#endif
+                        return;
+                }
+                grpptr->out_of_sequence = 0;
+                ch->th_seq_num = header->th_seq_num;
+
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("ctcmpc: %s() FromVTAM_th_seq=%08x\n",
+                               __FUNCTION__,
+                               ch->th_seq_num);
+#endif
+                pdu_last_seen = 0;
+                if(fsm_getstate(grpptr->fsm) == MPCG_STATE_READY)
+                        while((pskb->len > 0) && !pdu_last_seen)
+                        {
+                                curr_pdu = (struct pdu *)pskb->data;
+#ifdef DEBUGDATA
+                                ctcmpc_pr_debug("ctcmpc: %s() pdu_header\n",
+                                               __FUNCTION__);
+                                dumpit((char *)pskb->data,PDU_HEADER_LENGTH);
+                                ctcmpc_pr_debug("ctcmpc: %s() pskb len: %04x \n",
+                                               __FUNCTION__,pskb->len);
+#endif
+                                skb_pull(pskb,PDU_HEADER_LENGTH);
+                                if(curr_pdu->pdu_flag & PDU_LAST)
+                                        pdu_last_seen = 1;
+                                if(curr_pdu->pdu_flag & PDU_CNTL)
+                                        pskb->protocol = htons(ETH_P_SNAP);
+                                else
+                                        pskb->protocol = htons(ETH_P_SNA_DIX);
+                                if((pskb->len <= 0) ||
+                                   (pskb->len > ch->max_bufsize))
+                                {
+                                        printk(KERN_INFO
+                                               "%s Illegal packet size %d "
+                                               "received "
+                                               "dropping\n", dev->name,
+                                               pskb->len);
+                                        privptr->stats.rx_dropped++;
+                                        privptr->stats.rx_length_errors++;
+                                        spin_unlock(&ch->segment_lock);
+                                        goto done;
+                                }
+                                pskb->mac.raw = pskb->data;
+                                new_len = curr_pdu->pdu_offset;
+#ifdef DEBUGDATA
+                                ctcmpc_pr_debug("ctcmpc: %s() new_len: %04x \n",
+                                               __FUNCTION__,
+                                               new_len);
+#endif
+                                if((new_len == 0) ||
+                                   (new_len > pskb->len))
+                                {
+                                        /* should never happen              */
+                                        /* pskb len must be hosed...bail out */
+                                        printk(KERN_INFO
+                                               "ctcmpc: %s(): invalid pdu"
+                                               " offset of %04x - data may be"
+                                               "lost\n", __FUNCTION__,new_len);
+                                        spin_unlock(&ch->segment_lock);
+                                        goto done;
+                                }
+                                skb = __dev_alloc_skb(new_len+4,GFP_ATOMIC);
+
+                                if(!skb)
+                                {
+                                        printk(KERN_INFO
+                                               "ctcmpc: %s Out of memory in "
+                                               "%s()- request-len:%04x \n",
+                                               dev->name,
+                                               __FUNCTION__,
+                                               new_len+4);
+                                        privptr->stats.rx_dropped++;
+                                        spin_unlock(&ch->segment_lock);
+                                        fsm_event(grpptr->fsm,
+                                                  MPCG_EVENT_INOP,dev);
+                                        goto done;
+                                }
+
+                                memcpy(skb_put(skb, new_len),
+                                       pskb->data, new_len);
+
+                                skb->mac.raw = skb->data;
+                                skb->dev = pskb->dev;
+                                skb->protocol = pskb->protocol;
+                                skb->ip_summed = CHECKSUM_UNNECESSARY;
+                                *((__u32 *) skb_push(skb, 4)) = ch->pdu_seq;
+                                ch->pdu_seq++;
+
+#ifdef DEBUGDATA
+                                ctcmpc_pr_debug("%s: ToDCM_pdu_seq= %08x\n" ,
+                                               __FUNCTION__,
+                                               ch->pdu_seq);
+#endif
+
+                                ctcmpc_pr_debug("ctcmpc: %s() skb:%0lx "
+                                               "skb len: %d \n",
+                                               __FUNCTION__,
+                                               (unsigned long)skb,
+                                               skb->len);
+#ifdef DEBUGDATA
+                                __u32 out_len;
+                                if(skb->len > 32) out_len = 32;
+                                else out_len = skb->len;
+                                ctcmpc_pr_debug("ctcmpc: %s() up to 32 bytes"
+                                               " of pdu_data sent\n",
+                                               __FUNCTION__);
+                                dumpit((char *)skb->data,out_len);
+#endif
+
+                                sendrc = netif_rx(skb);
+                                privptr->stats.rx_packets++;
+                                privptr->stats.rx_bytes += skb->len;
+                                skb_pull(pskb, new_len); /* point to next PDU */
+                        }
+        } else
+        {
+                if((mpcginfo =
+                    (struct mpcg_info *)kmalloc(sizeof(struct mpcg_info),
+                                                gfp_type())) == NULL)
+                {
+                        spin_unlock(&ch->segment_lock);
+                        goto done;
+                }
+
+                mpcginfo->ch = ch;
+                mpcginfo->th = header;
+                mpcginfo->skb = pskb;
+                ctcmpc_pr_debug("ctcmpc: %s() Not PDU - may be control pkt\n",
+                               __FUNCTION__);
+                /*  it's a sweep?   */
+                sweep = (struct th_sweep *) pskb->data;
+                mpcginfo->sweep = sweep;
+                if(header->th_ch_flag == TH_SWEEP_REQ)
+                        mpc_rcvd_sweep_req(mpcginfo);
+                else
+                        if(header->th_ch_flag == TH_SWEEP_RESP)
+                        mpc_rcvd_sweep_resp(mpcginfo);
+                else
+                {
+                        if(header->th_blk_flag == TH_DATA_IS_XID)
+                        {
+                                struct xid2 *thisxid =
+                                (struct xid2 *)pskb->data;
+                                skb_pull(pskb,XID2_LENGTH);
+                                mpcginfo->xid = thisxid;
+                                fsm_event(grpptr->fsm,
+                                          MPCG_EVENT_XID2,
+                                          mpcginfo);
+                        } else
+                        {
+                                if(header->th_blk_flag ==
+                                   TH_DISCONTACT)
+                                {
+                                        fsm_event(grpptr->fsm,
+                                                  MPCG_EVENT_DISCONC,
+                                                  mpcginfo);
+                                } else
+                                        if(header->th_seq_num != 0)
+                                {
+                                        printk(KERN_INFO
+                                               "%s unexpected packet"
+                                               " expected control pkt\n",
+                                               dev->name);
+                                        privptr->stats.rx_dropped++;
+                                        /* mpcginfo only used for
+                                        non-data transfers */
+                                        kfree(mpcginfo);
+#ifdef DEBUGDATA
+                                        ctcmpc_dump_skb(pskb, -8);
+#endif
+                                }
+                        }
+                }
+        }
+        spin_unlock(&ch->segment_lock);
+        done:
+
+        dev_kfree_skb_any(pskb);
+        switch(sendrc)
+        {
+                case NET_RX_DROP:
+                        printk(KERN_WARNING "%s %s() NETWORK BACKLOG EXCEEDED"
+                               " - PACKET DROPPED\n",
+                               dev->name,
+                               __FUNCTION__);
+                        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                        break;
+                case NET_RX_SUCCESS:
+                case NET_RX_CN_LOW:
+                case NET_RX_CN_MOD:
+                case NET_RX_CN_HIGH:
+                default:
+                        break;
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n",
+                        dev->name,
+                        __func__,
+                        ch,
+                        ch->id);
+#endif
+}
+
+/**
+ * Bottom half routine.
+ *
+ * @param ch The channel to work on.
+ * Allow flow control back pressure to occur here.
+ * Throttling back channel can result in excessive
+ * channel inactivity and system deact of channel
+ */
+static void
+ctcmpc_bh(unsigned long thischan)
+{
+        struct channel    *ch         = (struct channel *)thischan;
+        struct sk_buff    *peek_skb   = NULL;
+        struct sk_buff    *skb;
+        struct sk_buff    *same_skb   = NULL;
+        struct net_device *dev        = ch->netdev;
+        struct ctc_priv   *privptr    = (struct ctc_priv *)dev->priv;
+        struct mpc_group  *grpptr     = privptr->mpcg;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("%s cp:%i enter:  %s() %s\n",
+                       dev->name,smp_processor_id(),__FUNCTION__,ch->id);
+#endif
+        /* caller has requested driver to throttle back */
+        if(fsm_getstate(grpptr->fsm) == MPCG_STATE_FLOWC)
+        {
+                goto done;
+        } else
+        {
+                while((skb = skb_dequeue(&ch->io_queue)))
+                {
+                        same_skb = skb;
+                        ctcmpc_unpack_skb(ch, skb);
+                        if(grpptr->out_of_sequence > 20)
+                        {
+                                /* assume data loss has occurred if */
+                                /* missing seq_num for extended     */
+                                /* period of time                   */
+                                grpptr->out_of_sequence = 0;
+                                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                                goto done;
+                        }
+                        peek_skb = skb_peek(&ch->io_queue);
+                        if(peek_skb == same_skb)
+                                goto done;
+                        if(fsm_getstate(grpptr->fsm) == MPCG_STATE_FLOWC)
+                                goto done;
+                }
+        }
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n",
+                        dev->name,
+                        __func__,
+                        ch,
+                        ch->id);
+#endif
+        return;
+}
+
+
+/**
+ * Check return code of a preceeding ccw_device call, halt_IO etc...
+ *
+ * @param ch          The channel, the error belongs to.
+ * @param return_code The error code to inspect.
+ */
+static void inline
+ccw_check_return_code(struct channel *ch, int return_code, char *msg)
+{
+        switch(return_code)
+        {
+                case 0:
+                        fsm_event(ch->fsm, CH_EVENT_IO_SUCCESS, ch);
+                        break;
+                case -EBUSY:
+                        ctcmpc_pr_warn("%s (%s): Busy !\n", ch->id, msg);
+                        fsm_event(ch->fsm, CH_EVENT_IO_EBUSY, ch);
+                        break;
+                case -ENODEV:
+                        ctcmpc_pr_emerg(
+                                "%s (%s):Invalid device called for IO\n",
+                                        ch->id, msg);
+                        fsm_event(ch->fsm, CH_EVENT_IO_ENODEV, ch);
+                        break;
+                case -EIO:
+                        ctcmpc_pr_emerg("%s (%s): Status pending... \n",
+                                        ch->id, msg);
+                        fsm_event(ch->fsm, CH_EVENT_IO_EIO, ch);
+                        break;
+                default:
+                        ctcmpc_pr_emerg("%s (%s): Unknown error in IO %04x\n",
+                                        ch->id, msg, return_code);
+                        fsm_event(ch->fsm, CH_EVENT_IO_UNKNOWN, ch);
+        }
+}
+
+/**
+ * Check sense of a unit check.
+ *
+ * @param ch    The channel, the sense code belongs to.
+ * @param sense The sense code to inspect.
+ */
+static void inline
+ccw_unit_check(struct channel *ch, unsigned char sense)
+{
+        if(sense & SNS0_INTERVENTION_REQ)
+        {
+                if(sense & 0x01)
+                {
+                        ctcmpc_pr_debug("%s: Interface disc. or Sel. reset "
+                                        "(remote)\n", ch->id);
+                        fsm_event(ch->fsm, CH_EVENT_UC_RCRESET, ch);
+                } else
+                {
+                        ctcmpc_pr_debug("%s: System reset (remote)\n", ch->id);
+                        fsm_event(ch->fsm, CH_EVENT_UC_RSRESET, ch);
+                }
+        } else if(sense & SNS0_EQUIPMENT_CHECK)
+        {
+                if(sense & SNS0_BUS_OUT_CHECK)
+                {
+                        ctcmpc_pr_warn("%s: Hardware malfunction (remote)\n",
+                                       ch->id);
+                        fsm_event(ch->fsm, CH_EVENT_UC_HWFAIL, ch);
+                } else
+                {
+                        ctcmpc_pr_warn("%s: Read-data parity error (remote)\n",
+                                       ch->id);
+                        fsm_event(ch->fsm, CH_EVENT_UC_RXPARITY, ch);
+                }
+        } else if(sense & SNS0_BUS_OUT_CHECK)
+        {
+                if(sense & 0x04)
+                {
+                        ctcmpc_pr_warn("%s: Data-streaming timeout)\n",
+                                       ch->id);
+                        fsm_event(ch->fsm, CH_EVENT_UC_TXTIMEOUT, ch);
+                } else
+                {
+                        ctcmpc_pr_warn("%s: Data-transfer parity error\n",
+                                       ch->id);
+                        fsm_event(ch->fsm, CH_EVENT_UC_TXPARITY, ch);
+                }
+        } else if(sense & SNS0_CMD_REJECT)
+        {
+                ctcmpc_pr_warn("%s: Command reject\n", ch->id);
+        } else if(sense == 0)
+        {
+                ctcmpc_pr_debug("%s: Unit check ZERO\n", ch->id);
+                fsm_event(ch->fsm, CH_EVENT_UC_ZERO, ch);
+        } else
+        {
+                ctcmpc_pr_warn("%s: Unit Check with sense code: %02x\n",
+                               ch->id, sense);
+                fsm_event(ch->fsm, CH_EVENT_UC_UNKNOWN, ch);
+        }
+}
+
+static void
+ctcmpc_purge_skb_queue(struct sk_buff_head *q)
+{
+        struct sk_buff *skb;
+
+        while((skb = skb_dequeue(q)))
+        {
+                atomic_dec(&skb->users);
+                dev_kfree_skb_any(skb);
+        }
+}
+
+static __inline__ int
+ctcmpc_checkalloc_buffer(struct channel *ch, int warn)
+{
+        if((ch->trans_skb == NULL) ||
+           (ch->flags & CHANNEL_FLAGS_BUFSIZE_CHANGED))
+        {
+                if(ch->trans_skb != NULL)
+                        dev_kfree_skb_any(ch->trans_skb);
+                clear_normalized_cda(&ch->ccw[1]);
+                ch->trans_skb = __dev_alloc_skb(ch->max_bufsize,
+                                                GFP_ATOMIC | GFP_DMA);
+                if(ch->trans_skb == NULL)
+                {
+                        if(warn)
+                                ctcmpc_pr_warn(
+                                        "%s: Couldn't alloc %s trans_skb\n",
+                                        ch->id,
+                                        (CHANNEL_DIRECTION(ch->flags) == READ) ?
+                                        "RX" : "TX");
+                        return -ENOMEM;
+                }
+                ch->ccw[1].count = ch->max_bufsize;
+                if(set_normalized_cda(&ch->ccw[1], ch->trans_skb->data))
+                {
+                        dev_kfree_skb_any(ch->trans_skb);
+                        ch->trans_skb = NULL;
+                        if(warn)
+                                ctcmpc_pr_warn(
+                                              "%s: set_normalized_cda for %s "
+                                              "trans_skb failed, dropping "
+                                              "packets\n", ch->id,
+                                              (CHANNEL_DIRECTION(ch->flags)
+                                               == READ) ?
+                                              "RX" : "TX");
+                        return -ENOMEM;
+                }
+                ch->ccw[1].count = 0;
+                ch->trans_skb_data = ch->trans_skb->data;
+                ch->flags &= ~CHANNEL_FLAGS_BUFSIZE_CHANGED;
+        }
+        return 0;
+}
+
+
+/**
+ * Actions for channel - statemachines.
+ *****************************************************************************/
+
+/**
+ * Normal data has been send. Free the corresponding
+ * skb (it's in io_queue), reset dev->tbusy and
+ * revert to idle state.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_txdone(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+        struct ctc_priv *privptr = dev->priv;
+        struct mpc_group      *grpptr  = privptr->mpcg;
+        struct sk_buff *skb;
+        int first = 1;
+        int i;
+        struct timespec done_stamp;
+        __u32 data_space;
+        unsigned long duration;
+        struct sk_buff *peekskb;
+#ifdef DEBUGDATA
+        __u32                out_len = 0;
+#endif
+
+        ctcmpc_pr_debug("%s cp:%i enter:  %s()\n",
+                       dev->name,smp_processor_id(),__FUNCTION__);
+        done_stamp = xtime;
+        duration =
+        (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 +
+        (done_stamp.tv_nsec - ch->prof.send_stamp.tv_nsec) / 1000;
+        if(duration > ch->prof.tx_time)
+                ch->prof.tx_time = duration;
+
+        if(ch->irb->scsw.count != 0)
+                ctcmpc_pr_debug("%s: TX not complete, remaining %d bytes\n",
+                                dev->name, ch->irb->scsw.count);
+        fsm_deltimer(&ch->timer);
+        while((skb = skb_dequeue(&ch->io_queue)))
+        {
+                privptr->stats.tx_packets++;
+                privptr->stats.tx_bytes += skb->len - TH_HEADER_LENGTH;
+                if(first)
+                {
+                        privptr->stats.tx_bytes += 2;
+                        first = 0;
+                }
+                atomic_dec(&skb->users);
+                dev_kfree_skb_irq(skb);
+        }
+        spin_lock(&ch->collect_lock);
+        clear_normalized_cda(&ch->ccw[4]);
+        if((ch->collect_len > 0) && (grpptr->in_sweep == 0))
+        {
+                int rc;
+                struct th_header        *header;
+                struct pdu      *p_header = NULL;
+
+                if(ctcmpc_checkalloc_buffer(ch, 1))
+                {
+                        spin_unlock(&ch->collect_lock);
+                        goto done;
+                }
+                ch->trans_skb->tail = ch->trans_skb->data = ch->trans_skb_data;
+                ch->trans_skb->len = 0;
+                if(ch->prof.maxmulti < (ch->collect_len + TH_HEADER_LENGTH))
+                        ch->prof.maxmulti = ch->collect_len + TH_HEADER_LENGTH;
+                if(ch->prof.maxcqueue < skb_queue_len(&ch->collect_queue))
+                        ch->prof.maxcqueue = skb_queue_len(&ch->collect_queue);
+                i = 0;
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("ctcmpc: %s() building "
+                               "trans_skb from collect_q \n", __FUNCTION__);
+#endif
+
+                data_space = grpptr->group_max_buflen - TH_HEADER_LENGTH;
+
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("ctcmpc: %s() building trans_skb from collect_q"
+                               " data_space:%04x\n",
+                               __FUNCTION__,data_space);
+#endif
+                while((skb = skb_dequeue(&ch->collect_queue)))
+                {
+                        memcpy(skb_put(ch->trans_skb, skb->len), skb->data,
+                               skb->len);
+                        p_header =
+                                (struct pdu *)(ch->trans_skb->tail - skb->len);
+                        p_header->pdu_flag = 0x00;
+                        if(skb->protocol == ntohs(ETH_P_SNAP))
+                        {
+                                p_header->pdu_flag |= 0x60;
+                        } else
+                        {
+                                p_header->pdu_flag |= 0x20;
+                        }
+
+#ifdef DEBUGDATA
+                        __u32            out_len = 0;
+                        ctcmpc_pr_debug("ctcmpc: %s()trans_skb len:%04x \n",
+                                       __FUNCTION__,ch->trans_skb->len);
+                        if(skb->len > 32) out_len = 32;
+                        else out_len = skb->len;
+                        ctcmpc_pr_debug("ctcmpc: %s() pdu header and data"
+                                       " for up to 32 bytes sent to vtam\n",
+                                       __FUNCTION__);
+                        dumpit((char *)p_header,out_len);
+#endif
+                        ch->collect_len -= skb->len;
+                        data_space -= skb->len;
+                        privptr->stats.tx_packets++;
+                        privptr->stats.tx_bytes += skb->len;
+                        atomic_dec(&skb->users);
+                        dev_kfree_skb_any(skb);
+                        peekskb = skb_peek(&ch->collect_queue);
+                        if(peekskb->len > data_space)
+                                break;
+                        i++;
+                }
+                /* p_header points to the last one we handled */
+                if(p_header)
+                        p_header->pdu_flag |= PDU_LAST;/*Say it's the last one*/
+                header = (struct th_header *)kmalloc(TH_HEADER_LENGTH,
+                                                     gfp_type());
+
+                if(!header)
+                {
+                        printk(KERN_WARNING "ctcmpc: OUT OF MEMORY IN %s()"
+                               ": Data Lost \n",
+                               __FUNCTION__);
+                        spin_unlock(&ch->collect_lock);
+                        fsm_event(privptr->mpcg->fsm,MPCG_EVENT_INOP,dev);
+                        goto done;
+                }
+                header->th_seg = 0x00;
+                header->th_ch_flag = TH_HAS_PDU;  /* Normal data */
+                header->th_blk_flag = 0x00;
+                header->th_is_xid = 0x00;
+                ch->th_seq_num++;
+                header->th_seq_num = ch->th_seq_num;
+
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("%s: ToVTAM_th_seq= %08x\n" ,
+                               __FUNCTION__,
+                               ch->th_seq_num);
+#endif
+                memcpy(skb_push(ch->trans_skb, TH_HEADER_LENGTH), header,
+                       TH_HEADER_LENGTH);       /*  put the TH on the packet */
+
+                kfree(header);
+
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("ctcmpc: %s()trans_skb len:%04x \n",
+                               __FUNCTION__,
+                               ch->trans_skb->len);
+                if(ch->trans_skb->len > 50) out_len = 50;
+                else out_len = ch->trans_skb->len;
+                ctcmpc_pr_debug("ctcmpc: %s() up-to-50 bytes of trans_skb"
+                               " data to vtam from collect_q\n",
+                               __FUNCTION__);
+                dumpit((char *)ch->trans_skb->data,out_len);
+#endif
+
+                spin_unlock(&ch->collect_lock);
+                clear_normalized_cda(&ch->ccw[1]);
+                if(set_normalized_cda(&ch->ccw[1],ch->trans_skb->data))
+                {
+                        dev_kfree_skb_any(ch->trans_skb);
+                        ch->trans_skb = NULL;
+                        printk(KERN_WARNING
+                               "ctcmpc: %s()CCW failure - data lost\n",
+                               __FUNCTION__);
+                        fsm_event(privptr->mpcg->fsm,MPCG_EVENT_INOP,dev);
+                        return;
+                }
+                ch->ccw[1].count = ch->trans_skb->len;
+                fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+                ch->prof.send_stamp = xtime;
+#ifdef DEBUGCCW
+                dumpit((char *)&ch->ccw[0],sizeof(struct ccw1) * 3);
+#endif
+                rc = ccw_device_start(ch->cdev, &ch->ccw[0],
+                                      (unsigned long) ch, 0xff, 0);
+                ch->prof.doios_multi++;
+                if(rc != 0)
+                {
+                        privptr->stats.tx_dropped += i;
+                        privptr->stats.tx_errors += i;
+                        fsm_deltimer(&ch->timer);
+                        ccw_check_return_code(ch, rc, "chained TX");
+                }
+        } else
+        {
+                spin_unlock(&ch->collect_lock);
+                fsm_newstate(fi, CH_STATE_TXIDLE);
+        }
+        done:
+        ctcmpc_clear_busy(dev);
+        ctcmpc_pr_debug("ctcmpc exit: %s  %s()\n", dev->name,__FUNCTION__);
+        return;
+}
+
+/**
+ * Initial data is sent.
+ * Notify device statemachine that we are up and
+ * running.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_txidle(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+        fsm_deltimer(&ch->timer);
+        fsm_newstate(fi, CH_STATE_TXIDLE);
+        fsm_event(((struct ctc_priv *) ch->netdev->priv)->fsm, DEV_EVENT_TXUP,
+                  ch->netdev);
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+}
+
+/**
+ * Got normal data, check for sanity, queue it up, allocate new buffer
+ * trigger bottom half, and initiate next read.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_rx(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+        struct ctc_priv *privptr = dev->priv;
+        struct mpc_group *grpptr = privptr->mpcg;
+        int len = ch->max_bufsize - ch->irb->scsw.count;
+        struct sk_buff *skb = ch->trans_skb;
+        struct sk_buff    *new_skb;
+        int rc = 0;
+        __u32 block_len;
+        unsigned long saveflags;
+        int gotlock     = 0;
+
+
+        ctcmpc_pr_debug("ctcmpc enter: %s() %s cp:%i %s\n",
+                       __FUNCTION__,
+                       dev->name,
+                       smp_processor_id(),
+                       ch->id);
+#ifdef DEBUGDATA
+        ctcmpc_pr_debug("ctcmpc:%s() max_bufsize:%04x len:%04x\n",
+                       __FUNCTION__,ch->max_bufsize,len);
+#endif
+
+        fsm_deltimer(&ch->timer);
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+
+        if(skb == NULL)
+        {
+                ctcmpc_pr_debug("ctcmpc exit:  %s() TRANS_SKB = NULL \n",
+                               __FUNCTION__);
+                goto again;
+        }
+
+        if(len < TH_HEADER_LENGTH )
+        {
+                ctcmpc_pr_info("%s: got packet with invalid length %d\n",
+                               dev->name, len);
+                privptr->stats.rx_dropped++;
+                privptr->stats.rx_length_errors++;
+                goto again;
+        } else
+        {
+                /* must have valid th header or game over */
+                block_len = len;
+                len = TH_HEADER_LENGTH + XID2_LENGTH + 4;
+                new_skb = __dev_alloc_skb(ch->max_bufsize,GFP_ATOMIC);
+
+                if(new_skb == NULL)
+                {
+                        printk(KERN_INFO "ctcmpc:%s() NEW_SKB = NULL\n",
+                               __FUNCTION__);
+                        printk(KERN_WARNING "ctcmpc: %s() MEMORY ALLOC FAILED"
+                               " - DATA LOST - MPC FAILED\n",
+                               __FUNCTION__);
+                        fsm_event(privptr->mpcg->fsm,MPCG_EVENT_INOP,dev);
+                        goto again;
+                }
+                switch(fsm_getstate(grpptr->fsm))
+                {
+                        case MPCG_STATE_RESET:
+                        case MPCG_STATE_INOP:
+                                dev_kfree_skb_any(new_skb);
+                                goto again;
+                        case MPCG_STATE_FLOWC:
+                        case MPCG_STATE_READY:
+                                memcpy(skb_put(new_skb, block_len),
+                                       skb->data,
+                                       block_len);
+                                skb_queue_tail(&ch->io_queue, new_skb);
+                                tasklet_schedule(&ch->ch_tasklet);
+                                goto again;
+                        default:
+                                memcpy(skb_put(new_skb, len),
+                                       skb->data,len);
+                                skb_queue_tail(&ch->io_queue, new_skb);
+                                tasklet_hi_schedule(&ch->ch_tasklet);
+                                goto again;
+                }
+
+        }
+
+        again:
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_FLOWC:
+                case MPCG_STATE_READY:
+                        if(ctcmpc_checkalloc_buffer(ch, 1))
+                                break;
+                        ch->trans_skb->data = ch->trans_skb->tail =
+                                              ch->trans_skb_data;
+                        ch->trans_skb->len = 0;
+                        ch->ccw[1].count = ch->max_bufsize;
+#ifdef DEBUGCCW
+                        dumpit((char *)&ch->ccw[0],sizeof(struct ccw1) * 3);
+#endif
+                        if(!in_irq())
+                        {
+                                spin_lock_irqsave(get_ccwdev_lock(ch->cdev),
+                                                  saveflags);
+                                gotlock = 1;
+                        }
+                        rc = ccw_device_start(ch->cdev, &ch->ccw[0],
+                                              (unsigned long) ch,
+                                              0xff,0);
+                        if(gotlock)
+                                spin_unlock_irqrestore(
+                                        get_ccwdev_lock(ch->cdev),
+                                                       saveflags);
+                        if(rc != 0)
+                                ccw_check_return_code(ch, rc, "normal RX");
+                        break;
+                default:
+                        break;
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s %s(): ch=0x%p id=%s\n",
+                        dev->name,__func__, ch, ch->id);
+#endif
+
+}
+
+static void ctcmpc_ch_action_rxidle(fsm_instance * fi, int event, void *arg);
+
+/**
+ * Initialize connection by sending a __u16 of value 0.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_firstio(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+
+#ifdef DEBUG
+        struct net_device *dev = ch->netdev;
+        struct mpc_group *grpptr = ((struct ctc_priv *)dev->priv)->mpcg;
+        ctcmpc_pr_debug("ctcmpc enter: %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+        ctcmpc_pr_debug("%s() %s chstate:%i grpstate:%i chprotocol:%i\n",
+                       __FUNCTION__,
+                       ch->id,
+                       fsm_getstate(fi),
+                       fsm_getstate(grpptr->fsm),
+                       ch->protocol);
+#endif
+        if(fsm_getstate(fi) == CH_STATE_TXIDLE)
+                ctcmpc_pr_debug("%s: remote side issued READ?,"
+                                " init ...\n", ch->id);
+        fsm_deltimer(&ch->timer);
+        if(ctcmpc_checkalloc_buffer(ch, 1))
+                goto done;
+
+        if(ch->protocol == CTC_PROTO_MPC)
+                switch(fsm_getstate(fi))
+                {
+                        case CH_STATE_STARTRETRY:
+                        case CH_STATE_SETUPWAIT:
+                                if(CHANNEL_DIRECTION(ch->flags) == READ)
+                                {
+                                        ctcmpc_ch_action_rxidle(fi, event, arg);
+                                } else
+                                {
+                                        struct net_device *dev = ch->netdev;
+                                        fsm_newstate(fi, CH_STATE_TXIDLE);
+                                        fsm_event(
+                                            ((struct ctc_priv *)dev->priv)->fsm,
+                                                  DEV_EVENT_TXUP, dev);
+                                }
+                                goto done;
+                        default:
+                                break;
+
+                };
+
+        fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ)
+                     ? CH_STATE_RXINIT : CH_STATE_TXINIT);
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return;
+}
+
+/**
+ * Got initial data, check it. If OK,
+ * notify device statemachine that we are up and
+ * running.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_rxidle(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+        struct ctc_priv   *privptr = (struct ctc_priv *)dev->priv;
+        struct mpc_group  *grpptr = privptr->mpcg;
+        int rc;
+        unsigned long saveflags;
+
+        fsm_deltimer(&ch->timer);
+        ctcmpc_pr_debug("%s cp:%i enter:  %s()\n",
+                       dev->name,smp_processor_id(),__FUNCTION__);
+#ifdef DEBUG
+        ctcmpc_pr_debug("%s() %s chstate:%i grpstate:%i\n",
+                       __FUNCTION__, ch->id,
+                       fsm_getstate(fi),
+                       fsm_getstate(grpptr->fsm));
+#endif
+
+
+        fsm_newstate(fi, CH_STATE_RXIDLE);
+        /* XID processing complete */
+
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        switch(fsm_getstate(grpptr->fsm))
+        {
+                case MPCG_STATE_FLOWC:
+                case MPCG_STATE_READY:
+                        if(ctcmpc_checkalloc_buffer(ch, 1)) goto done;
+                        ch->trans_skb->data =  ch->trans_skb->tail =
+                                               ch->trans_skb_data;
+                        ch->trans_skb->len = 0;
+                        ch->ccw[1].count = ch->max_bufsize;
+#ifdef DEBUGCCW
+                        dumpit((char *)&ch->ccw[0],
+                               sizeof(struct ccw1) * 3);
+#endif
+                        if(event == CH_EVENT_START)
+                                spin_lock_irqsave(get_ccwdev_lock(ch->cdev),
+                                                  saveflags);
+                        rc = ccw_device_start(ch->cdev, &ch->ccw[0],
+                                              (unsigned long) ch, 0xff, 0);
+                        if(event == CH_EVENT_START)
+                                spin_unlock_irqrestore(
+                                        get_ccwdev_lock(ch->cdev),saveflags);
+                        if(rc != 0)
+                        {
+                                fsm_newstate(fi, CH_STATE_RXINIT);
+                                ccw_check_return_code(ch, rc, "initial RX");
+                                goto done;
+                        }
+                        break;
+                default:
+                        break;
+        }
+        fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                  DEV_EVENT_RXUP, dev);
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit: %s  %s()\n", dev->name,__FUNCTION__);
+#endif
+        return;
+}
+
+/**
+ * Set channel into extended mode.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_setmode(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        int rc;
+        unsigned long saveflags;
+
+        fsm_deltimer(&ch->timer);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+        fsm_addtimer(&ch->timer, 1500, CH_EVENT_TIMER, ch);
+        fsm_newstate(fi, CH_STATE_SETUPWAIT);
+#ifdef DEBUGCCW
+        dumpit((char *)&ch->ccw[6],sizeof(struct ccw1) * 2);
+#endif
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        if(event == CH_EVENT_TIMER)
+                spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+        rc = ccw_device_start(ch->cdev,&ch->ccw[6],(unsigned long) ch,0xff,0);
+        if(event == CH_EVENT_TIMER)
+                spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+        if(rc != 0)
+        {
+                fsm_deltimer(&ch->timer);
+                fsm_newstate(fi, CH_STATE_STARTWAIT);
+                ccw_check_return_code(ch, rc, "set Mode");
+        } else
+                ch->retry = 0;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Setup channel.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_start(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        unsigned long saveflags;
+        int rc;
+        struct net_device *dev;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        if(ch == NULL)
+        {
+                ctcmpc_pr_warn("ctcmpc_ch_action_start ch=NULL\n");
+                goto done;
+        }
+        if(ch->netdev == NULL)
+        {
+                ctcmpc_pr_warn("ctcmpc_ch_action_start dev=NULL, id=%s\n",
+                               ch->id);
+                goto done;
+        }
+        dev = ch->netdev;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("%s: %s channel start\n", dev->name,
+                        (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+#endif
+
+        if(ch->trans_skb != NULL)
+        {
+                clear_normalized_cda(&ch->ccw[1]);
+                dev_kfree_skb(ch->trans_skb);
+                ch->trans_skb = NULL;
+        }
+        if(CHANNEL_DIRECTION(ch->flags) == READ)
+        {
+                ch->ccw[1].cmd_code = CCW_CMD_READ;
+                ch->ccw[1].flags = CCW_FLAG_SLI;
+                ch->ccw[1].count = 0;
+        } else
+        {
+                ch->ccw[1].cmd_code = CCW_CMD_WRITE;
+                ch->ccw[1].flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+                ch->ccw[1].count = 0;
+        }
+        if(ctcmpc_checkalloc_buffer(ch, 0))
+        {
+                ctcmpc_pr_notice(
+                                "%s: Could not allocate %s trans_skb, delaying "
+                                "allocation until first transfer\n",
+                                dev->name,
+                                (CHANNEL_DIRECTION(ch->flags)
+                                 == READ) ? "RX" : "TX");
+        }
+
+        ch->ccw[0].cmd_code = CCW_CMD_PREPARE;
+        ch->ccw[0].flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[0].count = 0;
+        ch->ccw[0].cda = 0;
+        ch->ccw[2].cmd_code = CCW_CMD_NOOP;     /* jointed CE + DE */
+        ch->ccw[2].flags = CCW_FLAG_SLI;
+        ch->ccw[2].count = 0;
+        ch->ccw[2].cda = 0;
+        memcpy(&ch->ccw[3], &ch->ccw[0], sizeof (struct ccw1) * 3);
+        ch->ccw[4].cda = 0;
+        ch->ccw[4].flags &= ~CCW_FLAG_IDA;
+
+        fsm_newstate(fi, CH_STATE_STARTWAIT);
+        fsm_addtimer(&ch->timer, 1000, CH_EVENT_TIMER, ch);
+        spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+        rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+        spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+        if(rc != 0)
+        {
+                if(rc != -EBUSY)
+                        fsm_deltimer(&ch->timer);
+                ccw_check_return_code(ch, rc, "initial HaltIO");
+        }
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc: %s(): leaving\n", __FUNCTION__);
+#endif
+        return;
+}
+
+/**
+ * Shutdown a channel.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_haltio(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        unsigned long saveflags;
+        int rc;
+        int oldstate;
+        int gotlock = 0;
+
+        fsm_deltimer(&ch->timer);
+        fsm_deltimer(&ch->sweep_timer);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        if(event == CH_EVENT_STOP)
+                spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+        gotlock = 1;
+        oldstate = fsm_getstate(fi);
+        fsm_newstate(fi, CH_STATE_TERM);
+        rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+        if(gotlock)
+                spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+        if(rc != 0)
+        {
+                if(rc != -EBUSY)
+                {
+                        fsm_deltimer(&ch->timer);
+                        /* When I say stop..that means STOP */
+                        if(event != CH_EVENT_STOP)
+                        {
+                                fsm_newstate(fi, oldstate);
+                                ccw_check_return_code(ch, rc,
+                                           "HaltIO in ctcmpc_ch_action_haltio");
+                        }
+                }
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * A channel has successfully been halted.
+ * Cleanup it's queue and notify interface statemachine.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_stopped(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+
+        fsm_deltimer(&ch->timer);
+        fsm_deltimer(&ch->sweep_timer);
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        fsm_newstate(fi, CH_STATE_STOPPED);
+        if(ch->trans_skb != NULL)
+        {
+                clear_normalized_cda(&ch->ccw[1]);
+                dev_kfree_skb_any(ch->trans_skb);
+                ch->trans_skb = NULL;
+        }
+        ch->th_seg = 0x00;
+        ch->th_seq_num = 0x00;
+
+#ifdef DEBUGDATA
+        ctcmpc_pr_debug("ctcmpc: %s() CH_th_seq= %08x\n" ,
+                       __FUNCTION__,
+                       ch->th_seq_num);
+#endif
+        if(CHANNEL_DIRECTION(ch->flags) == READ)
+        {
+                skb_queue_purge(&ch->io_queue);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_RXDOWN, dev);
+        } else
+        {
+                ctcmpc_purge_skb_queue(&ch->io_queue);
+                ctcmpc_purge_skb_queue(&ch->sweep_queue);
+                spin_lock(&ch->collect_lock);
+                ctcmpc_purge_skb_queue(&ch->collect_queue);
+                ch->collect_len = 0;
+                spin_unlock(&ch->collect_lock);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_TXDOWN, dev);
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * A stop command from device statemachine arrived and we are in
+ * not operational mode. Set state to stopped.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_stop(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        fsm_newstate(fi, CH_STATE_STOPPED);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * A machine check for no path, not operational status or gone device has
+ * happened.
+ * Cleanup queue and notify interface statemachine.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_fail(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+
+        fsm_deltimer(&ch->timer);
+        fsm_deltimer(&ch->sweep_timer);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        fsm_newstate(fi, CH_STATE_NOTOP);
+        ch->th_seg = 0x00;
+        ch->th_seq_num = 0x00;
+
+#ifdef DEBUGDATA
+        ctcmpc_pr_debug("ctcmpc: %s() CH_th_seq= %08x\n" ,
+                       __FUNCTION__,
+                       ch->th_seq_num);
+#endif
+        if(CHANNEL_DIRECTION(ch->flags) == READ)
+        {
+                skb_queue_purge(&ch->io_queue);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_RXDOWN, dev);
+        } else
+        {
+                ctcmpc_purge_skb_queue(&ch->io_queue);
+                ctcmpc_purge_skb_queue(&ch->sweep_queue);
+                spin_lock(&ch->collect_lock);
+                ctcmpc_purge_skb_queue(&ch->collect_queue);
+                ch->collect_len = 0;
+                spin_unlock(&ch->collect_lock);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_TXDOWN, dev);
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Handle error during setup of channel.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_setuperr(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        /**
+         * Special case: Got UC_RCRESET on setmode.
+         * This means that remote side isn't setup. In this case
+         * simply retry after some 10 secs...
+         */
+        if((fsm_getstate(fi) == CH_STATE_SETUPWAIT) &&
+           ((event == CH_EVENT_UC_RCRESET) ||
+            (event == CH_EVENT_UC_RSRESET)))
+        {
+                fsm_newstate(fi, CH_STATE_STARTRETRY);
+                fsm_deltimer(&ch->timer);
+                fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+//		if (CHANNEL_DIRECTION(ch->flags) == READ) {
+//			int rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+//			if (rc != 0)
+//				ccw_check_return_code(
+//				  ch, rc, "HaltIO in ctcmpc_ch_action_setuperr");
+//		}
+                goto done;
+        }
+
+        ctcmpc_pr_debug("%s: Error %s during %s channel setup state=%s\n",
+                        dev->name, ch_event_names[event],
+                        (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX",
+                        fsm_getstate_str(fi));
+        if(CHANNEL_DIRECTION(ch->flags) == READ)
+        {
+                fsm_newstate(fi, CH_STATE_RXERR);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_RXDOWN, dev);
+        } else
+        {
+                fsm_newstate(fi, CH_STATE_TXERR);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_TXDOWN, dev);
+        }
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+        return;
+}
+
+/**
+ * Restart a channel after an error.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_restart(fsm_instance * fi, int event, void *arg)
+{
+        unsigned long saveflags;
+        int oldstate;
+        int rc;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+
+        fsm_deltimer(&ch->timer);
+        ctcmpc_pr_debug("%s: %s channel restart\n", dev->name,
+                        (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+        fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        oldstate = fsm_getstate(fi);
+        fsm_newstate(fi, CH_STATE_STARTWAIT);
+        if(event == CH_EVENT_TIMER)
+                spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+        rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+        if(event == CH_EVENT_TIMER)
+                spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+        if(rc != 0)
+        {
+                if(rc != -EBUSY)
+                {
+                        fsm_deltimer(&ch->timer);
+                        fsm_newstate(fi, oldstate);
+                }
+                ccw_check_return_code(ch, rc,
+                                      "HaltIO in ctcmpc_ch_action_restart");
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Handle error during RX initial handshake (exchange of
+ * 0-length block header)
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_rxiniterr(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        if(event == CH_EVENT_TIMER)
+        {
+                ctcmpc_pr_debug("%s: Timeout during RX init "
+                                "handshake\n",
+                                dev->name);
+                if(ch->retry++ < 3)
+                        ctcmpc_ch_action_restart(fi, event, arg);
+                else
+                {
+                        fsm_newstate(fi, CH_STATE_RXERR);
+                        fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                                  DEV_EVENT_RXDOWN, dev);
+                }
+        } else
+                ctcmpc_pr_warn("%s: Error during RX "
+                               "init handshake\n",
+                               dev->name);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Notify device statemachine if we gave up initialization
+ * of RX channel.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_rxinitfail(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+
+        fsm_newstate(fi, CH_STATE_RXERR);
+        ctcmpc_pr_warn("%s: RX initialization failed\n", dev->name);
+        ctcmpc_pr_warn("%s: RX <-> RX connection detected\n", dev->name);
+        fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+}
+
+/**
+ * Handle RX Unit check remote reset (remote disconnected)
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_rxdisc(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct channel *ch2;
+        struct net_device *dev = ch->netdev;
+
+        fsm_deltimer(&ch->timer);
+        ctcmpc_pr_debug("%s: Got remote disconnect, re-initializing ...\n",
+                        dev->name);
+
+        /**
+         * Notify device statemachine
+         */
+        fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+        fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
+
+        fsm_newstate(fi, CH_STATE_DTERM);
+        ch2 = ((struct ctc_priv *) dev->priv)->channel[WRITE];
+        fsm_newstate(ch2->fsm, CH_STATE_DTERM);
+
+        ccw_device_halt(ch->cdev, (unsigned long) ch);
+        ccw_device_halt(ch2->cdev, (unsigned long) ch2);
+}
+
+/**
+ * Handle error during TX channel initialization.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_txiniterr(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+
+        if(event == CH_EVENT_TIMER)
+        {
+                fsm_deltimer(&ch->timer);
+                ctcmpc_pr_debug("%s: Timeout during TX "
+                                "init handshake\n",
+                                dev->name);
+                if(ch->retry++ < 3)
+                        ctcmpc_ch_action_restart(fi, event, arg);
+                else
+                {
+                        fsm_newstate(fi, CH_STATE_TXERR);
+                        fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                                  DEV_EVENT_TXDOWN, dev);
+                }
+        } else
+                ctcmpc_pr_warn("%s: Error during TX "
+                               "init handshake\n", dev->name);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Handle TX timeout by retrying operation.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_txretry(fsm_instance * fi, int event, void *arg)
+{
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+        unsigned long saveflags;
+
+        struct ctc_priv *privptr = (struct ctc_priv *)dev->priv;
+        struct mpc_group *grpptr = privptr->mpcg;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): cp=%i ch=0x%p id=%s\n",
+                        __func__, smp_processor_id(),ch, ch->id);
+#endif
+
+        fsm_deltimer(&ch->timer);
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        if(ch->retry++ > 3)
+        {
+                ctcmpc_pr_debug("%s: TX retry failed, restarting channel\n",
+                                dev->name);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_TXDOWN, dev);
+                if((grpptr) && (fsm_getstate(grpptr->fsm) == MPCG_STATE_READY))
+                        ctcmpc_ch_action_restart(fi, event, arg);
+        } else
+        {
+                struct sk_buff *skb;
+
+                ctcmpc_pr_debug("%s: TX retry %d\n", dev->name, ch->retry);
+                if((skb = skb_peek(&ch->io_queue)))
+                {
+                        int rc = 0;
+
+                        clear_normalized_cda(&ch->ccw[4]);
+                        ch->ccw[4].count = skb->len;
+                        if(set_normalized_cda(&ch->ccw[4], skb->data))
+                        {
+                                ctcmpc_pr_debug("%s: IDAL alloc failed, "
+                                                "chan restart\n", dev->name);
+                                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                                          DEV_EVENT_TXDOWN, dev);
+                                ctcmpc_ch_action_restart(fi, event, arg);
+                                goto done;
+                        }
+                        fsm_addtimer(&ch->timer, 1000, CH_EVENT_TIMER, ch);
+                        if(event == CH_EVENT_TIMER)
+                                spin_lock_irqsave(get_ccwdev_lock(ch->cdev),
+                                                  saveflags);
+#ifdef DEBUGCCW
+                        dumpit((char *)&ch->ccw[3],sizeof(struct ccw1) * 3);
+#endif
+                        rc = ccw_device_start(ch->cdev, &ch->ccw[3],
+                                              (unsigned long) ch, 0xff, 0);
+                        if(event == CH_EVENT_TIMER)
+                                spin_unlock_irqrestore(get_ccwdev_lock(
+                                        ch->cdev),saveflags);
+                        if(rc != 0)
+                        {
+                                fsm_deltimer(&ch->timer);
+                                ccw_check_return_code(ch,
+                                                      rc,
+                                              "TX in ctcmpc_ch_action_txretry");
+                                ctcmpc_purge_skb_queue(&ch->io_queue);
+                        }
+                }
+        }
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+        return;
+
+}
+
+/**
+ * Handle fatal errors during an I/O command.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ctcmpc_ch_action_iofatal(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct channel *ch = (struct channel *) arg;
+        struct net_device *dev = ch->netdev;
+        struct ctc_priv   *privptr = (struct ctc_priv *)dev->priv;
+
+        fsm_deltimer(&ch->timer);
+        printk(KERN_WARNING "ctcmpc: %s() UNRECOVERABLE CHANNEL ERR - "
+               "CHANNEL REMOVED FROM MPC GROUP\n",
+               __FUNCTION__);
+        privptr->stats.tx_dropped++;
+        privptr->stats.tx_errors++;
+        if(CHANNEL_DIRECTION(ch->flags) == READ)
+        {
+                ctcmpc_pr_debug("%s: RX I/O error\n", dev->name);
+                fsm_newstate(fi, CH_STATE_RXERR);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_RXDOWN, dev);
+        } else
+        {
+                ctcmpc_pr_debug("%s: TX I/O error\n", dev->name);
+                fsm_newstate(fi, CH_STATE_TXERR);
+                fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+                          DEV_EVENT_TXDOWN, dev);
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+//static void
+//ctcmpc_ch_action_reinit(fsm_instance *fi, int event, void *arg)
+//{
+// 	struct channel *ch = (struct channel *)arg;
+// 	struct net_device *dev = ch->netdev;
+// 	struct ctc_priv *privptr = dev->priv;
+//
+// 	ctcmpc_ch_action_iofatal(fi, event, arg);
+// 	fsm_addtimer(&privptr->restart_timer, 1000, DEV_EVENT_RESTART, dev);
+//}
+
+
+/**
+ * The statemachine for a channel.
+ */
+static const fsm_node ch_fsm[] = {
+        {CH_STATE_STOPPED,    CH_EVENT_STOP,      fsm_action_nop},
+        {CH_STATE_STOPPED,    CH_EVENT_START,     ctcmpc_ch_action_start},
+        {CH_STATE_STOPPED,    CH_EVENT_IO_ENODEV, ctcmpc_ch_action_iofatal},
+        {CH_STATE_STOPPED,    CH_EVENT_FINSTAT,   fsm_action_nop},
+        {CH_STATE_STOPPED,    CH_EVENT_MC_FAIL,   fsm_action_nop},
+
+        {CH_STATE_NOTOP,      CH_EVENT_STOP,       ctcmpc_ch_action_stop},
+        {CH_STATE_NOTOP,      CH_EVENT_START,      fsm_action_nop},
+        {CH_STATE_NOTOP,      CH_EVENT_FINSTAT,    fsm_action_nop},
+        {CH_STATE_NOTOP,      CH_EVENT_MC_FAIL,    fsm_action_nop},
+        {CH_STATE_NOTOP,      CH_EVENT_MC_GOOD,    ctcmpc_ch_action_start},
+        {CH_STATE_NOTOP,      CH_EVENT_UC_RCRESET, ctcmpc_ch_action_stop},
+        {CH_STATE_NOTOP,      CH_EVENT_UC_RSRESET, ctcmpc_ch_action_stop},
+        {CH_STATE_NOTOP,      CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+
+        {CH_STATE_STARTWAIT,     CH_EVENT_STOP,      ctcmpc_ch_action_haltio},
+        {CH_STATE_STARTWAIT,     CH_EVENT_START,     fsm_action_nop},
+        {CH_STATE_STARTWAIT,     CH_EVENT_FINSTAT,   ctcmpc_ch_action_setmode},
+        {CH_STATE_STARTWAIT,     CH_EVENT_TIMER,     ctcmpc_ch_action_setuperr},
+        {CH_STATE_STARTWAIT,     CH_EVENT_IO_ENODEV, ctcmpc_ch_action_iofatal},
+        {CH_STATE_STARTWAIT,     CH_EVENT_IO_EIO,    ctcmpc_ch_action_iofatal},
+        {CH_STATE_STARTWAIT,     CH_EVENT_MC_FAIL,   ctcmpc_ch_action_fail},
+
+        {CH_STATE_STARTRETRY,    CH_EVENT_STOP,      ctcmpc_ch_action_haltio},
+        {CH_STATE_STARTRETRY,    CH_EVENT_TIMER,     ctcmpc_ch_action_setmode},
+        {CH_STATE_STARTRETRY,    CH_EVENT_FINSTAT,   ctcmpc_ch_action_setmode},
+        {CH_STATE_STARTRETRY,    CH_EVENT_MC_FAIL,   ctcmpc_ch_action_fail},
+        {CH_STATE_STARTRETRY,    CH_EVENT_IO_ENODEV, ctcmpc_ch_action_iofatal},
+
+        {CH_STATE_SETUPWAIT,     CH_EVENT_STOP,      ctcmpc_ch_action_haltio},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_START,     fsm_action_nop},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_FINSTAT,   ctcmpc_ch_action_firstio},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_UC_RCRESET,ctcmpc_ch_action_setuperr},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_UC_RSRESET,ctcmpc_ch_action_setuperr},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_TIMER,     ctcmpc_ch_action_setmode},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_IO_ENODEV, ctcmpc_ch_action_iofatal},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_IO_EIO,    ctcmpc_ch_action_iofatal},
+        {CH_STATE_SETUPWAIT,     CH_EVENT_MC_FAIL,   ctcmpc_ch_action_fail},
+
+        {CH_STATE_RXINIT,     CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        {CH_STATE_RXINIT,     CH_EVENT_START,      fsm_action_nop},
+        {CH_STATE_RXINIT,     CH_EVENT_FINSTAT,    ctcmpc_ch_action_rxidle},
+        {CH_STATE_RXINIT,     CH_EVENT_UC_RCRESET, ctcmpc_ch_action_rxiniterr},
+        {CH_STATE_RXINIT,     CH_EVENT_UC_RSRESET, ctcmpc_ch_action_rxiniterr},
+        {CH_STATE_RXINIT,     CH_EVENT_TIMER,      ctcmpc_ch_action_rxiniterr},
+        {CH_STATE_RXINIT,     CH_EVENT_ATTNBUSY,   ctcmpc_ch_action_rxinitfail},
+        {CH_STATE_RXINIT,     CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        {CH_STATE_RXINIT,     CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        {CH_STATE_RXINIT,     CH_EVENT_UC_ZERO,    ctcmpc_ch_action_firstio},
+        {CH_STATE_RXINIT,     CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+
+        { CH_XID0_PENDING,      CH_EVENT_FINSTAT,    fsm_action_nop},
+        { CH_XID0_PENDING,      CH_EVENT_ATTN,       ctcmpc_action_attn},
+        { CH_XID0_PENDING,      CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        { CH_XID0_PENDING,      CH_EVENT_START,      fsm_action_nop},
+        { CH_XID0_PENDING,      CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        { CH_XID0_PENDING,      CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        { CH_XID0_PENDING,      CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        { CH_XID0_PENDING,      CH_EVENT_UC_RCRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID0_PENDING,      CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID0_PENDING,      CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID0_PENDING,      CH_EVENT_ATTNBUSY,   ctcmpc_ch_action_iofatal},
+
+
+        { CH_XID0_INPROGRESS,  CH_EVENT_FINSTAT,     ctcmpc_ch_action_rx},
+        { CH_XID0_INPROGRESS,  CH_EVENT_ATTN,        ctcmpc_action_attn},
+        { CH_XID0_INPROGRESS,  CH_EVENT_STOP,        ctcmpc_ch_action_haltio},
+        { CH_XID0_INPROGRESS,  CH_EVENT_START,       fsm_action_nop},
+        { CH_XID0_INPROGRESS,  CH_EVENT_IO_ENODEV,   ctcmpc_ch_action_iofatal},
+        { CH_XID0_INPROGRESS,  CH_EVENT_IO_EIO,      ctcmpc_ch_action_iofatal},
+        { CH_XID0_INPROGRESS,  CH_EVENT_MC_FAIL,     ctcmpc_ch_action_fail},
+        { CH_XID0_INPROGRESS,  CH_EVENT_UC_ZERO,     ctcmpc_ch_action_rx},
+        { CH_XID0_INPROGRESS,  CH_EVENT_UC_RCRESET,  ctcmpc_ch_action_setuperr},
+        { CH_XID0_INPROGRESS,  CH_EVENT_ATTNBUSY,    ctcmpc_action_attnbusy},
+        { CH_XID0_INPROGRESS,  CH_EVENT_TIMER,       ctcmpc_action_resend},
+        { CH_XID0_INPROGRESS,  CH_EVENT_IO_EBUSY,    ctcmpc_ch_action_fail},
+
+
+
+        { CH_XID7_PENDING,      CH_EVENT_FINSTAT,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING,      CH_EVENT_ATTN,       ctcmpc_action_attn},
+        { CH_XID7_PENDING,      CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        { CH_XID7_PENDING,      CH_EVENT_START,      fsm_action_nop},
+        { CH_XID7_PENDING,      CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING,      CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING,      CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        { CH_XID7_PENDING,      CH_EVENT_UC_ZERO,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING,      CH_EVENT_UC_RCRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING,      CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING,      CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING,      CH_EVENT_ATTNBUSY,   ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING,      CH_EVENT_TIMER,      ctcmpc_action_resend},
+        { CH_XID7_PENDING,      CH_EVENT_IO_EBUSY,   ctcmpc_ch_action_fail},
+
+
+        { CH_XID7_PENDING1,     CH_EVENT_FINSTAT,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING1,     CH_EVENT_ATTN,       ctcmpc_action_attn},
+        { CH_XID7_PENDING1,     CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        { CH_XID7_PENDING1,     CH_EVENT_START,      fsm_action_nop},
+        { CH_XID7_PENDING1,     CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING1,     CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING1,     CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        { CH_XID7_PENDING1,     CH_EVENT_UC_ZERO,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING1,     CH_EVENT_UC_RCRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING1,     CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING1,     CH_EVENT_ATTNBUSY,   ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING1,     CH_EVENT_TIMER,      ctcmpc_action_resend},
+        { CH_XID7_PENDING1,     CH_EVENT_IO_EBUSY,   ctcmpc_ch_action_fail},
+
+        { CH_XID7_PENDING2,     CH_EVENT_FINSTAT,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING2,     CH_EVENT_ATTN,       ctcmpc_action_attn},
+        { CH_XID7_PENDING2,     CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        { CH_XID7_PENDING2,     CH_EVENT_START,      fsm_action_nop},
+        { CH_XID7_PENDING2,     CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING2,     CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING2,     CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        { CH_XID7_PENDING2,     CH_EVENT_UC_ZERO,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING2,     CH_EVENT_UC_RCRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING2,     CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING2,     CH_EVENT_ATTNBUSY,   ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING2,     CH_EVENT_TIMER,      ctcmpc_action_resend},
+        { CH_XID7_PENDING2,     CH_EVENT_IO_EBUSY,   ctcmpc_ch_action_fail},
+
+
+        { CH_XID7_PENDING3,     CH_EVENT_FINSTAT,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING3,     CH_EVENT_ATTN,       ctcmpc_action_attn},
+        { CH_XID7_PENDING3,     CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        { CH_XID7_PENDING3,     CH_EVENT_START,      fsm_action_nop},
+        { CH_XID7_PENDING3,     CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING3,     CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING3,     CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        { CH_XID7_PENDING3,     CH_EVENT_UC_ZERO,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING3,     CH_EVENT_UC_RCRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING3,     CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING3,     CH_EVENT_ATTNBUSY,   ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING3,     CH_EVENT_TIMER,      ctcmpc_action_resend},
+        { CH_XID7_PENDING3,     CH_EVENT_IO_EBUSY,   ctcmpc_ch_action_fail},
+
+
+        { CH_XID7_PENDING4,     CH_EVENT_FINSTAT,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING4,     CH_EVENT_ATTN,       ctcmpc_action_attn},
+        { CH_XID7_PENDING4,     CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        { CH_XID7_PENDING4,     CH_EVENT_START,      fsm_action_nop},
+        { CH_XID7_PENDING4,     CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING4,     CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING4,     CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        { CH_XID7_PENDING4,     CH_EVENT_UC_ZERO,    ctcmpc_ch_action_rx},
+        { CH_XID7_PENDING4,     CH_EVENT_UC_RCRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING4,     CH_EVENT_UC_RSRESET, ctcmpc_ch_action_setuperr},
+        { CH_XID7_PENDING4,     CH_EVENT_ATTNBUSY,   ctcmpc_ch_action_iofatal},
+        { CH_XID7_PENDING4,     CH_EVENT_TIMER,      ctcmpc_action_resend},
+        { CH_XID7_PENDING4,     CH_EVENT_IO_EBUSY,   ctcmpc_ch_action_fail},
+
+        {CH_STATE_RXIDLE,     CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        {CH_STATE_RXIDLE,     CH_EVENT_START,      fsm_action_nop},
+        {CH_STATE_RXIDLE,     CH_EVENT_FINSTAT,    ctcmpc_ch_action_rx},
+        {CH_STATE_RXIDLE,     CH_EVENT_UC_RCRESET, ctcmpc_ch_action_rxdisc},
+        {CH_STATE_RXIDLE,     CH_EVENT_UC_RSRESET, ctcmpc_ch_action_fail},
+        {CH_STATE_RXIDLE,     CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        {CH_STATE_RXIDLE,     CH_EVENT_IO_EIO,     ctcmpc_ch_action_iofatal},
+        {CH_STATE_RXIDLE,     CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        {CH_STATE_RXIDLE,     CH_EVENT_UC_ZERO,    ctcmpc_ch_action_rx},
+
+        {CH_STATE_TXINIT,     CH_EVENT_STOP,        ctcmpc_ch_action_haltio},
+        {CH_STATE_TXINIT,     CH_EVENT_START,       fsm_action_nop},
+        {CH_STATE_TXINIT,     CH_EVENT_FINSTAT,     ctcmpc_ch_action_txidle},
+        {CH_STATE_TXINIT,     CH_EVENT_UC_RCRESET,  ctcmpc_ch_action_txiniterr},
+        {CH_STATE_TXINIT,     CH_EVENT_UC_RSRESET,  ctcmpc_ch_action_txiniterr},
+        {CH_STATE_TXINIT,     CH_EVENT_TIMER,       ctcmpc_ch_action_txiniterr},
+        {CH_STATE_TXINIT,     CH_EVENT_IO_ENODEV,   ctcmpc_ch_action_iofatal},
+        {CH_STATE_TXINIT,     CH_EVENT_IO_EIO,      ctcmpc_ch_action_iofatal},
+        {CH_STATE_TXINIT,     CH_EVENT_MC_FAIL,     ctcmpc_ch_action_fail},
+        {CH_STATE_TXINIT,     CH_EVENT_RSWEEP1_TIMER,    ctcmpc_send_sweep},
+
+        {CH_STATE_TXIDLE,     CH_EVENT_STOP,          ctcmpc_ch_action_haltio},
+        {CH_STATE_TXIDLE,     CH_EVENT_START,         fsm_action_nop},
+        {CH_STATE_TXIDLE,     CH_EVENT_FINSTAT,       ctcmpc_ch_action_firstio},
+        {CH_STATE_TXIDLE,     CH_EVENT_UC_RCRESET,    ctcmpc_ch_action_fail},
+        {CH_STATE_TXIDLE,     CH_EVENT_UC_RSRESET,    ctcmpc_ch_action_fail},
+        {CH_STATE_TXIDLE,     CH_EVENT_IO_ENODEV,     ctcmpc_ch_action_iofatal},
+        {CH_STATE_TXIDLE,     CH_EVENT_IO_EIO,        ctcmpc_ch_action_iofatal},
+        {CH_STATE_TXIDLE,     CH_EVENT_MC_FAIL,       ctcmpc_ch_action_fail},
+        {CH_STATE_TXIDLE,     CH_EVENT_RSWEEP1_TIMER, ctcmpc_send_sweep},
+
+        {CH_STATE_TERM,       CH_EVENT_STOP,       fsm_action_nop},
+        {CH_STATE_TERM,       CH_EVENT_START,      ctcmpc_ch_action_restart},
+        {CH_STATE_TERM,       CH_EVENT_FINSTAT,    ctcmpc_ch_action_stopped},
+        {CH_STATE_TERM,       CH_EVENT_UC_RCRESET, fsm_action_nop},
+        {CH_STATE_TERM,       CH_EVENT_UC_RSRESET, fsm_action_nop},
+        {CH_STATE_TERM,       CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        {CH_STATE_TERM,       CH_EVENT_IO_EBUSY,   ctcmpc_ch_action_fail},
+        {CH_STATE_TERM,       CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+
+        {CH_STATE_DTERM,      CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        {CH_STATE_DTERM,      CH_EVENT_START,      ctcmpc_ch_action_restart},
+        {CH_STATE_DTERM,      CH_EVENT_FINSTAT,    ctcmpc_ch_action_setmode},
+        {CH_STATE_DTERM,      CH_EVENT_UC_RCRESET, fsm_action_nop},
+        {CH_STATE_DTERM,      CH_EVENT_UC_RSRESET, fsm_action_nop},
+        {CH_STATE_DTERM,      CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        {CH_STATE_DTERM,      CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+
+        {CH_STATE_TX,         CH_EVENT_STOP,          ctcmpc_ch_action_haltio},
+        {CH_STATE_TX,         CH_EVENT_START,         fsm_action_nop},
+        {CH_STATE_TX,         CH_EVENT_FINSTAT,       ctcmpc_ch_action_txdone},
+        {CH_STATE_TX,         CH_EVENT_UC_RCRESET,    ctcmpc_ch_action_fail},
+        {CH_STATE_TX,         CH_EVENT_UC_RSRESET,    ctcmpc_ch_action_fail},
+        {CH_STATE_TX,         CH_EVENT_TIMER,         ctcmpc_ch_action_txretry},
+        {CH_STATE_TX,         CH_EVENT_IO_ENODEV,     ctcmpc_ch_action_iofatal},
+        {CH_STATE_TX,         CH_EVENT_IO_EIO,        ctcmpc_ch_action_iofatal},
+        {CH_STATE_TX,         CH_EVENT_MC_FAIL,       ctcmpc_ch_action_fail},
+        {CH_STATE_TX,         CH_EVENT_RSWEEP1_TIMER, ctcmpc_send_sweep},
+        {CH_STATE_TX,         CH_EVENT_IO_EBUSY,      ctcmpc_ch_action_fail},
+
+        {CH_STATE_RXERR,      CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        {CH_STATE_TXERR,      CH_EVENT_STOP,       ctcmpc_ch_action_haltio},
+        {CH_STATE_TXERR,      CH_EVENT_IO_ENODEV,  ctcmpc_ch_action_iofatal},
+        {CH_STATE_TXERR,      CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+        {CH_STATE_RXERR,      CH_EVENT_MC_FAIL,    ctcmpc_ch_action_fail},
+};
+
+static const int CH_FSM_LEN = sizeof (ch_fsm) / sizeof (fsm_node);
+
+/**
+ * Functions related to setup and device detection.
+ *****************************************************************************/
+
+static inline int
+ctcmpc_less_than(char *id1, char *id2)
+{
+        int dev1, dev2, i;
+
+        for(i = 0; i < 5; i++)
+        {
+                id1++;
+                id2++;
+        }
+        dev1 = simple_strtoul(id1, &id1, 16);
+        dev2 = simple_strtoul(id2, &id2, 16);
+
+        return(dev1 < dev2);
+}
+
+/**
+ * Add a new channel to the list of channels.
+ * Keeps the channel list sorted.
+ *
+ * @param cdev  The ccw_device to be added.
+ * @param type  The type class of the new channel.
+ *
+ * @return 0 on success, !0 on error.
+ */
+static int
+ctcmpc_add_channel(struct ccw_device *cdev, enum channel_types type)
+{
+        struct channel **c = &channels;
+        struct channel *ch;
+        int rc = 0;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        if((ch =
+            (struct channel *) kmalloc(sizeof (struct channel),
+                                       GFP_KERNEL)) == NULL)
+        {
+                ctcmpc_pr_warn("ctcmpc: Out of memory in ctcmpc_add_channel\n");
+                rc = -1;
+                goto done;
+        }
+        memset(ch, 0, sizeof (struct channel));
+        ch->discontact_th =
+        (struct th_header *)kmalloc(TH_HEADER_LENGTH,gfp_type());
+        if(ch->discontact_th == NULL)
+        {
+                kfree(ch);
+                ctcmpc_pr_warn("ctcmpc: Out of memory in ctcmpc_add_channel\n");
+                rc = -1;
+                goto done;
+        }
+        memset(ch->discontact_th, 0, TH_HEADER_LENGTH);
+        ch->discontact_th->th_blk_flag = TH_DISCONTACT;
+        tasklet_init(&ch->ch_disc_tasklet,
+                     mpc_action_send_discontact,
+                     (unsigned long)ch);
+
+
+        tasklet_init(&ch->ch_tasklet,ctcmpc_bh,(unsigned long)ch);
+        if((ch->ccw = (struct ccw1 *) kmalloc(17*sizeof(struct ccw1),
+                                              GFP_KERNEL | GFP_DMA)) == NULL)
+        {
+                kfree(ch);
+                ctcmpc_pr_warn("ctcmpc: Out of memory in ctcmpc_add_channel\n");
+                rc = -1;
+                goto done;
+        }
+        memset(ch->ccw, 0, 17*sizeof(struct ccw1));
+
+        ch->max_bufsize = (MPC_BUFSIZE_DEFAULT - 35);
+        /**
+         * "static" ccws are used in the following way:
+         *
+         * ccw[0..2] (Channel program for generic I/O):
+         *           0: prepare
+         *           1: read or write (depending on direction) with fixed
+         *              buffer (idal allocated once when buffer is allocated)
+         *           2: nop
+         * ccw[3..5] (Channel program for direct write of packets)
+         *           3: prepare
+         *           4: write (idal allocated on every write).
+         *           5: nop
+         * ccw[6..7] (Channel program for initial channel setup):
+         *           3: set extended mode
+         *           4: nop
+         *
+         * ch->ccw[0..5] are initialized in ctcmpc_ch_action_start because
+         * the channel's direction is yet unknown here.
+         *
+         * ccws used for xid2 negotiations
+         *  ch-ccw[8-14] need to be used for the XID exchange either
+         *    X side XID2 Processing
+         *       8:  write control
+         *       9:  write th
+         *	     10: write XID
+         *	     11: read th from secondary
+         *	     12: read XID   from secondary
+         *	     13: read 4 byte ID
+         *	     14: nop
+         *    Y side XID Processing
+         *	     8:  sense
+         *       9:  read th
+         *	     10: read XID
+         *	     11: write th
+         *	     12: write XID
+         *	     13: write 4 byte ID
+         *	     14: nop
+         *
+         *  ccws used for double noop due to VM timing issues
+         *  which result in unrecoverable Busy on channel
+         *       15: nop
+         *       16: nop
+         */
+        ch->ccw[6].cmd_code = CCW_CMD_SET_EXTENDED;
+        ch->ccw[6].flags = CCW_FLAG_SLI;
+        ch->ccw[6].count = 0;
+        ch->ccw[6].cda = 0;
+
+        ch->ccw[7].cmd_code = CCW_CMD_NOOP;
+        ch->ccw[7].flags = CCW_FLAG_SLI;
+        ch->ccw[7].count = 0;
+        ch->ccw[7].cda = 0;
+        ch->ccw[15].cmd_code = CCW_CMD_WRITE;
+        ch->ccw[15].flags    = CCW_FLAG_SLI | CCW_FLAG_CC;
+        ch->ccw[15].count    = TH_HEADER_LENGTH;
+        ch->ccw[15].cda      = virt_to_phys(ch->discontact_th);
+
+        ch->ccw[16].cmd_code = CCW_CMD_NOOP;
+        ch->ccw[16].flags    = CCW_FLAG_SLI;
+        ch->ccw[16].count    = 0;
+        ch->ccw[16].cda      = 0;
+        ch->cdev = cdev;
+        ch->in_mpcgroup = 0;
+        snprintf(ch->id, CTC_ID_SIZE, "ch-%s", cdev->dev.bus_id);
+        ch->type = type;
+        loglevel = CTC_LOGLEVEL_DEFAULT;
+        ch->fsm = init_fsm(ch->id, ch_state_names,
+                           ch_event_names, NR_CH_STATES, NR_CH_EVENTS,
+                           ch_fsm, CH_FSM_LEN, GFP_KERNEL);
+        if(ch->fsm == NULL)
+        {
+                ctcmpc_pr_warn("ctcmpc: Could not create FSM in ctcmpc_add_channel\n");
+                kfree(ch);
+                rc = -1;
+                goto done;
+        }
+        fsm_newstate(ch->fsm, CH_STATE_IDLE);
+        if((ch->irb = (struct irb *) kmalloc(sizeof (struct irb),
+                                             GFP_KERNEL)) == NULL)
+        {
+                ctcmpc_pr_warn("ctcmpc: Out of memory in ctcmpc_add_channel\n");
+                kfree_fsm(ch->fsm);
+                kfree(ch);
+                rc = -1;
+                goto done;
+        }
+        memset(ch->irb, 0, sizeof (struct irb));
+        while(*c && ctcmpc_less_than((*c)->id, ch->id))
+                c = &(*c)->next;
+        if(*c && (!strncmp((*c)->id, ch->id, CTC_ID_SIZE)))
+        {
+                ctcmpc_pr_debug(
+                               "ctc: ctcmpc_add_channel: device %s already in list, "
+                               "using old entry\n", (*c)->id);
+                kfree(ch->irb);
+                kfree_fsm(ch->fsm);
+                kfree(ch);
+                rc = 0;
+                goto done;
+        }
+
+	spin_lock_init(&ch->collect_lock);
+	spin_lock_init(&ch->segment_lock);
+
+        fsm_settimer(ch->fsm, &ch->timer);
+        fsm_settimer(ch->fsm, &ch->sweep_timer);
+        skb_queue_head_init(&ch->io_queue);
+        skb_queue_head_init(&ch->collect_queue);
+        skb_queue_head_init(&ch->sweep_queue);
+        ch->next = *c;
+        *c = ch;
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+
+        return(rc);
+}
+
+/**
+ * Release a specific channel in the channel list.
+ *
+ * @param ch Pointer to channel struct to be released.
+ */
+static void
+channel_free(struct channel *ch)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        ch->flags &= ~CHANNEL_FLAGS_INUSE;
+        fsm_newstate(ch->fsm, CH_STATE_IDLE);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Remove a specific channel in the channel list.
+ *
+ * @param ch Pointer to channel struct to be released.
+ */
+static void
+channel_remove(struct channel *ch)
+{
+        struct channel **c = &channels;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        if(ch == NULL)
+                goto done;
+
+        channel_free(ch);
+        while(*c)
+        {
+                if(*c == ch)
+                {
+                        *c = ch->next;
+                        fsm_deltimer(&ch->timer);
+                        fsm_deltimer(&ch->sweep_timer);
+                        kfree_fsm(ch->fsm);
+                        clear_normalized_cda(&ch->ccw[4]);
+                        if(ch->trans_skb != NULL)
+                        {
+                                clear_normalized_cda(&ch->ccw[1]);
+                                dev_kfree_skb_any(ch->trans_skb);
+                        }
+                        tasklet_kill(&ch->ch_tasklet);
+                        tasklet_kill(&ch->ch_disc_tasklet);
+                        kfree(ch->discontact_th);
+                        kfree(ch->ccw);
+                        kfree(ch->irb);
+                        kfree(ch);
+                        goto done;
+                }
+                c = &((*c)->next);
+        }
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+        return;
+}
+
+/**
+ * Get a specific channel from the channel list.
+ *
+ * @param type Type of channel we are interested in.
+ * @param id Id of channel we are interested in.
+ * @param direction Direction we want to use this channel for.
+ *
+ * @return Pointer to a channel or NULL if no matching channel available.
+ */
+static struct channel
+*
+channel_get(enum channel_types type, char *id, int direction)
+{
+        struct channel *ch = channels;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc:%s():searching for ch with id %s and type %d\n",
+                        __func__, id, type);
+#endif
+
+        while(ch &&
+              ((strncmp(ch->id, id, CTC_ID_SIZE)) || (
+                                                     ch->type != type)))
+        {
+#ifdef DEBUG
+                ctcmpc_pr_debug("ctcmpc: %s(): ch=0x%p (id=%s, type=%d\n",
+                                __func__, ch, ch->id, ch->type);
+#endif
+                ch = ch->next;
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc: %s(): ch=0x%pq (id=%s, type=%d\n",
+                        __func__, ch, ch->id, ch->type);
+#endif
+        if(!ch)
+        {
+                ctcmpc_pr_warn("ctcmpc: %s(): channel with id %s "
+                               "and type %d not found in channel list\n",
+                               __func__, id, type);
+        } else
+        {
+                if(ch->flags & CHANNEL_FLAGS_INUSE)
+                        ch = NULL;
+                else
+                {
+                        ch->flags |= CHANNEL_FLAGS_INUSE;
+                        ch->flags &= ~CHANNEL_FLAGS_RWMASK;
+                        ch->flags |= (direction == WRITE)
+                                     ? CHANNEL_FLAGS_WRITE : CHANNEL_FLAGS_READ;
+                        fsm_newstate(ch->fsm, CH_STATE_STOPPED);
+                }
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+        return ch;
+}
+
+/**
+ * Return the channel type by name.
+ *
+ * @param name Name of network interface.
+ *
+ * @return Type class of channel to be used for that interface.
+ */
+static enum channel_types inline
+extract_channel_media(char *name)
+{
+        enum channel_types ret = channel_type_unknown;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+
+        if(name != NULL)
+        {
+                if(strncmp(name, "mpc", 3) == 0)
+                        ret = channel_type_parallel;
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+        return ret;
+}
+
+static long
+__ctcmpc_check_irb_error(struct ccw_device *cdev, struct irb *irb)
+{
+        if(!IS_ERR(irb))
+                return 0;
+
+        switch(PTR_ERR(irb))
+        {
+                case -EIO:
+                        ctcmpc_pr_warn("i/o-error on device %s\n",
+                                       cdev->dev.bus_id);
+//		CTC_DBF_TEXT(trace, 2, "ckirberr");
+//		CTC_DBF_TEXT_(trace, 2, "  rc%d", -EIO);
+                        break;
+                case -ETIMEDOUT:
+                        ctcmpc_pr_warn("timeout on device %s\n",
+                                       cdev->dev.bus_id);
+//		CTC_DBF_TEXT(trace, 2, "ckirberr");
+//		CTC_DBF_TEXT_(trace, 2, "  rc%d", -ETIMEDOUT);
+                        break;
+                default:
+                        ctcmpc_pr_warn("unknown error %ld on device %s\n",
+                                       PTR_ERR(irb),
+                                       cdev->dev.bus_id);
+//		CTC_DBF_TEXT(trace, 2, "ckirberr");
+//		CTC_DBF_TEXT(trace, 2, "  rc???");
+        }
+        return PTR_ERR(irb);
+}
+
+/**
+ * Main IRQ handler.
+ *
+ * @param cdev    The ccw_device the interrupt is for.
+ * @param intparm interruption parameter.
+ * @param irb     interruption response block.
+ */
+static void
+ctcmpc_irq_handler(struct ccw_device *cdev,unsigned long intparm,struct irb *irb)
+{
+        struct channel *ch;
+        struct net_device *dev;
+        struct ctc_priv *priv;
+
+        if(__ctcmpc_check_irb_error(cdev, irb))
+                return;
+
+        /* Check for unsolicited interrupts. */
+        if(!cdev->dev.driver_data)
+        {
+                ctcmpc_pr_warn("ctcmpc:Got unsolicited irq: %s c-%02x d-%02x\n",
+                               cdev->dev.bus_id, irb->scsw.cstat,
+                               irb->scsw.dstat);
+                return;
+        }
+
+        priv = ((struct ccwgroup_device *)cdev->dev.driver_data)
+               ->dev.driver_data;
+
+        /* Try to extract channel from driver data. */
+        if(priv->channel[READ]->cdev == cdev)
+                ch = priv->channel[READ];
+        else if(priv->channel[WRITE]->cdev == cdev)
+                ch = priv->channel[WRITE];
+        else
+        {
+                ctcmpc_pr_err("ctcmpc: Can't determine channel for interrupt, "
+                              "device %s\n", cdev->dev.bus_id);
+                return;
+        }
+
+        dev = (struct net_device *) (ch->netdev);
+        if(dev == NULL)
+        {
+                ctcmpc_pr_crit("ctcmpc: ctcmpc_irq_handler dev=NULL bus_id=%s,"
+                               " ch=0x%p\n",
+                               cdev->dev.bus_id,
+                               ch);
+                goto done;
+        }
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("%s: interrupt for device: %s received c-%02x d-%02x\n",
+                        dev->name, ch->id, irb->scsw.cstat, irb->scsw.dstat);
+#endif
+
+        /* Copy interruption response block. */
+        memcpy(ch->irb, irb, sizeof(struct irb));
+
+        /* Check for good subchannel return code, otherwise error message */
+        if(ch->irb->scsw.cstat)
+        {
+                fsm_event(ch->fsm, CH_EVENT_SC_UNKNOWN, ch);
+                ctcmpc_pr_warn("%s:subchannel check for device:%s -%02x %02x\n",
+                               dev->name, ch->id, ch->irb->scsw.cstat,
+                               ch->irb->scsw.dstat);
+                goto done;
+        }
+
+        /* Check the reason-code of a unit check */
+        if(ch->irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
+        {
+                ccw_unit_check(ch, ch->irb->ecw[0]);
+                goto done;
+        }
+        if(ch->irb->scsw.dstat & DEV_STAT_BUSY)
+        {
+                if(ch->irb->scsw.dstat & DEV_STAT_ATTENTION)
+                        fsm_event(ch->fsm, CH_EVENT_ATTNBUSY, ch);
+                else
+                        fsm_event(ch->fsm, CH_EVENT_BUSY, ch);
+                goto done;
+        }
+        if(ch->irb->scsw.dstat & DEV_STAT_ATTENTION)
+        {
+                fsm_event(ch->fsm, CH_EVENT_ATTN, ch);
+                goto done;
+        }
+        if((ch->irb->scsw.stctl & SCSW_STCTL_SEC_STATUS) ||
+           (ch->irb->scsw.stctl == SCSW_STCTL_STATUS_PEND) ||
+           (ch->irb->scsw.stctl ==
+            (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND)))
+                fsm_event(ch->fsm, CH_EVENT_FINSTAT, ch);
+        else
+                fsm_event(ch->fsm, CH_EVENT_IRQ, ch);
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s(): ch=0x%p id=%s\n",
+                        __func__, ch, ch->id);
+#endif
+        return;
+}
+
+/**
+ * Actions for interface - statemachine.
+ *****************************************************************************/
+
+/**
+ * Startup channels by sending CH_EVENT_START to each channel.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_start(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct net_device *dev = (struct net_device *) arg;
+        struct ctc_priv *privptr = dev->priv;
+        struct mpc_group  *grpptr = privptr->mpcg;
+        int direction;
+
+        fsm_deltimer(&privptr->restart_timer);
+        fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX);
+        grpptr->channels_terminating = 0;
+        for(direction = READ; direction <= WRITE; direction++)
+        {
+                struct channel *ch = privptr->channel[direction];
+                fsm_event(ch->fsm, CH_EVENT_START, ch);
+        }
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Shutdown channels by sending CH_EVENT_STOP to each channel.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_stop(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct net_device *dev = (struct net_device *) arg;
+        struct ctc_priv *privptr = dev->priv;
+        struct mpc_group  * grpptr = privptr->mpcg;
+        int direction;
+
+        fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
+        for(direction = READ; direction <= WRITE; direction++)
+        {
+                struct channel *ch = privptr->channel[direction];
+                fsm_event(ch->fsm, CH_EVENT_STOP, ch);
+                ch->th_seq_num = 0x00;
+
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("ctcmpc: %s() CH_th_seq= %08x\n" ,
+                               __FUNCTION__,
+                               ch->th_seq_num);
+#endif
+        }
+        fsm_newstate(grpptr->fsm, MPCG_STATE_RESET);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+static void
+dev_action_restart(fsm_instance *fi, int event, void *arg)
+{
+        struct net_device *dev = (struct net_device *)arg;
+        struct ctc_priv *privptr = dev->priv;
+        struct mpc_group  * grpptr = privptr->mpcg;
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        ctcmpc_pr_info("ctcmpc: %s Restarting Device and "
+                       "MPC Group in 5 seconds\n",
+                       dev->name);
+        dev_action_stop(fi, event, arg);
+        fsm_event(privptr->fsm, DEV_EVENT_STOP, dev);
+        fsm_newstate(grpptr->fsm, MPCG_STATE_RESET);
+        /* going back into start sequence too quickly can         */
+        /* result in the other side becoming unreachable   due    */
+        /* to sense reported when IO is aborted                   */
+        fsm_addtimer(&privptr->restart_timer, 1000, DEV_EVENT_START, dev);
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Called from channel statemachine
+ * when a channel is up and running.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_chup(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct net_device *dev = (struct net_device *) arg;
+        struct ctc_priv *privptr = dev->priv;
+
+        switch(fsm_getstate(fi))
+        {
+                case DEV_STATE_STARTWAIT_RXTX:
+                        if(event == DEV_EVENT_RXUP)
+                                fsm_newstate(fi, DEV_STATE_STARTWAIT_TX);
+                        else
+                                fsm_newstate(fi, DEV_STATE_STARTWAIT_RX);
+                        break;
+                case DEV_STATE_STARTWAIT_RX:
+                        if(event == DEV_EVENT_RXUP)
+                        {
+                                fsm_newstate(fi, DEV_STATE_RUNNING);
+                                ctcmpc_pr_info(
+                                        "%s: connected with remote side\n",
+                                        dev->name);
+                                ctcmpc_clear_busy(dev);
+                        }
+                        break;
+                case DEV_STATE_STARTWAIT_TX:
+                        if(event == DEV_EVENT_TXUP)
+                        {
+                                fsm_newstate(fi, DEV_STATE_RUNNING);
+                                ctcmpc_pr_info(
+                                        "%s: connected with remote side\n",
+                                        dev->name);
+                                ctcmpc_clear_busy(dev);
+                        }
+                        break;
+                case DEV_STATE_STOPWAIT_TX:
+                        if(event == DEV_EVENT_RXUP)
+                                fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
+                        break;
+                case DEV_STATE_STOPWAIT_RX:
+                        if(event == DEV_EVENT_TXUP)
+                                fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
+                        break;
+        }
+        if(event == DEV_EVENT_RXUP)
+                mpc_channel_action(privptr->channel[READ],
+                                   READ,
+                                   MPC_CHANNEL_ADD);
+        else
+                mpc_channel_action(privptr->channel[WRITE],
+                                   WRITE,
+                                   MPC_CHANNEL_ADD);
+
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Called from channel statemachine
+ * when a channel has been shutdown.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_chdown(fsm_instance * fi, int event, void *arg)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+        struct net_device *dev = (struct net_device *) arg;
+        struct ctc_priv *privptr = (struct ctc_priv *)dev->priv;
+
+        switch(fsm_getstate(fi))
+        {
+                case DEV_STATE_RUNNING:
+                        if(event == DEV_EVENT_TXDOWN)
+                                fsm_newstate(fi, DEV_STATE_STARTWAIT_TX);
+                        else
+                                fsm_newstate(fi, DEV_STATE_STARTWAIT_RX);
+                        break;
+                case DEV_STATE_STARTWAIT_RX:
+                        if(event == DEV_EVENT_TXDOWN)
+                                fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX);
+                        break;
+                case DEV_STATE_STARTWAIT_TX:
+                        if(event == DEV_EVENT_RXDOWN)
+                                fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX);
+                        break;
+                case DEV_STATE_STOPWAIT_RXTX:
+                        if(event == DEV_EVENT_TXDOWN)
+                                fsm_newstate(fi, DEV_STATE_STOPWAIT_RX);
+                        else
+                                fsm_newstate(fi, DEV_STATE_STOPWAIT_TX);
+                        break;
+                case DEV_STATE_STOPWAIT_RX:
+                        if(event == DEV_EVENT_RXDOWN)
+                                fsm_newstate(fi, DEV_STATE_STOPPED);
+                        break;
+                case DEV_STATE_STOPWAIT_TX:
+                        if(event == DEV_EVENT_TXDOWN)
+                                fsm_newstate(fi, DEV_STATE_STOPPED);
+                        break;
+        }
+        if(event == DEV_EVENT_RXDOWN)
+                mpc_channel_action(privptr->channel[READ],
+                                   READ,
+                                   MPC_CHANNEL_REMOVE);
+        else
+                mpc_channel_action(privptr->channel[WRITE],
+                                   WRITE,
+                                   MPC_CHANNEL_REMOVE);
+
+
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+}
+
+static const fsm_node dev_fsm[] = {
+        {DEV_STATE_STOPPED, DEV_EVENT_START, dev_action_start},
+
+        {DEV_STATE_STOPWAIT_RXTX,  DEV_EVENT_START,   dev_action_start},
+        {DEV_STATE_STOPWAIT_RXTX,  DEV_EVENT_RXDOWN,  dev_action_chdown},
+        {DEV_STATE_STOPWAIT_RXTX,  DEV_EVENT_TXDOWN,  dev_action_chdown},
+        {DEV_STATE_STOPWAIT_RXTX,  DEV_EVENT_RESTART, dev_action_restart},
+
+        {DEV_STATE_STOPWAIT_RX,    DEV_EVENT_START,   dev_action_start},
+        {DEV_STATE_STOPWAIT_RX,    DEV_EVENT_RXUP,    dev_action_chup},
+        {DEV_STATE_STOPWAIT_RX,    DEV_EVENT_TXUP,    dev_action_chup},
+        {DEV_STATE_STOPWAIT_RX,    DEV_EVENT_RXDOWN,  dev_action_chdown},
+        {DEV_STATE_STOPWAIT_RX,    DEV_EVENT_RESTART, dev_action_restart},
+
+        {DEV_STATE_STOPWAIT_TX,    DEV_EVENT_START,   dev_action_start},
+        {DEV_STATE_STOPWAIT_TX,    DEV_EVENT_RXUP,    dev_action_chup},
+        {DEV_STATE_STOPWAIT_TX,    DEV_EVENT_TXUP,    dev_action_chup},
+        {DEV_STATE_STOPWAIT_TX,    DEV_EVENT_TXDOWN,  dev_action_chdown},
+        {DEV_STATE_STOPWAIT_TX,    DEV_EVENT_RESTART, dev_action_restart},
+
+        {DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_STOP,    dev_action_stop},
+        {DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_RXUP,    dev_action_chup},
+        {DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_TXUP,    dev_action_chup},
+        {DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_RXDOWN,  dev_action_chdown},
+        {DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_TXDOWN,  dev_action_chdown},
+        {DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_RESTART, dev_action_restart},
+
+        {DEV_STATE_STARTWAIT_TX,   DEV_EVENT_STOP,    dev_action_stop},
+        {DEV_STATE_STARTWAIT_TX,   DEV_EVENT_RXUP,    dev_action_chup},
+        {DEV_STATE_STARTWAIT_TX,   DEV_EVENT_TXUP,    dev_action_chup},
+        {DEV_STATE_STARTWAIT_TX,   DEV_EVENT_RXDOWN,  dev_action_chdown},
+        {DEV_STATE_STARTWAIT_TX,   DEV_EVENT_RESTART, dev_action_restart},
+
+        {DEV_STATE_STARTWAIT_RX,   DEV_EVENT_STOP,    dev_action_stop},
+        {DEV_STATE_STARTWAIT_RX,   DEV_EVENT_RXUP,    dev_action_chup},
+        {DEV_STATE_STARTWAIT_RX,   DEV_EVENT_TXUP,    dev_action_chup},
+        {DEV_STATE_STARTWAIT_RX,   DEV_EVENT_TXDOWN,  dev_action_chdown},
+        {DEV_STATE_STARTWAIT_RX,   DEV_EVENT_RESTART, dev_action_restart},
+
+        {DEV_STATE_RUNNING,        DEV_EVENT_STOP,    dev_action_stop},
+        {DEV_STATE_RUNNING,        DEV_EVENT_RXDOWN,  dev_action_chdown},
+        {DEV_STATE_RUNNING,        DEV_EVENT_TXDOWN,  dev_action_chdown},
+        {DEV_STATE_RUNNING,        DEV_EVENT_TXUP,    fsm_action_nop},
+        {DEV_STATE_RUNNING,        DEV_EVENT_RXUP,    fsm_action_nop},
+        {DEV_STATE_RUNNING,        DEV_EVENT_RESTART, dev_action_restart},
+};
+
+static const int DEV_FSM_LEN = sizeof (dev_fsm) / sizeof (fsm_node);
+
+/**
+ * Transmit a packet.
+ * This is a helper function for ctcmpc_tx().
+ *
+ * @param ch Channel to be used for sending.
+ * @param skb Pointer to struct sk_buff of packet to send.
+ *            The linklevel header has already been set up
+ *            by ctcmpc_tx().
+ *
+ * @return 0 on success, -ERRNO on failure. (Never fails.)
+ */
+static int
+transmit_skb(struct channel *ch, struct sk_buff *skb)
+{
+        unsigned long saveflags;
+        struct pdu *p_header;
+        int rc = 0;
+        struct net_device *dev;
+        struct ctc_priv *privptr;
+        struct mpc_group *grpptr;
+        struct th_header *header;
+#ifdef DEBUGDATA
+        __u32 out_len = 0;
+#endif
+
+        dev = ch->netdev;
+        privptr = (struct ctc_priv *)dev->priv;
+        grpptr = privptr->mpcg;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): %s cp=%i ch=0x%p id=%s\n",
+                        __func__,
+                        dev->name,
+                        smp_processor_id(),
+                        ch,
+                        ch->id);
+#endif
+#ifdef DEBUG
+        ctcmpc_pr_debug("%s cp:%i enter:  %s() state: %s \n",
+                       dev->name,
+                       smp_processor_id(),
+                       __FUNCTION__,
+                       fsm_getstate_str(ch->fsm));
+#endif
+
+        saveflags = 0;	/* avoids compiler warning with
+                           spin_unlock_irqrestore */
+
+        if((fsm_getstate(ch->fsm) != CH_STATE_TXIDLE) ||
+           (grpptr->in_sweep))
+        {
+                spin_lock_irqsave(&ch->collect_lock, saveflags);
+                atomic_inc(&skb->users);
+                p_header = (struct pdu *)kmalloc(PDU_HEADER_LENGTH, gfp_type());
+
+                if(!p_header)
+                {
+                        printk(KERN_WARNING "ctcmpc: OUT OF MEMORY IN %s():"
+                               " Data Lost \n",
+                               __FUNCTION__);
+                        atomic_dec(&skb->users);
+                        dev_kfree_skb_any(skb);
+                        spin_unlock_irqrestore(&ch->collect_lock, saveflags);
+                        fsm_event(privptr->mpcg->fsm,MPCG_EVENT_INOP,dev);
+                        goto done;
+                }
+
+                p_header->pdu_offset = skb->len;
+                p_header->pdu_proto = 0x01;
+                p_header->pdu_flag = 0x00;
+                if(skb->protocol == ntohs(ETH_P_SNAP))
+                {
+                        p_header->pdu_flag |= PDU_FIRST | PDU_CNTL;
+                } else
+                {
+                        p_header->pdu_flag |= PDU_FIRST;
+                }
+                p_header->pdu_seq = 0;
+                memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header,
+                       PDU_HEADER_LENGTH);
+#ifdef DEBUGDATA
+                __u32 out_len;
+                if(skb->len > 32) out_len = 32;
+                else out_len = skb->len;
+                ctcmpc_pr_debug("ctcmpc: %s() Putting on collect_q"
+                               " - skb len: %04x \n",
+                               __FUNCTION__,
+                               skb->len);
+                ctcmpc_pr_debug("ctcmpc: %s() pdu header and data"
+                               " for up to 32 bytes\n",
+                               __FUNCTION__);
+                dumpit((char *)skb->data,out_len);
+#endif
+                skb_queue_tail(&ch->collect_queue, skb);
+                ch->collect_len += skb->len;
+                kfree(p_header);
+
+                spin_unlock_irqrestore(&ch->collect_lock, saveflags);
+        } else
+        {
+                __u16 block_len;
+                int ccw_idx;
+                struct sk_buff *nskb;
+                unsigned long hi;
+
+                /**
+                 * Protect skb against beeing free'd by upper
+                 * layers.
+                 */
+                atomic_inc(&skb->users);
+
+                block_len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH;
+
+                /**
+                 * IDAL support in CTC is broken, so we have to
+                 * care about skb's above 2G ourselves.
+                 */
+                hi = ((unsigned long)skb->tail + TH_HEADER_LENGTH) >> 31;
+                if(hi)
+                {
+                        nskb = __dev_alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA);
+                        if(!nskb)
+                        {
+                                printk(KERN_WARNING "ctcmpc: %s() OUT OF MEMORY"
+                                       "-  Data Lost \n",
+                                       __FUNCTION__);
+                                atomic_dec(&skb->users);
+                                dev_kfree_skb_any(skb);
+                                fsm_event(privptr->mpcg->fsm,
+                                          MPCG_EVENT_INOP,
+                                          dev);
+                                goto done;
+                        } else
+                        {
+                                memcpy(skb_put(nskb, skb->len),
+                                       skb->data, skb->len);
+                                atomic_inc(&nskb->users);
+                                atomic_dec(&skb->users);
+                                dev_kfree_skb_irq(skb);
+                                skb = nskb;
+                        }
+                }
+
+                p_header = (struct pdu *)kmalloc(PDU_HEADER_LENGTH, gfp_type());
+
+                if(!p_header)
+                {
+                        printk(KERN_WARNING "ctcmpc: %s() OUT OF MEMORY"
+                               ": Data Lost \n",
+                               __FUNCTION__);
+                        atomic_dec(&skb->users);
+                        dev_kfree_skb_any(skb);
+                        fsm_event(privptr->mpcg->fsm,MPCG_EVENT_INOP,dev);
+                        goto done;
+                }
+
+                p_header->pdu_offset = skb->len;
+                p_header->pdu_proto = 0x01;
+                p_header->pdu_flag = 0x00;
+                p_header->pdu_seq = 0;
+                if(skb->protocol == ntohs(ETH_P_SNAP))
+                {
+                        p_header->pdu_flag |= PDU_FIRST | PDU_CNTL;
+                } else
+                {
+                        p_header->pdu_flag |= PDU_FIRST;
+                }
+                memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header,
+                       PDU_HEADER_LENGTH);
+
+                kfree(p_header);
+
+                if(ch->collect_len > 0)
+                {
+                        spin_lock_irqsave(&ch->collect_lock, saveflags);
+                        skb_queue_tail(&ch->collect_queue, skb);
+                        ch->collect_len += skb->len;
+                        skb = skb_dequeue(&ch->collect_queue);
+                        ch->collect_len -= skb->len;
+                        spin_unlock_irqrestore(&ch->collect_lock, saveflags);
+                }
+
+                p_header = (struct pdu *)skb->data;
+                p_header->pdu_flag |= PDU_LAST;
+
+                ch->prof.txlen += skb->len - PDU_HEADER_LENGTH;
+
+                header = (struct th_header *)kmalloc(TH_HEADER_LENGTH,
+                                                     gfp_type());
+
+                if(!header)
+                {
+                        printk(KERN_WARNING "ctcmpc: %s() OUT OF MEMORY"
+                               ": Data Lost \n",
+                               __FUNCTION__);
+                        atomic_dec(&skb->users);
+                        dev_kfree_skb_any(skb);
+                        fsm_event(privptr->mpcg->fsm,MPCG_EVENT_INOP,dev);
+                        goto done;
+                }
+
+                header->th_seg = 0x00;
+                header->th_ch_flag = TH_HAS_PDU;  /* Normal data */
+                header->th_blk_flag = 0x00;
+                header->th_is_xid = 0x00;          /* Just data here */
+                ch->th_seq_num++;
+                header->th_seq_num = ch->th_seq_num;
+
+#ifdef DEBUGDATA
+                ctcmpc_pr_debug("ctcmpc: %s() ToVTAM_th_seq= %08x\n" ,
+                               __FUNCTION__,
+                               ch->th_seq_num);
+#endif
+
+                memcpy(skb_push(skb, TH_HEADER_LENGTH), header,
+                       TH_HEADER_LENGTH);       /*  put the TH on the packet */
+
+                kfree(header);
+
+#ifdef DEBUGDATA
+                if(skb->len > 32) out_len = 32;
+                else out_len = skb->len;
+                ctcmpc_pr_debug("ctcmpc: %s(): skb len: %04x \n",
+                               __FUNCTION__,
+                               skb->len);
+                ctcmpc_pr_debug("ctcmpc: %s(): pdu header and"
+                               " data for up to 32 bytes sent to vtam\n",
+                               __FUNCTION__);
+                dumpit((char *)skb->data,out_len);
+#endif
+
+                ch->ccw[4].count = skb->len;
+                if(set_normalized_cda(&ch->ccw[4], skb->data))
+                {
+                        /**
+                         * idal allocation failed, try via copying to
+                         * trans_skb. trans_skb usually has a pre-allocated
+                         * idal.
+                         */
+                        if(ctcmpc_checkalloc_buffer(ch, 1))
+                        {
+                                /**
+                                 * Remove our header. It gets added
+                                 * again on retransmit.
+                                 */
+                                atomic_dec(&skb->users);
+                                dev_kfree_skb_any(skb);
+                                printk(KERN_WARNING "ctcmpc: %s()OUT"
+                                       " OF MEMORY: Data Lost \n",
+                                       __FUNCTION__);
+                                fsm_event(privptr->mpcg->fsm,
+                                          MPCG_EVENT_INOP,
+                                          dev);
+                                goto done;
+                        }
+
+                        ch->trans_skb->tail = ch->trans_skb->data;
+                        ch->trans_skb->len = 0;
+                        ch->ccw[1].count = skb->len;
+                        memcpy(skb_put(ch->trans_skb, skb->len), skb->data,
+                               skb->len);
+                        atomic_dec(&skb->users);
+                        dev_kfree_skb_irq(skb);
+                        ccw_idx = 0;
+#ifdef DEBUGDATA
+                        if(ch->trans_skb->len > 32) out_len = 32;
+                        else out_len = ch->trans_skb->len;
+                        ctcmpc_pr_debug("ctcmpc: %s() TRANS"
+                                       " skb len: %d \n",
+                                       __FUNCTION__,
+                                       ch->trans_skb->len);
+                        ctcmpc_pr_debug("ctcmpc: %s up to 32"
+                                       " bytes of data sent to vtam\n",
+                                       __FUNCTION__);
+                        dumpit((char *)ch->trans_skb->data,out_len);
+#endif
+                } else
+                {
+                        skb_queue_tail(&ch->io_queue, skb);
+                        ccw_idx = 3;
+                }
+                ch->retry = 0;
+                fsm_newstate(ch->fsm, CH_STATE_TX);
+                fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+
+#ifdef DEBUGCCW
+                dumpit((char *)&ch->ccw[ccw_idx],sizeof(struct ccw1) * 3);
+#endif
+                spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+                ch->prof.send_stamp = xtime;
+                rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx],
+                                      (unsigned long) ch, 0xff, 0);
+                spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+                if(ccw_idx == 3)
+                        ch->prof.doios_single++;
+                if(rc != 0)
+                {
+                        fsm_deltimer(&ch->timer);
+                        ccw_check_return_code(ch, rc, "single skb TX");
+                        if(ccw_idx == 3)
+                                skb_dequeue_tail(&ch->io_queue);
+                } else
+                {
+                        if(ccw_idx == 0)
+                        {
+                                struct net_device *dev = ch->netdev;
+                                struct ctc_priv *privptr = dev->priv;
+                                privptr->stats.tx_packets++;
+                                privptr->stats.tx_bytes +=
+                                skb->len - TH_HEADER_LENGTH;
+                        }
+                }
+                if(ch->th_seq_num > 0xf0000000)
+                {
+                        /* Chose 4Billion at random.  */
+                        ctcmpc_send_sweep_req(ch);
+                }
+        }
+
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit: %s  %s()\n", dev->name,__FUNCTION__);
+#endif
+        return(0);
+}
+
+/**
+ * Interface API for upper network layers
+ *****************************************************************************/
+
+/**
+ * Open an interface.
+ * Called from generic network layer when ifconfig up is run.
+ *
+ * @param dev Pointer to interface struct.
+ *
+ * @return 0 on success, -ERRNO on failure. (Never fails.)
+ */
+static int
+ctcmpc_open(struct net_device *dev)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+
+//	MOD_INC_USE_COUNT;
+        /* for MPC this is called from ctc_mpc_alloc_channel */
+//        fsm_event(((struct ctc_priv *)dev->priv)->fsm, DEV_EVENT_START, dev);
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+        return 0;
+}
+
+/**
+ * Close an interface.
+ * Called from generic network layer when ifconfig down is run.
+ *
+ * @param dev Pointer to interface struct.
+ *
+ * @return 0 on success, -ERRNO on failure. (Never fails.)
+ */
+static int
+ctcmpc_close(struct net_device * dev)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter:  %s()\n", __FUNCTION__);
+#endif
+
+        /*Now called from mpc close only         */
+        /*fsm_event(((struct ctc_priv *)dev->priv)->fsm, DEV_EVENT_STOP, dev);*/
+//	MOD_DEC_USE_COUNT;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+        return 0;
+}
+
+/**
+ * Start transmission of a packet.
+ * Called from generic network device layer.
+ *
+ * @param skb Pointer to buffer containing the packet.
+ * @param dev Pointer to interface struct.
+ *
+ * @return 0 if packet consumed, !0 if packet rejected.
+ *         Note: If we return !0, then the packet is free'd by
+ *               the generic network layer.
+ */
+static int
+ctcmpc_tx(struct sk_buff *skb, struct net_device * dev)
+{
+        int len = 0;
+        struct ctc_priv *privptr = NULL;
+        struct mpc_group *grpptr  = NULL;
+        struct sk_buff *newskb = NULL;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s(): skb:%0lx\n",
+                        __func__, (unsigned long)skb);
+#endif
+        privptr = (struct ctc_priv *) dev->priv;
+        grpptr  = privptr->mpcg;
+        /**
+         * Some sanity checks ...
+         */
+        if(skb == NULL)
+        {
+                ctcmpc_pr_warn("ctcmpc: %s: NULL sk_buff passed\n",
+                               dev->name);
+                privptr->stats.tx_dropped++;
+                goto done;
+        }
+        if(skb_headroom(skb) < (TH_HEADER_LENGTH + PDU_HEADER_LENGTH))
+        {
+                ctcmpc_pr_warn("%s: Got sk_buff with head room < %ld bytes\n",
+                               dev->name, TH_HEADER_LENGTH + PDU_HEADER_LENGTH);
+                if(skb->len > 32) len = 32;
+                else len = skb->len;
+#ifdef DEBUGDATA
+                dumpit((char *)skb->data,len);
+#endif
+                len =  skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH;
+                newskb = __dev_alloc_skb(len,gfp_type() | GFP_DMA);
+
+                if(!newskb)
+                {
+                        printk(KERN_WARNING "ctcmpc: %s() OUT OF MEMORY-"
+                               "Data Lost\n",
+                               __FUNCTION__);
+                        printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR"
+                               " - UNRECOVERABLE DATA LOSS\n",
+                               __FUNCTION__);
+                        dev_kfree_skb_any(skb);
+                        privptr->stats.tx_dropped++;
+                        privptr->stats.tx_errors++;
+                        privptr->stats.tx_carrier_errors++;
+                        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                        goto done;
+                }
+                newskb->protocol = skb->protocol;
+                skb_reserve(newskb,TH_HEADER_LENGTH + PDU_HEADER_LENGTH);
+                memcpy(skb_put(newskb,skb->len),skb->data,skb->len);
+                dev_kfree_skb_any(skb);
+                skb = newskb;
+        }
+
+        /**
+         * If channels are not running,
+         * notify anybody about a link failure and throw
+         * away packet.
+         */
+        if((fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) ||
+           (fsm_getstate(grpptr->fsm) <  MPCG_STATE_XID2INITW))
+        {
+                dev_kfree_skb_any(skb);
+                printk(KERN_INFO "ctcmpc: %s() DATA RCVD - MPC GROUP "
+                       "NOT ACTIVE - DROPPED\n",
+                       __FUNCTION__);
+                privptr->stats.tx_dropped++;
+                privptr->stats.tx_errors++;
+                privptr->stats.tx_carrier_errors++;
+                goto done;
+        }
+
+        if(ctcmpc_test_and_set_busy(dev))
+        {
+                printk(KERN_WARNING "%s:DEVICE ERR - UNRECOVERABLE DATA LOSS\n",
+                       __FUNCTION__);
+                dev_kfree_skb_any(skb);
+                privptr->stats.tx_dropped++;
+                privptr->stats.tx_errors++;
+                privptr->stats.tx_carrier_errors++;
+                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                goto done;
+        }
+
+        dev->trans_start = jiffies;
+        if(transmit_skb(privptr->channel[WRITE], skb) != 0)
+        {
+                printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR"
+                       ": Data Lost \n",
+                       __FUNCTION__);
+                printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR"
+                       " - UNRECOVERABLE DATA LOSS\n",
+                       __FUNCTION__);
+                dev_kfree_skb_any(skb);
+                privptr->stats.tx_dropped++;
+                privptr->stats.tx_errors++;
+                privptr->stats.tx_carrier_errors++;
+                ctcmpc_clear_busy(dev);
+                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);
+                goto done;
+        }
+        ctcmpc_clear_busy(dev);
+        done:
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit:  %s()\n", __FUNCTION__);
+#endif
+
+        return(0);      /*handle freeing of skb here */
+}
+
+/**
+ * Sets MTU of an interface.
+ *
+ * @param dev     Pointer to interface struct.
+ * @param new_mtu The new MTU to use for this interface.
+ *
+ * @return 0 on success, -EINVAL if MTU is out of valid range.
+ *         (valid range is 576 .. 65527). If VM is on the
+ *         remote side, maximum MTU is 32760, however this is
+ *         <em>not</em> checked here.
+ */
+static int
+ctcmpc_change_mtu(struct net_device * dev, int new_mtu)
+{
+
+        struct ctc_priv *privptr = (struct ctc_priv *) dev->priv;
+
+#ifdef DEBUG
+ctcmpc_pr_debug("ctcmpc enter: %s()\n",
+                __FUNCTION__);
+#endif
+
+        if((new_mtu < 576) || (new_mtu > 65527) ||
+           (new_mtu > (privptr->channel[READ]->max_bufsize -
+                       TH_HEADER_LENGTH )))
+                return -EINVAL;
+        dev->mtu = new_mtu;
+        dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc exit : %s()\n",
+                        __func__);
+#endif
+        return 0;
+}
+
+/**
+ * Returns interface statistics of a device.
+ *
+ * @param dev Pointer to interface struct.
+ *
+ * @return Pointer to stats struct of this interface.
+ */
+static struct net_device_stats *
+ctcmpc_stats(struct net_device * dev)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s()\n",
+                        __func__);
+#endif
+        return &((struct ctc_priv *) dev->priv)->stats;
+}
+
+/*
+ * sysfs attributes
+ */
+static ssize_t
+loglevel_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+        struct ctc_priv *priv;
+
+        priv = dev->driver_data;
+        if(!priv)
+                return -ENODEV;
+        return sprintf(buf, "%d\n", loglevel);
+}
+
+static ssize_t
+loglevel_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+        struct ctc_priv *priv;
+        int ll1;
+
+        priv = dev->driver_data;
+        if(!priv)
+                return -ENODEV;
+        sscanf(buf, "%i", &ll1);
+
+        if((ll1 > CTC_LOGLEVEL_MAX) || (ll1 < 0))
+                return -EINVAL;
+        loglevel = ll1;
+        return count;
+}
+
+static void
+ctcmpc_print_statistics(struct ctc_priv *priv)
+{
+        char *sbuf;
+        char *p;
+
+        if(!priv)
+                return;
+        sbuf = (char *)kmalloc(2048, GFP_KERNEL);
+        if(sbuf == NULL)
+                return;
+        p = sbuf;
+
+        p += sprintf(p, "  Device FSM state: %s\n",
+                     fsm_getstate_str(priv->fsm));
+        p += sprintf(p, "  RX channel FSM state: %s\n",
+                     fsm_getstate_str(priv->channel[READ]->fsm));
+        p += sprintf(p, "  TX channel FSM state: %s\n",
+                     fsm_getstate_str(priv->channel[WRITE]->fsm));
+        p += sprintf(p, "  Max. TX buffer used: %ld\n",
+                     priv->channel[WRITE]->prof.maxmulti);
+        p += sprintf(p, "  Max. chained SKBs: %ld\n",
+                     priv->channel[WRITE]->prof.maxcqueue);
+        p += sprintf(p, "  TX single write ops: %ld\n",
+                     priv->channel[WRITE]->prof.doios_single);
+        p += sprintf(p, "  TX multi write ops: %ld\n",
+                     priv->channel[WRITE]->prof.doios_multi);
+        p += sprintf(p, "  Netto bytes written: %ld\n",
+                     priv->channel[WRITE]->prof.txlen);
+        p += sprintf(p, "  Max. TX IO-time: %ld\n",
+                     priv->channel[WRITE]->prof.tx_time);
+
+        ctcmpc_pr_debug("Statistics for %s:\n%s",
+                        priv->channel[WRITE]->netdev->name, sbuf);
+        kfree(sbuf);
+        return;
+}
+
+static ssize_t
+stats_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+        struct ctc_priv *priv = dev->driver_data;
+        if(!priv)
+                return -ENODEV;
+        ctcmpc_print_statistics(priv);
+        return sprintf(buf, "0\n");
+}
+
+static ssize_t
+stats_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+        struct ctc_priv *priv = dev->driver_data;
+        if(!priv)
+                return -ENODEV;
+        /* Reset statistics */
+        memset(&priv->channel[WRITE]->prof, 0,
+               sizeof(priv->channel[WRITE]->prof));
+        return count;
+}
+
+static DEVICE_ATTR(loglevel, 0644, loglevel_show, loglevel_write);
+static DEVICE_ATTR(stats, 0644, stats_show, stats_write);
+
+static int
+ctcmpc_add_attributes(struct device *dev)
+{
+        device_create_file(dev, &dev_attr_loglevel);
+        device_create_file(dev, &dev_attr_stats);
+        return 0;
+}
+
+static void
+ctcmpc_remove_attributes(struct device *dev)
+{
+        device_remove_file(dev, &dev_attr_stats);
+        device_remove_file(dev, &dev_attr_loglevel);
+}
+
+
+static void
+ctcmpc_netdev_unregister(struct net_device * dev)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s()\n",
+                        __func__);
+#endif
+
+        struct ctc_priv *privptr;
+
+        if(!dev)
+                return;
+        privptr = (struct ctc_priv *) dev->priv;
+        unregister_netdev(dev);
+}
+
+static int
+ctcmpc_netdev_register(struct net_device * dev)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s()\n",
+                        __func__);
+#endif
+        return register_netdev(dev);
+}
+
+static void
+ctcmpc_free_netdevice(struct net_device * dev, int free_dev)
+{
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s()\n",
+                        __func__);
+#endif
+        struct ctc_priv *privptr;
+        struct mpc_group *grpptr;
+        if(!dev)
+                return;
+        privptr = dev->priv;
+        if(privptr)
+        {
+                grpptr = privptr->mpcg;
+                if(privptr->fsm)
+                        kfree_fsm(privptr->fsm);
+                if(grpptr)
+                {
+                        if(grpptr->fsm)
+                                kfree_fsm(grpptr->fsm);
+                        if(grpptr->xid_skb)
+                                dev_kfree_skb(grpptr->xid_skb);
+                        if(grpptr->rcvd_xid_skb)
+                                dev_kfree_skb(grpptr->rcvd_xid_skb);
+                        tasklet_kill(&grpptr->mpc_tasklet2);
+                        kfree(grpptr);
+                }
+                if(privptr->xid)
+                        kfree(privptr->xid);
+                kfree(privptr);
+        }
+#ifdef MODULE
+        if(free_dev)
+                free_netdev(dev);
+#endif
+}
+
+/**
+ * Initialize everything of the net device except the name and the
+ * channel structs.
+ */
+static struct net_device *
+ctcmpc_init_netdevice(struct net_device * dev, int alloc_device,
+                      struct ctc_priv *privptr)
+{
+        struct mpc_group *grpptr;
+        //     int      priv_size;
+
+        if(!privptr)
+                return NULL;
+#ifdef DEBUG
+        ctcmpc_pr_debug("ctcmpc enter: %s()\n",
+                        __func__);
+#endif
+        if(alloc_device)
+        {
+                dev = kmalloc(sizeof (struct net_device), GFP_KERNEL);
+                if(!dev)
+                        return NULL;
+#ifdef DEBUG
+                ctcmpc_pr_debug("kmalloc dev:  %s()\n", __FUNCTION__);
+#endif
+                memset(dev, 0, sizeof (struct net_device));
+        }
+
+        dev->priv = privptr;
+        privptr->fsm = init_fsm("ctcdev", dev_state_names,
+                                dev_event_names, NR_DEV_STATES, NR_DEV_EVENTS,
+                                dev_fsm, DEV_FSM_LEN, GFP_KERNEL);
+        if(privptr->fsm == NULL)
+        {
+                if(alloc_device)
+                        kfree(dev);
+                return NULL;
+        }
+        fsm_newstate(privptr->fsm, DEV_STATE_STOPPED);
+        fsm_settimer(privptr->fsm, &privptr->restart_timer);
+        /********************************************************/
+        /*  MPC Group Initializations                           */
+        /********************************************************/
+        privptr->mpcg = kmalloc(sizeof(struct mpc_group),GFP_KERNEL);
+        if(privptr->mpcg == NULL)
+        {
+                if(alloc_device)
+                        kfree(dev);
+                return NULL;
+        }
+        grpptr = privptr->mpcg;
+        memset(grpptr, 0, sizeof(struct mpc_group));
+
+        grpptr->fsm = init_fsm("mpcg", mpcg_state_names,
+                               mpcg_event_names, NR_MPCG_STATES, NR_MPCG_EVENTS,
+                               mpcg_fsm, MPCG_FSM_LEN, GFP_KERNEL);
+        if(grpptr->fsm == NULL)
+        {
+                kfree(grpptr);
+                grpptr = NULL;
+                if(alloc_device)
+                        kfree(dev);
+                return NULL;
+        }
+
+        fsm_newstate(grpptr->fsm, MPCG_STATE_RESET);
+        fsm_settimer(grpptr->fsm,&grpptr->timer);
+
+
+        grpptr->xid_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT,
+                                          GFP_ATOMIC|GFP_DMA);
+        if(grpptr->xid_skb == NULL)
+        {
+                printk(KERN_INFO
+                       "Couldn't alloc MPCgroup xid_skb\n");
+                kfree_fsm(grpptr->fsm);
+                grpptr->fsm = NULL;
+                kfree(grpptr);
+                grpptr = NULL;
+                if(alloc_device)
+                        kfree(dev);
+                return NULL;
+        }
+        /*  base xid for all channels in group  */
+        grpptr->xid_skb_data = grpptr->xid_skb->data;
+        grpptr->xid_th = (struct th_header *)grpptr->xid_skb->data;
+        memcpy(skb_put(grpptr->xid_skb,TH_HEADER_LENGTH),
+               &thnorm,TH_HEADER_LENGTH);
+
+        privptr->xid = grpptr->xid = (struct xid2 *)grpptr->xid_skb->tail;
+        memcpy(skb_put(grpptr->xid_skb,XID2_LENGTH),&init_xid,XID2_LENGTH);
+        privptr->xid->xid2_adj_id = jiffies | 0xfff00000;
+        privptr->xid->xid2_sender_id = jiffies;
+
+        grpptr->xid_id = (char *)grpptr->xid_skb->tail;
+        memcpy(skb_put(grpptr->xid_skb,4),"VTAM",4);
+
+
+        grpptr->rcvd_xid_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT,
+                                               GFP_ATOMIC|GFP_DMA);
+        if(grpptr->rcvd_xid_skb == NULL)
+        {
+                printk(KERN_INFO
+                       "Couldn't alloc MPCgroup rcvd_xid_skb\n");
+                kfree_fsm(grpptr->fsm);
+                grpptr->fsm = NULL;
+                dev_kfree_skb(grpptr->xid_skb);
+                grpptr->xid_skb = NULL;
+                grpptr->xid_id = NULL;
+                grpptr->xid_skb_data = NULL;
+                grpptr->xid_th = NULL;
+                kfree(grpptr);
+                grpptr = NULL;
+                privptr->xid = NULL;
+                if(alloc_device)
+                        kfree(dev);
+                return NULL;
+        }
+        grpptr->rcvd_xid_data =  grpptr->rcvd_xid_skb->data;
+        grpptr->rcvd_xid_th =   (struct th_header *)grpptr->rcvd_xid_skb->data;
+        memcpy(skb_put(grpptr->rcvd_xid_skb,TH_HEADER_LENGTH),
+               &thnorm,
+               TH_HEADER_LENGTH);
+        grpptr->saved_xid2 = NULL;
+
+        tasklet_init(&grpptr->mpc_tasklet2,
+                     ctc_mpc_group_ready,
+                     (unsigned long)dev);
+        /********************************************************/
+        /*  MPC Group Initializations				 */
+        /********************************************************/
+        dev->mtu = MPC_BUFSIZE_DEFAULT - TH_HEADER_LENGTH - PDU_HEADER_LENGTH;
+        dev->hard_start_xmit = ctcmpc_tx;
+        dev->open = ctcmpc_open;
+        dev->stop = ctcmpc_close;
+        dev->get_stats = ctcmpc_stats;
+        dev->change_mtu = ctcmpc_change_mtu;
+        dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH;
+        dev->addr_len = 0;
+        dev->type = ARPHRD_SLIP;
+        dev->tx_queue_len = 100;
+        dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+        SET_MODULE_OWNER(dev);
+        return dev;
+}
+
+static ssize_t
+ctcmpc_proto_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+        struct ctc_priv *priv;
+
+        priv = dev->driver_data;
+        if(!priv)
+                return -ENODEV;
+
+        return sprintf(buf, "%d\n", priv->protocol);
+}
+
+static ssize_t
+ctcmpc_proto_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+        struct ctc_priv *priv;
+        int value;
+
+        pr_debug("%s() called\n", __FUNCTION__);
+
+        priv = dev->driver_data;
+        if(!priv)
+                return -ENODEV;
+        sscanf(buf, "%u", &value);
+        if(value != CTC_PROTO_MPC)
+                return -EINVAL;
+        priv->protocol = value;
+
+        return count;
+}
+
+static DEVICE_ATTR(protocol, 0644, ctcmpc_proto_show, ctcmpc_proto_store);
+
+static ssize_t
+ctcmpc_type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+        struct ccwgroup_device *cgdev;
+
+        cgdev = to_ccwgroupdev(dev);
+        if(!cgdev)
+                return -ENODEV;
+
+        return sprintf(buf, "%s\n",
+                       cu3088_type[cgdev->cdev[0]->id.driver_info]);
+}
+
+static DEVICE_ATTR(type, 0444, ctcmpc_type_show, NULL);
+
+static struct attribute *ctcmpc_attr[] = {
+        &dev_attr_protocol.attr,
+        &dev_attr_type.attr,
+        NULL,
+};
+
+static struct attribute_group ctcmpc_attr_group = {
+        .attrs = ctcmpc_attr,
+};
+
+static int
+ctcmpc_add_files(struct device *dev)
+{
+        pr_debug("%s() called\n", __FUNCTION__);
+
+        return sysfs_create_group(&dev->kobj, &ctcmpc_attr_group);
+}
+
+static void
+ctcmpc_remove_files(struct device *dev)
+{
+        pr_debug("%s() called\n", __FUNCTION__);
+
+        sysfs_remove_group(&dev->kobj, &ctcmpc_attr_group);
+}
+
+/**
+ * Add ctc specific attributes.
+ * Add ctc private data.
+ *
+ * @param cgdev pointer to ccwgroup_device just added
+ *
+ * @returns 0 on success, !0 on failure.
+ */
+
+static int
+ctcmpc_probe_device(struct ccwgroup_device *cgdev)
+{
+        struct ctc_priv *priv;
+        int rc;
+
+        pr_debug("%s() called\n", __FUNCTION__);
+
+        if(!get_device(&cgdev->dev))
+                return -ENODEV;
+
+        priv = kmalloc(sizeof (struct ctc_priv), GFP_KERNEL);
+        if(!priv)
+        {
+                ctcmpc_pr_err("%s: Out of memory\n", __func__);
+                put_device(&cgdev->dev);
+                return -ENOMEM;
+        }
+
+        memset(priv, 0, sizeof (struct ctc_priv));
+        rc = ctcmpc_add_files(&cgdev->dev);
+        if(rc)
+        {
+                kfree(priv);
+                put_device(&cgdev->dev);
+                return rc;
+        }
+
+        cgdev->cdev[0]->handler = ctcmpc_irq_handler;
+        cgdev->cdev[1]->handler = ctcmpc_irq_handler;
+        cgdev->dev.driver_data = priv;
+
+        return 0;
+}
+
+/**
+ *
+ * Setup an interface.
+ *
+ * @param cgdev  Device to be setup.
+ *
+ * @returns 0 on success, !0 on failure.
+ */
+static int
+ctcmpc_new_device(struct ccwgroup_device *cgdev)
+{
+        char read_id[CTC_ID_SIZE];
+        char write_id[CTC_ID_SIZE];
+        int direction;
+        enum channel_types type;
+        struct ctc_priv *privptr;
+        struct net_device *dev;
+        int ret;
+
+        pr_debug("%s() called\n", __FUNCTION__);
+
+        privptr = cgdev->dev.driver_data;
+        if(!privptr)
+                return -ENODEV;
+
+        privptr->protocol = CTC_PROTO_MPC;
+        type = get_channel_type(&cgdev->cdev[0]->id);
+
+        snprintf(read_id, CTC_ID_SIZE, "ch-%s", cgdev->cdev[0]->dev.bus_id);
+        snprintf(write_id, CTC_ID_SIZE, "ch-%s", cgdev->cdev[1]->dev.bus_id);
+
+        if(ctcmpc_add_channel(cgdev->cdev[0], type))
+                return -ENOMEM;
+        if(ctcmpc_add_channel(cgdev->cdev[1], type))
+                return -ENOMEM;
+
+        ret = ccw_device_set_online(cgdev->cdev[0]);
+        if(ret != 0)
+        {
+                printk(KERN_WARNING
+                       "ccw_device_set_online (cdev[0]) failed with ret = %d\n"
+                       , ret);
+        }
+
+        ret = ccw_device_set_online(cgdev->cdev[1]);
+        if(ret != 0)
+        {
+                printk(KERN_WARNING
+                       "ccw_device_set_online (cdev[1]) failed with ret = %d\n"
+                       , ret);
+        }
+
+        dev = ctcmpc_init_netdevice(NULL, 1, privptr);
+
+        if(!dev)
+        {
+                ctcmpc_pr_warn("ctcmpc_init_netdevice failed\n");
+                goto out;
+        }
+
+        strlcpy(dev->name, "ctcmpc%d", IFNAMSIZ);
+
+        for(direction = READ; direction <= WRITE; direction++)
+        {
+                privptr->channel[direction] =
+                channel_get(type, direction == READ ? read_id : write_id,
+                            direction);
+                if(privptr->channel[direction] == NULL)
+                {
+                        if(direction == WRITE)
+                                channel_free(privptr->channel[READ]);
+
+                        ctcmpc_free_netdevice(dev, 1);
+                        goto out;
+                }
+                privptr->channel[direction]->netdev = dev;
+                privptr->channel[direction]->protocol = privptr->protocol;
+                privptr->channel[direction]->max_bufsize = MPC_BUFSIZE_DEFAULT;
+        }
+        /* sysfs magic */
+        SET_NETDEV_DEV(dev, &cgdev->dev);
+
+        if(ctcmpc_netdev_register(dev) != 0)
+        {
+                ctcmpc_free_netdevice(dev, 1);
+                goto out;
+        }
+
+        ctcmpc_add_attributes(&cgdev->dev);
+
+        strlcpy(privptr->fsm->name, dev->name, sizeof (privptr->fsm->name));
+
+        print_banner();
+
+        ctcmpc_pr_info("%s: read: %s, write: %s, proto: %d\n",
+                       dev->name, privptr->channel[READ]->id,
+                       privptr->channel[WRITE]->id, privptr->protocol);
+
+        return 0;
+        out:
+        ccw_device_set_offline(cgdev->cdev[1]);
+        ccw_device_set_offline(cgdev->cdev[0]);
+
+        return -ENODEV;
+}
+
+/**
+ * Shutdown an interface.
+ *
+ * @param cgdev  Device to be shut down.
+ *
+ * @returns 0 on success, !0 on failure.
+ */
+static int
+ctcmpc_shutdown_device(struct ccwgroup_device *cgdev)
+{
+        struct ctc_priv *priv;
+        struct net_device *ndev;
+
+
+        pr_debug("%s() called\n", __FUNCTION__);
+
+        priv = cgdev->dev.driver_data;
+        ndev = NULL;
+        if(!priv)
+                return -ENODEV;
+
+        if(priv->channel[READ])
+        {
+                ndev = priv->channel[READ]->netdev;
+
+                /* Close the device */
+                ctcmpc_close(ndev);
+                ndev->flags &=~IFF_RUNNING;
+
+                ctcmpc_remove_attributes(&cgdev->dev);
+
+                channel_free(priv->channel[READ]);
+        }
+        if(priv->channel[WRITE])
+                channel_free(priv->channel[WRITE]);
+
+        if(ndev)
+        {
+                ctcmpc_netdev_unregister(ndev);
+                ndev->priv = NULL;
+                ctcmpc_free_netdevice(ndev, 1);
+        }
+
+        if(priv->fsm)
+                kfree_fsm(priv->fsm);
+
+        ccw_device_set_offline(cgdev->cdev[1]);
+        ccw_device_set_offline(cgdev->cdev[0]);
+
+        if(priv->channel[READ])
+                channel_remove(priv->channel[READ]);
+        if(priv->channel[WRITE])
+                channel_remove(priv->channel[WRITE]);
+
+        priv->channel[READ] = priv->channel[WRITE] = NULL;
+
+        return 0;
+
+}
+
+static void
+ctcmpc_remove_device(struct ccwgroup_device *cgdev)
+{
+        struct ctc_priv *priv;
+
+        pr_debug("%s() called\n", __FUNCTION__);
+
+        priv = cgdev->dev.driver_data;
+        if(!priv)
+                return;
+        if(cgdev->state == CCWGROUP_ONLINE)
+                ctcmpc_shutdown_device(cgdev);
+        ctcmpc_remove_files(&cgdev->dev);
+        cgdev->dev.driver_data = NULL;
+        kfree(priv);
+        put_device(&cgdev->dev);
+}
+
+static struct ccwgroup_driver ctcmpc_group_driver = {
+        .owner       = THIS_MODULE,
+        .name        = "ctcmpc",
+        .max_slaves  = 2,
+        .driver_id   = 0xC3E3C3D4,
+        .probe       = ctcmpc_probe_device,
+        .remove      = ctcmpc_remove_device,
+        .set_online  = ctcmpc_new_device,
+        .set_offline = ctcmpc_shutdown_device,
+};
+
+/**
+ * Module related routines
+ *****************************************************************************/
+
+/**
+ * Prepare to be unloaded. Free IRQ's and release all resources.
+ * This is called just before this module is unloaded. It is
+ * <em>not</em> called, if the usage count is !0, so we don't need to check
+ * for that.
+ */
+static void __exit
+ctcmpc_exit(void)
+{
+        unregister_cu3088_discipline(&ctcmpc_group_driver);
+        ctcmpc_pr_info("CTCMPC driver unloaded\n");
+}
+
+/**
+ * Initialize module.
+ * This is called just after the module is loaded.
+ *
+ * @return 0 on success, !0 on error.
+ */
+static int __init
+ctcmpc_init(void)
+{
+        int ret = 0;
+
+        print_banner();
+        ret = register_cu3088_discipline(&ctcmpc_group_driver);
+        return ret;
+}
+
+module_init(ctcmpc_init);
+module_exit(ctcmpc_exit);
+
+/* --- This is the END my friend --- */
diff -purN linux-2.6.17.s390/drivers/s390/net/ctcmpcfsm.h linux-2.6.17.s390_xxx/drivers/s390/net/ctcmpcfsm.h
--- linux-2.6.17.s390/drivers/s390/net/ctcmpcfsm.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.17.s390_xxx/drivers/s390/net/ctcmpcfsm.h	2006-08-11 14:02:43.000000000 +0200
@@ -0,0 +1,220 @@
+#include "fsm.h"
+
+/**
+  * MPC Group Station FSM States
+
+State Name		When In This State
+======================	=======================================
+MPCG_STATE_RESET	Initial State When Driver Loaded
+			We receive and send NOTHING
+
+MPCG_STATE_INOP         INOP Received.
+			Group level non-recoverable error
+
+MPCG_STATE_READY	XID exchanges for at least 1 write and
+			1 read channel have completed.
+			Group is ready for data transfer.
+
+States from ctc_mpc_alloc_channel
+==============================================================
+MPCG_STATE_XID2INITW	Awaiting XID2(0) Initiation
+			      ATTN from other side will start
+			      XID negotiations.
+			      Y-side protocol only.
+
+MPCG_STATE_XID2INITX	XID2(0) negotiations are in progress.
+			      At least 1, but not all, XID2(0)'s
+			      have been received from partner.
+
+MPCG_STATE_XID7INITW	XID2(0) complete
+			      No XID2(7)'s have yet been received.
+			      XID2(7) negotiations pending.
+
+MPCG_STATE_XID7INITX	XID2(7) negotiations in progress.
+			      At least 1, but not all, XID2(7)'s
+			      have been received from partner.
+
+MPCG_STATE_XID7INITF	XID2(7) negotiations complete.
+			      Transitioning to READY.
+
+MPCG_STATE_READY	      Ready for Data Transfer.
+
+
+States from ctc_mpc_establish_connectivity call
+==============================================================
+MPCG_STATE_XID0IOWAIT	Initiating XID2(0) negotiations.
+			      X-side protocol only.
+			      ATTN-BUSY from other side will convert
+			      this to Y-side protocol and the
+			      ctc_mpc_alloc_channel flow will begin.
+
+MPCG_STATE_XID0IOWAIX	XID2(0) negotiations are in progress.
+			      At least 1, but not all, XID2(0)'s
+			      have been received from partner.
+
+MPCG_STATE_XID7INITI	XID2(0) complete
+			      No XID2(7)'s have yet been received.
+			      XID2(7) negotiations pending.
+
+MPCG_STATE_XID7INITZ	XID2(7) negotiations in progress.
+			      At least 1, but not all, XID2(7)'s
+			      have been received from partner.
+
+MPCG_STATE_XID7INITF	XID2(7) negotiations complete.
+			      Transitioning to READY.
+
+MPCG_STATE_READY	      Ready for Data Transfer.
+
+*/
+
+enum mpcg_events {
+	MPCG_EVENT_INOP,
+	MPCG_EVENT_DISCONC,
+	MPCG_EVENT_XID0DO,
+	MPCG_EVENT_XID2,
+	MPCG_EVENT_XID2DONE,
+	MPCG_EVENT_XID7DONE,
+	MPCG_EVENT_TIMER,
+	MPCG_EVENT_DOIO,
+	NR_MPCG_EVENTS,
+};
+
+static const char *mpcg_event_names[]  = {
+	"INOP Condition",
+	"Discontact Received",
+	"Channel Active - Start XID",
+	"XID2 Received",
+	"XID0 Complete",
+	"XID7 Complete",
+	"XID Setup Timer",
+	"XID DoIO",
+};
+
+
+enum mpcg_states {
+	MPCG_STATE_RESET,
+	MPCG_STATE_INOP,
+	MPCG_STATE_XID2INITW,
+	MPCG_STATE_XID2INITX,
+	MPCG_STATE_XID7INITW,
+	MPCG_STATE_XID7INITX,
+	MPCG_STATE_XID0IOWAIT,
+	MPCG_STATE_XID0IOWAIX,
+	MPCG_STATE_XID7INITI,
+	MPCG_STATE_XID7INITZ,
+	MPCG_STATE_XID7INITF,
+	MPCG_STATE_FLOWC,
+	MPCG_STATE_READY,
+	NR_MPCG_STATES,
+};
+
+
+static const char *mpcg_state_names[] =  {
+	"Reset",
+	"INOP",
+	"Passive XID- XID0 Pending Start",
+	"Passive XID- XID0 Pending Complete",
+	"Passive XID- XID7 Pending P1 Start",
+	"Passive XID- XID7 Pending P2 Complete",
+	"Active  XID- XID0 Pending Start",
+	"Active  XID- XID0 Pending Complete",
+	"Active  XID- XID7 Pending Start",
+	"Active  XID- XID7 Pending Complete ",
+	"XID        - XID7 Complete ",
+	"FLOW CONTROL ON",
+	"READY",
+};
+
+
+static void fsm_action_nop(fsm_instance *, int, void *);
+static void mpc_action_go_inop(fsm_instance *, int, void *);
+static void mpc_action_timeout(fsm_instance *, int, void *);
+static void mpc_action_yside_xid(fsm_instance *, int, void *);
+static void mpc_action_doxid0(fsm_instance *, int, void *);
+static void mpc_action_doxid7(fsm_instance *, int, void *);
+static void mpc_action_xside_xid(fsm_instance *, int, void *);
+static void mpc_action_rcvd_xid0(fsm_instance *, int, void *);
+static void mpc_action_rcvd_xid7(fsm_instance *, int, void *);
+static void ctcmpc_action_attn(fsm_instance *, int, void *);
+static void ctcmpc_action_attnbusy(fsm_instance *, int, void *);
+static void ctcmpc_action_resend(fsm_instance *, int, void *);
+static void mpc_action_go_ready(fsm_instance *, int, void *);
+static void mpc_action_discontact(fsm_instance *, int, void *);
+
+
+/**
+ * The MPC Group Station FSM
+ *   22 events
+ */
+static const fsm_node mpcg_fsm[] = {
+{  MPCG_STATE_RESET,           MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_INOP,		MPCG_EVENT_INOP,        fsm_action_nop},
+
+{  MPCG_STATE_FLOWC,		MPCG_EVENT_INOP,        mpc_action_go_inop},
+
+{  MPCG_STATE_READY,		MPCG_EVENT_DISCONC,     mpc_action_discontact},
+{  MPCG_STATE_READY,		MPCG_EVENT_INOP,        mpc_action_go_inop},
+
+{  MPCG_STATE_XID2INITW,	MPCG_EVENT_XID0DO,      mpc_action_doxid0},
+{  MPCG_STATE_XID2INITW,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid0},
+{  MPCG_STATE_XID2INITW,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID2INITW,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID2INITW,	MPCG_EVENT_DOIO,        mpc_action_yside_xid},
+
+{  MPCG_STATE_XID2INITX,	MPCG_EVENT_XID0DO,      mpc_action_doxid0},
+{  MPCG_STATE_XID2INITX,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid0},
+{  MPCG_STATE_XID2INITX,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID2INITX,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID2INITX,	MPCG_EVENT_DOIO,        mpc_action_yside_xid},
+
+{  MPCG_STATE_XID7INITW,	MPCG_EVENT_XID2DONE,    mpc_action_doxid7},
+{  MPCG_STATE_XID7INITW,	MPCG_EVENT_DISCONC,     mpc_action_discontact},
+{  MPCG_STATE_XID7INITW,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid7},
+{  MPCG_STATE_XID7INITW,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID7INITW,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID7INITW,	MPCG_EVENT_XID7DONE,    mpc_action_doxid7},
+{  MPCG_STATE_XID7INITW,	MPCG_EVENT_DOIO,        mpc_action_yside_xid},
+
+{  MPCG_STATE_XID7INITX,	MPCG_EVENT_DISCONC,     mpc_action_discontact},
+{  MPCG_STATE_XID7INITX,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid7},
+{  MPCG_STATE_XID7INITX,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID7INITX,	MPCG_EVENT_XID7DONE,    mpc_action_doxid7},
+{  MPCG_STATE_XID7INITX,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID7INITX,	MPCG_EVENT_DOIO,        mpc_action_yside_xid},
+
+{  MPCG_STATE_XID0IOWAIT,	MPCG_EVENT_XID0DO,      mpc_action_doxid0},
+{  MPCG_STATE_XID0IOWAIT,	MPCG_EVENT_DISCONC,     mpc_action_discontact},
+{  MPCG_STATE_XID0IOWAIT,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid0},
+{  MPCG_STATE_XID0IOWAIT,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID0IOWAIT,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID0IOWAIT,	MPCG_EVENT_DOIO,        mpc_action_xside_xid},
+
+{  MPCG_STATE_XID0IOWAIX,	MPCG_EVENT_XID0DO,	mpc_action_doxid0},
+{  MPCG_STATE_XID0IOWAIX,	MPCG_EVENT_DISCONC,     mpc_action_discontact},
+{  MPCG_STATE_XID0IOWAIX,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid0},
+{  MPCG_STATE_XID0IOWAIX,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID0IOWAIX,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID0IOWAIX,	MPCG_EVENT_DOIO,        mpc_action_xside_xid},
+
+{  MPCG_STATE_XID7INITI,	MPCG_EVENT_XID2DONE,    mpc_action_doxid7},
+{  MPCG_STATE_XID7INITI,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid7},
+{  MPCG_STATE_XID7INITI,	MPCG_EVENT_DISCONC,     mpc_action_discontact},
+{  MPCG_STATE_XID7INITI,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID7INITI,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID7INITI,	MPCG_EVENT_XID7DONE,    mpc_action_doxid7},
+{  MPCG_STATE_XID7INITI,	MPCG_EVENT_DOIO,        mpc_action_xside_xid},
+
+{  MPCG_STATE_XID7INITZ,	MPCG_EVENT_XID2,        mpc_action_rcvd_xid7},
+{  MPCG_STATE_XID7INITZ,	MPCG_EVENT_XID7DONE,    mpc_action_doxid7},
+{  MPCG_STATE_XID7INITZ,	MPCG_EVENT_DISCONC,     mpc_action_discontact},
+{  MPCG_STATE_XID7INITZ,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID7INITZ,	MPCG_EVENT_TIMER,       mpc_action_timeout},
+{  MPCG_STATE_XID7INITZ,	MPCG_EVENT_DOIO,        mpc_action_xside_xid},
+
+{  MPCG_STATE_XID7INITF,	MPCG_EVENT_INOP,        mpc_action_go_inop},
+{  MPCG_STATE_XID7INITF,	MPCG_EVENT_XID7DONE,	mpc_action_go_ready},
+
+};
+
+static const int MPCG_FSM_LEN = sizeof(mpcg_fsm) / sizeof(fsm_node);
+
diff -purN linux-2.6.17.s390/drivers/s390/net/ctcmpc.h linux-2.6.17.s390_xxx/drivers/s390/net/ctcmpc.h
--- linux-2.6.17.s390/drivers/s390/net/ctcmpc.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.17.s390_xxx/drivers/s390/net/ctcmpc.h	2006-08-11 14:02:43.000000000 +0200
@@ -0,0 +1,48 @@
+/*
+ * CTC / ESCON network driver, mpc interface.
+ *
+ * Copyright (C) 2003 IBM United States, IBM Corporation
+ * Author(s): Belinda Thompson (belindat@us.ibm.com)
+ *            Andy Richter (richtera@us.ibm.com)
+ *
+ * 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.
+ *
+ */
+
+#ifndef _CTCMPC_H_
+#define _CTCMPC_H_
+
+#include "ctcmpcfsm.h"
+
+typedef struct sk_buff sk_buff;
+typedef void (*callbacktypei2)(int,int);	      /* void (*void)(int,int) */
+typedef void (*callbacktypei3)(int,int,int);	  /* void (*void)(int,int,int) */
+
+/*  port_number is the mpc device 0,1,2 etc mpc2 is port_number 2 */
+/*  passive open  Just wait for XID2 exchange */
+/*  ctc_mpc_alloc channel(port_number,
+                                void(*callback)(port_number,max_write_size)) */
+extern  int ctc_mpc_alloc_channel(int,callbacktypei2);
+/* active open  Alloc then send XID2 */
+/*  ctc_mpc_establish_connectivity(port_number ,
+                              void(callback*)(port_number,rc,max_write_size)) */
+extern void ctc_mpc_establish_connectivity(int,callbacktypei3);
+extern void ctc_mpc_dealloc_ch(int);
+extern void ctc_mpc_flow_control(int,int);
+EXPORT_SYMBOL(ctc_mpc_alloc_channel);
+EXPORT_SYMBOL(ctc_mpc_establish_connectivity);
+EXPORT_SYMBOL(ctc_mpc_dealloc_ch);
+EXPORT_SYMBOL(ctc_mpc_flow_control);
+#endif
diff -purN linux-2.6.17.s390/drivers/s390/net/Kconfig linux-2.6.17.s390_xxx/drivers/s390/net/Kconfig
--- linux-2.6.17.s390/drivers/s390/net/Kconfig	2006-06-18 03:49:35.000000000 +0200
+++ linux-2.6.17.s390_xxx/drivers/s390/net/Kconfig	2006-08-11 14:40:14.000000000 +0200
@@ -60,6 +60,17 @@ config CLAW
 	  To compile as a module choose M here:  The module will be called
 	  claw.ko to compile into the kernel choose Y
 
+config MPC
+	tristate "MPC SNA device support"
+	depends on NETDEVICES
+	help
+	  This driver supports channel-to-channel MPC SNA devices.
+	  MPC is a SNA protocol device used by Comm Server for Linux.
+	  If you don't have Comm Server for Linux you don't need the device.
+	  To compile as a module choose M here:  The module will be called
+	  ctcmpc.ko to compile into the kernel choose Y
+	  If you do not need SNA MPC device just say N
+
 config QETH
 	tristate "Gigabit Ethernet device support"
 	depends on NETDEVICES && IP_MULTICAST && QDIO