/*  INTERNAL VERSION:  051804b
 *
 * CTC / SNA/MPC 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>
 * 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.
 *
 * RELEASE-TAG: CTC/ESCON network driver $Revision: 1.2.2.2 $
 *
 */

#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/bitops.h>
#include <asm/uaccess.h>
#include <linux/wait.h>

DECLARE_WAIT_QUEUE_HEAD(my_queue);

#ifdef CONFIG_CHANDEV
        #define CTC_CHANDEV
#endif

#ifdef CTC_CHANDEV
        #include <asm/chandev.h>
        #define REQUEST_IRQ chandev_request_irq
        #define FREE_IRQ chandev_free_irq
#else
        #define REQUEST_IRQ request_irq
        #define FREE_IRQ free_irq
#endif

#include <asm/idals.h>
#include <asm/irq.h>

#include "ctcmpc.h"
#include "fsm.h"

#ifdef MODULE
MODULE_AUTHOR("(C) 2000 IBM Corp. by Fritz Elfert (felfert@millenux.com),"
              "Belinda Thompson (belindat@us.ibm.com)");
MODULE_DESCRIPTION("Linux for S/390 CTC/SNA MPC Driver");
        #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,12))
MODULE_LICENSE("GPL");
        #endif
        #ifndef CTC_CHANDEV
MODULE_PARM(mpc, "s");
MODULE_PARM_DESC(mpc,
                 "One or more definitions in the same format like the "
                 "kernel param for mpc.\n"
                 "E.g.: mpc0:0x700:0x701:4:mpc1:0x702:0x703:4\n");

char *mpc = NULL;
        #endif
#else
/**
 * Number of devices in monolithic (not module) driver version.
 */
        #define MAX_STATIC_DEVICES 16
#endif /* MODULE */

//#define DEBUG2 1
//#define DEBUGCCW 1
//#define DEBUGXID 1
//#define DEBUGDATA 1
//#define DEBUGSEQ 1
//#define DEBUG 1
//#undef DEBUG

#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  /* Disable Compatibility Mode */
#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

/**
 * Enum for classifying detected devices.
 */
enum channel_types
{
        /**
         * Device is not a channel.
         */
        channel_type_none,

        /**
         * Device is a channel, but we don't know
         * anything about it.
         */
        channel_type_unknown,
        /**
         * Device is an mpc capable channel.
         */
        channel_type_mpc,

        /**
         * Device is a CTC/A.
         */
        channel_type_ctca,

        /**
         * Device is a ESCON channel.
         */
        channel_type_escon,
        /**
         * Device is an unsupported model.
         */
        channel_type_unsupported
};

typedef enum channel_types channel_type_t;

#ifndef CTC_CHANDEV
static int ctc_no_auto = 0;
#endif

/**
 * If running on 64 bit, this must be changed. XXX Why? (bird)
 */
typedef unsigned long intparm_t;

#ifndef CTC_CHANDEV
/**
 * Definition of a per device parameter block
 */
        #define MAX_PARAM_NAME_LEN 11
typedef struct param_t
{
        struct param_t *next;
        int            read_dev;
        int            write_dev;
        __u16          proto;
        char           name[MAX_PARAM_NAME_LEN];
} param;

static param *params = NULL;
#endif

typedef struct
{
        unsigned long maxmulti;
        unsigned long maxcqueue;
        unsigned long doios_single;
        unsigned long doios_multi;
        unsigned long txlen;
        unsigned long tx_time;
        struct timeval send_stamp;
} ctc_profile;

/**
 * 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_t
{
        __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)); 
typedef struct xid2_t xid2;

#define XID2_LENGTH  (sizeof(xid2))

static const 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_t
{
        __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)); 
typedef struct th_header_t th_header;

static const 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 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_t
{
        __u32   th_last_seq;    
        __u32   th_resvd;       
}__attribute__ ((packed));
typedef struct th_addon_t th_addon;

struct th_sweep_t
{
        th_header th;
        th_addon  sw;
}__attribute__ ((packed));
typedef struct th_sweep_t th_sweep;

#define TH_HEADER_LENGTH (sizeof(th_header)) 
#define TH_SWEEP_LENGTH (sizeof(th_sweep)) 

#define PDU_LAST	0x80
#define PDU_CNTL	0x40
#define PDU_FIRST	0x20

struct pdu_t
{
        __u32   pdu_offset;
        __u8    pdu_flag;
        __u8    pdu_proto;   /*  0x01 is APPN SNA  */ 
        __u16   pdu_seq;
}__attribute__ ((packed));
typedef struct pdu_t pdu;
#define PDU_HEADER_LENGTH  (sizeof(pdu))

struct qllc_t
{
        __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));
typedef struct qllc_t qllc;


static void ctcmpc_bh(unsigned long);

/**
 * Definition of one channel
 */
struct channel_t
{

        /**
         * Pointer to next channel in list.
         */
        struct channel_t *next;
        __u16 devno;
        int irq;
        /**
         * Type of this channel.
         * CTC/A or Escon for valid channels.
         */
        channel_type_t type;
        /**
         * Misc. flags. See CHANNEL_FLAGS_... below
         */
        __u32 flags;
        /**
         * The protocol of this channel
         */
        __u16 protocol;
        /**
         * I/O and irq related stuff
         */
        ccw1_t *ccw;
        devstat_t *devstat;
        /**
         * RX/TX buffer size
         */
        __u32 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;
        sk_buff         *xid_skb;
        char            *xid_skb_data;
        th_header       *xid_th;
        xid2            *xid;
        char            *xid_id;
        th_header       *rcvd_xid_th;
        xid2            *rcvd_xid;
        char            *rcvd_xid_id;
        __u8            in_mpcgroup;
        fsm_timer       sweep_timer;
        struct sk_buff_head     sweep_queue;
        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;
        ctc_profile         prof;
        unsigned char       *trans_skb_data;
};
typedef struct channel_t channel;

#define CHANNEL_FLAGS_READ                0
#define CHANNEL_FLAGS_WRITE               1
#define CHANNEL_FLAGS_INUSE               2
#define CHANNEL_FLAGS_BUFSIZE_CHANGED     4
#define CHANNEL_FLAGS_RWMASK 	           1
#define CHANNEL_PRIMARY		   0x20   /* we are the x side */
#define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK)

/**
 * Linked list of all detected channels.
 */
static channel *channels = NULL;

#ifdef CTC_CHANDEV
static int activated;
#endif


/***
  * 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_t
{
        struct sk_buff        *skb;
        struct channel_t      *ch;
        struct xid2_t         *xid;
        struct th_sweep_t     *sweep;
        struct th_header_t    *th;
};
typedef struct mpcg_info_t mpcg_info;

struct mpc_group_t
{
        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;
        th_header *xid_th;
        xid2    *xid;
        char    *xid_id;
        th_header *rcvd_xid_th;
        sk_buff *rcvd_xid_skb;
        char    *rcvd_xid_data;
        __u8    in_sweep;
        __u8    roll;
        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 */
};
typedef struct mpc_group_t mpc_group;

typedef struct ctc_priv_t
{
        struct net_device_stats stats;
        unsigned long           tbusy;

        /**The MPC group struct of this interface
        */
        mpc_group               *mpcg;
        /**
         * The finite state machine of this interface.
         */
        fsm_instance            *fsm;
        /**
         * The protocol of this device
         */
        __u16                   protocol;
        channel                 *channel[2];
        xid2                    *xid;
        struct proc_dir_entry   *proc_dentry;
        struct proc_dir_entry   *proc_stat_entry;
        struct proc_dir_entry   *proc_ctrl_entry;
        int                     proc_registered;
        /**
         * Timer for restarting after I/O Errors
         */
        fsm_timer               restart_timer;

} ctc_priv;


static void ctcmpc_action_send_discontact(unsigned long);


/**
 * Compatibility macros for busy handling
 * of network devices.
 */
static __inline__ void 
ctcmpc_clear_busy(struct net_device *dev)
{

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s()\n", dev->name,__FUNCTION__);
#endif
        if(((ctc_priv *)dev->priv)->mpcg->in_sweep == 0)
        {
                clear_bit(0, &(((ctc_priv *)dev->priv)->tbusy));
                netif_wake_queue(dev);
        }

#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()\n", dev->name,__FUNCTION__);
#endif


}

static __inline__ int 
ctcmpc_test_and_set_busy(struct net_device *dev)
{

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s()\n", dev->name,__FUNCTION__);
#endif
        netif_stop_queue(dev);
        return test_and_set_bit(0, &((ctc_priv *)dev->priv)->tbusy);
}

#define SET_DEVICE_START(device, value)

static __inline__ int ctcmpc_checkalloc_buffer(channel *,int);
static void ctcmpc_purge_skb_queue(struct sk_buff_head *);


/**
 * Print Banner.
 */
static void 
print_banner(void)
{
        static int printed = 0;
        char vbuf[] = "$Revision: 1.2.2.2 $";
        char *version = vbuf;

        if(printed)
                return;
        if((version = strchr(version, ':')))
        {
                char *p = strchr(version + 1, '$');
                if(p)
                        *p = '\0';
        } else
                version = " ??? ";
        printk(KERN_INFO
               "CTC MPC driver Version%swith"
#ifndef CTC_CHANDEV
               "out"
#endif
               " CHANDEV support"
#ifdef DEBUG
               " (DEBUG-VERSION, " __DATE__ __TIME__ ")"
#endif
               " initialized\n", version);
        printed = 1;
}

static inline int 
gfp_type(void)
{
        return in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
}


/**
  * 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",
};

#ifndef CTC_CHANDEV
/**
 * Return type of a detected device.
 */
static channel_type_t channel_type (senseid_t *id)
{
        channel_type_t type = channel_type_none;

        switch(id->cu_type)
        {
                case 0x3088:
                        switch(id->cu_model)
                        {
                                case 0x1E:
                                        /**
                                         * 3088-1E = FICON channel
                                         */
                                case 0x08:
                                        /**
                                         * 3088-08 = CTCA
                                         */
                                case 0x1F:
                                        /**
                                         * 3088-1F = ESCON channel
                                         */
                                        type = channel_type_mpc;
                                        break;

                                        /**
                                         * 3088-01 = P390 OSA emulation
                                         */
                                case 0x01:
                                        /* fall thru */

                                        /**
                                         * 3088-60 = OSA/2 adapter
                                         */
                                case 0x60:
                                        /* fall thru */

                                        /**
                                         * 3088-61 = CISCO 7206 CLAW proto
                                         * on ESCON
                                         */
                                case 0x61:
                                        /* fall thru */

                                        /**
                                         * 3088-62 = OSA/D device
                                         */
                                case 0x62:
                                        type = channel_type_unsupported;
                                        break;

                                default:
                                        type = channel_type_unknown;
                                        printk(KERN_INFO
                                               "channel: Unknown model found "
                                               "3088-%02x\n", id->cu_model);
                        }
                        break;

                default:
                        type = channel_type_none;
        }
        return type;
}
#endif


/**
 * 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 (do_IO, halt_IO 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,

        /** 
         * Events, representing TX MPC buffer states
         */
        CH_EVENT_TX_LOMEM,
        CH_EVENT_TX_MEM_OK,
        CH_EVENT_RSWEEP1_TIMER,

        /**
         * MUST be always the last element!!
         */
        NR_CH_EVENTS,
};

static const char *ch_event_names[] = {
        "do_IO success",
        "do_IO busy",
        "do_IO enodev",
        "do_IO ioerr",
        "do_IO 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",

        "TX buffer shortage",
        "TX buffer shortage relieved",
        "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_STATE_TXLOMEM,
        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",
        "TX Buffers low",
        "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(channel *, struct sk_buff *);

#if defined(DEBUGDATA) || defined(DEBUGXID)\
  || defined(DEBUGCCW) || defined(DEBUGSEQ)
/*-------------------------------------------------------------------*
* 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  */

#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;
        th_header *header;
        pdu *pheader;
        int bl = skb->len;
        int i;

        if(p == NULL) return;
        p += offset;
        header = (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 = (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");
}

#endif

/**
 * Dummy NOP action for statemachines
 */
static void 
fsm_action_nop(fsm_instance *fi, int event, void *arg)
{
}

static int ctcmpc_open(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 (channel *,int);


/*
      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";
        net_device *dev = NULL;
        mpc_group *grpptr;
        ctc_priv *privptr;


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        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 = (ctc_priv *)dev->priv;
        grpptr = privptr->mpcg;
        if(!grpptr)
                return(1);

        grpptr->allochanfunc = callback;
        grpptr->port_num = port_num;
        grpptr->port_persist = 1;

        printk(KERN_INFO "%s: %s called for device %s refcount=%d state=%s\n", 
               dev->name,
               __FUNCTION__,
               dev->name,
               atomic_read(&dev->refcnt),
               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(((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);

        }


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif
        return(0);
}


void 
ctc_mpc_establish_connectivity(int port_num, callbacktypei3 callback)
{

        char device[20];
        char *devnam = "mpc";
        net_device *dev = NULL;
        mpc_group *grpptr;
        ctc_priv *privptr;
        channel *rch,*wch;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);

#endif

        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 = (ctc_priv *)dev->priv;
        rch = privptr->channel[READ];
        wch = privptr->channel[WRITE];

        grpptr = privptr->mpcg;

        printk(KERN_INFO "%s: %s called for device %s refcount=%d state=%s\n", 
               dev->name,
               __FUNCTION__,
               dev->name,
               atomic_read(&dev->refcnt),
               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 number 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 "ctcmpc: %s() ABORT ACTIVE"
                                       " XID Request - PASSIVE XID already 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 "ctcmpc: %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 "ctcmpc: %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:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return;
}

static int ctcmpc_close(net_device *);

void 
ctc_mpc_dealloc_ch(int port_num)
{
        net_device *dev;
        char device[20];
        char *devnam = "mpc";
        ctc_priv *privptr;
        mpc_group *grpptr;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        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;
        }

        printk(KERN_INFO "%s: %s called for device %s refcount=%d\n", 
               dev->name,__FUNCTION__,dev->name,atomic_read(&dev->refcnt));

        privptr  = (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:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif
        return;
}

void 
ctc_mpc_flow_control(int port_num,int flowc)
{
        char device[20];
        char *devnam = "mpc";
        ctc_priv *privptr;
        mpc_group *grpptr;
        net_device *dev;
        channel *rch = NULL;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %i\n", __FUNCTION__,flowc);
#endif

        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;
        }

#ifdef DEBUG
        printk(KERN_INFO "%s: %s called \n", 
               dev->name,__FUNCTION__);
#endif

        privptr  = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "ctc_mpc_flow_control %s privptr=NULL\n",
                       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;
        }

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %i\n", __FUNCTION__,flowc);
#endif


}

static int ctcmpc_send_qllc_discontact(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 
ctcmpc_action_go_inop(fsm_instance *fi, int event, void *arg)
{
        net_device  *dev = (net_device *)arg;
        ctc_priv    *privptr;
        mpc_group   *grpptr;
        int rc = 0;
        channel *wch,*rch;

        if(dev == NULL)
        {
                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
                return;
        }

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s()\n", dev->name,__FUNCTION__);
#endif

        privptr  = (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 = (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 = ctcmpc_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:
#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()\n", dev->name,__FUNCTION__);
#endif

        return;
}



static void 
ctcmpc_action_timeout(fsm_instance *fi, int event, void *arg)
{
        net_device *dev = (net_device *)arg;
        ctc_priv *privptr;
        mpc_group *grpptr;
        channel *wch;
        channel *rch;

        if(dev == NULL)
        {
                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
                return;
        }

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s()\n", dev->name,__FUNCTION__);
#endif

        privptr = (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);
        }

#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()\n", dev->name,__FUNCTION__);
#endif


        return;
}


static void 
ctcmpc_action_discontact(fsm_instance *fi, int event, void *arg)
{
        mpcg_info   *mpcginfo   = (mpcg_info *)arg;
        channel     *ch         = mpcginfo->ch;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        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, irq=%d\n",__FUNCTION__,
                       ch->irq);
                return;
        }

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s()\n", dev->name,__FUNCTION__);
#endif

        grpptr->send_qllc_disc = 1;
        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);

#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()\n", dev->name,__FUNCTION__);
#endif

        return;
}

static void 
ctcmpc_action_send_discontact(unsigned long thischan)
{
        channel *ch = (channel *)thischan;  
        int rc = 0;
        unsigned long saveflags;
#ifdef DEBUG
	net_device *dev = ch->netdev;
	ctc_priv *privptr = (ctc_priv *)dev->priv;
	mpc_group = *grpptr = privptr->mpcg;
#endif


#ifdef DEBUG

        printk(KERN_INFO "%s cp:%i enter: %s() irq=%d GrpState:%s ChState:%s\n", 
               dev->name,
               smp_processor_id(),
               __FUNCTION__,
               ch->irq,
               fsm_getstate_str(grpptr->fsm),
               fsm_getstate_str(ch->fsm));
#endif

        s390irq_spin_lock_irqsave(ch->irq,saveflags);
        rc = do_IO(ch->irq, &ch->ccw[15], (intparm_t)ch, 0xff, 0);
        s390irq_spin_unlock_irqrestore(ch->irq,saveflags);

        if(rc != 0)
        {
#ifdef DEBUG
                printk(KERN_INFO "%s() %04x do_IO failed \n", 
                       __FUNCTION__,ch->devno);
                ccw_check_return_code(ch, rc);
#endif
                /* Not checking return code value here */
                /* Making best effort to notify partner*/
                /* that MPC Group is going down        */
        }

#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()\n", dev->name,__FUNCTION__);
#endif

        return;
}



static int 
ctcmpc_validate_xid(mpcg_info *mpcginfo)
{
        channel     *ch         = mpcginfo->ch;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        mpc_group   *grpptr     = privptr->mpcg;
        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;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif


        if(mpcginfo->xid == NULL)
        {
                printk(KERN_INFO "%s() xid=NULL, irq=%d\n",__FUNCTION__,
                       ch->irq);
                rc = 1;
                goto done;
        }

#ifdef DEBUGXID
        printk(KERN_INFO "ctcmpc :  %s  xid received()\n", __FUNCTION__);
        dumpit((char *)mpcginfo->xid,XID2_LENGTH);
#endif
        /*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 "%s XID REJECTED - READ-WRITE CH "
                       "Pairing Invalid \n",
                       __FUNCTION__);
        }

        if(xid->xid2_dlc_type == XID2_READ_SIDE)
        {
#ifdef DEBUGDATA
                printk(KERN_INFO "%s(): grpmaxbuf:%d xid2buflen:%d\n", 
                       __FUNCTION__,
                       grpptr->group_max_buflen,
                       xid->xid2_buf_len);
#endif

                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 = (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;
#ifdef DEBUGXID
                        printk(KERN_INFO "ctcmpc :%s() WE HAVE LOW "
                               "ID-TAKE XSIDE\n", __FUNCTION__);
#endif
                } else
                {
                        grpptr->roll = YSIDE;
#ifdef DEBUGXID
                        printk(KERN_INFO "ctcmpc :%s() WE HAVE HIGH "
                               "ID-TAKE YSIDE\n", __FUNCTION__);
#endif
                } 

        } 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)
        {
#ifdef DEBUG
                printk(KERN_INFO "ctcmpc     :  %s() failed\n", __FUNCTION__);
#endif

                privptr->xid->xid2_flag2 = 0x40;
                grpptr->saved_xid2->xid2_flag2 = 0x40;
                rc = 1;
                goto done;
        }

        done:

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return(rc);
}


static void 
ctcmpc_action_yside_xid(fsm_instance *fsm,int event, void *arg) 
{
        channel           *ch         = (channel *)arg;
        ctc_priv          *privptr;
        mpc_group         *grpptr     = NULL;
        int               rc          = 0;
        unsigned long     saveflags;
        int               gotlock     = 0;


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc cp:%i enter:  %s()  %04x\n", 
               smp_processor_id(),__FUNCTION__,ch->devno);
#endif

        if(ch == NULL)
        {
                printk(KERN_INFO "%s ch=NULL\n",__FUNCTION__);
                goto done;
        }

        net_device *dev = ch->netdev;
        if(dev == NULL)
        {
                printk(KERN_INFO "%s dev=NULL, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                goto done;
        }

        privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "%s privptr=NULL, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                goto done;
        }

        grpptr = privptr->mpcg;
        if(grpptr == NULL)
        {
                printk(KERN_INFO "%s grpptr=NULL, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                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 =  (th_header *)ch->trans_skb->data;
        skb_put(ch->trans_skb,TH_HEADER_LENGTH);
        ch->rcvd_xid = (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, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,
                       ch->irq);
                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 DEBUGCCW
        dumpit((char *)&ch->ccw[8],sizeof(ccw1_t) * 7);
#endif
#ifdef DEBUGXID
        dumpit((char *)ch->xid_th,TH_HEADER_LENGTH);
        dumpit((char *)ch->xid,XID2_LENGTH);
        dumpit((char *)ch->xid_id,4);
#endif


        if(!in_irq())
        {
                s390irq_spin_lock_irqsave(ch->irq,saveflags);
                gotlock = 1;
        }

        fsm_addtimer(&ch->timer, 5000 , CH_EVENT_TIMER, ch);
        rc = do_IO(ch->irq, &ch->ccw[8], (intparm_t)ch, 0xff, 0);

        if(gotlock)
                s390irq_spin_unlock_irqrestore(ch->irq,saveflags);

        if(rc != 0)
        {
#ifdef DEBUG
                printk(KERN_INFO "ctcmpc: %s() %04x do_IO failed \n", 
                       __FUNCTION__,ch->devno);
#endif
                ccw_check_return_code(ch, rc);
                goto done;
        }

        done:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()  %04x\n", 
               __FUNCTION__, ch->devno);
#endif

        return;

}       

static void 
ctcmpc_action_doxid0(fsm_instance *fsm,int event, void *arg)    
{
        channel     *ch         = (channel *)arg;
        ctc_priv    *privptr;
        mpc_group   *grpptr     = NULL;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()  %04x\n", 
               __FUNCTION__, ch->devno);
#endif

        if(ch == NULL)
        {
                printk(KERN_WARNING "%s ch=NULL\n",__FUNCTION__);
                goto done;
        }

        net_device *dev = ch->netdev;
        if(dev == NULL)
        {
                printk(KERN_WARNING "%s dev=NULL, irq=%d\n",
                       __FUNCTION__, ch->irq);
                goto done;
        }

        privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_WARNING "%s privptr=NULL, irq=%d\n",
                       __FUNCTION__, ch->irq);
                goto done;
        }

        grpptr = privptr->mpcg;
        if(grpptr == NULL)
        {
                printk(KERN_WARNING "%s grpptr=NULL, irq=%d\n",
                       __FUNCTION__, ch->irq);
                goto done;
        }


        if(ch->xid == NULL)
        {
                printk(KERN_WARNING "%s ch-xid=NULL, irq=%d\n",
                       __FUNCTION__,ch->irq);
                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
        printk(KERN_INFO "ctcmpc exit:  %s()  %04x\n", 
               __FUNCTION__,ch->devno);
#endif

        return;

}       



static void 
ctcmpc_action_doxid7(fsm_instance *fsm,int event, void *arg)
{
        net_device  *dev        = (net_device *)arg;
        int         direction;
        int         rc          = 0;
        int         send        = 0;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() \n", __FUNCTION__);
#endif

        if(dev == NULL)
        {
                printk(KERN_INFO "%s dev=NULL \n",__FUNCTION__);
                rc = 1;
                goto done;
        }

        ctc_priv   *privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "%s privptr=NULL \n",__FUNCTION__);
                rc = 1;
                goto done;
        }

        mpc_group  *grpptr = privptr->mpcg;
        if(grpptr == NULL)
        {
                printk(KERN_INFO "%s grpptr=NULL \n",__FUNCTION__);
                rc = 1;
                goto done;
        }

        for(direction = READ; direction <= WRITE; direction++)
        {
                channel *ch = privptr->channel[direction];
                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);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return;
}



static void 
ctcmpc_action_xside_xid(fsm_instance *fsm,int event, void *arg)
{
        channel           *ch         = (channel *)arg;
        ctc_priv          *privptr;
        mpc_group         *grpptr     = 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
        printk(KERN_INFO "ctcmpc cp:%i enter:  %s()  %04x\n", 
               smp_processor_id(),__FUNCTION__,ch->devno);
#endif


        net_device *dev = ch->netdev;
        if(dev == NULL)
        {
                printk(KERN_INFO "%s dev=NULL, irq=%d\n",__FUNCTION__,ch->irq);
                goto done;
        }

        privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "%s privptr=NULL, irq=%d\n",
                       __FUNCTION__,ch->irq);
                goto done;
        }

        grpptr = privptr->mpcg;
        if(grpptr == NULL)
        {
                printk(KERN_INFO "%s grpptr=NULL, irq=%d\n",
                       __FUNCTION__,ch->irq);
                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 =  (th_header *)ch->trans_skb->data;
        skb_put(ch->trans_skb,TH_HEADER_LENGTH);
        ch->rcvd_xid = (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, irq=%d\n",
                       __FUNCTION__,ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,ch->irq);
                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, irq=%d\n",
                       __FUNCTION__,ch->irq);
                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(ccw1_t) * 7);
#endif
#ifdef DEBUGXID
        dumpit((char *)ch->xid_th,TH_HEADER_LENGTH);
        dumpit((char *)ch->xid,XID2_LENGTH);
#endif

        if(!in_irq())
        {
                s390irq_spin_lock_irqsave(ch->irq,saveflags);
                gotlock = 1;
        }

        fsm_addtimer(&ch->timer, 5000 , CH_EVENT_TIMER, ch);
        rc = do_IO(ch->irq, &ch->ccw[8], (intparm_t)ch, 0xff, 0);

        if(gotlock)
                s390irq_spin_unlock_irqrestore(ch->irq,saveflags);

        if(rc != 0)
        {

#ifdef DEBUG
                printk(KERN_INFO "ctcmpc: %s() %04x do_IO failed \n", 
                       __FUNCTION__,ch->devno);
#endif
                ccw_check_return_code(ch, rc);
                goto done;
        }

        done:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

        return;
}



static void 
ctcmpc_action_rcvd_xid0(fsm_instance *fsm,int event, void *arg)
{

        mpcg_info   *mpcginfo   = (mpcg_info *)arg;
        channel     *ch         = mpcginfo->ch;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr;
        mpc_group   *grpptr;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

        privptr = (ctc_priv *)dev->priv;
        grpptr = privptr->mpcg;

#ifdef DEBUGXID
        printk(KERN_INFO "ctcmpc in:%s() %04x xid2:%i xid7:%i xidt_p2:%i \n", 
               __FUNCTION__,ch->devno,
               grpptr->outstanding_xid2,
               grpptr->outstanding_xid7,
               grpptr->outstanding_xid7_p2); 
#endif

        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);
                        ctcmpc_validate_xid(mpcginfo);
                        break;
                case MPCG_STATE_XID0IOWAIT:
                        fsm_newstate(grpptr->fsm,MPCG_STATE_XID0IOWAIX);
                        ctcmpc_validate_xid(mpcginfo);
                        break;
                case MPCG_STATE_XID2INITX:       
                        if(grpptr->outstanding_xid2 == 0)
                        {
                                fsm_newstate(grpptr->fsm,MPCG_STATE_XID7INITW);
                                ctcmpc_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);
                                ctcmpc_validate_xid(mpcginfo);
                                fsm_event(grpptr->fsm,MPCG_EVENT_XID2DONE,dev);
                        }
                        break;
        }
        kfree(mpcginfo);

#ifdef DEBUGXID
        printk(KERN_INFO "ctcmpc out:%s() %04x xid2:%i xid7:%i xidt_p2:%i \n", 
               __FUNCTION__,ch->devno,
               grpptr->outstanding_xid2,
               grpptr->outstanding_xid7,
               grpptr->outstanding_xid7_p2); 
        printk(KERN_INFO "ctcmpc out:%s() %04x groupstate: %s chanstate: %s \n", 
               __FUNCTION__,ch->devno,
               fsm_getstate_str(grpptr->fsm), 
               fsm_getstate_str(ch->fsm)); 
#endif

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", __FUNCTION__,ch->devno);
#endif

        return;

}


static void 
ctcmpc_action_rcvd_xid7(fsm_instance *fsm,int event, void *arg)
{

        mpcg_info   *mpcginfo   = (mpcg_info *)arg;
        channel     *ch         = mpcginfo->ch;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        mpc_group   *grpptr     = privptr->mpcg;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

#ifdef DEBUGXID
        printk(KERN_INFO "ctcmpc:  outstanding_xid7: %i, "
               "outstanding_xid7_p2: %i\n",
               grpptr->outstanding_xid7,
               grpptr->outstanding_xid7_p2);
#endif


        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);
                        ctcmpc_validate_xid(mpcginfo);
                        break;
                case MPCG_STATE_XID7INITW:
                        fsm_newstate(grpptr->fsm,MPCG_STATE_XID7INITX);
                        ctcmpc_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);
                                ctcmpc_validate_xid(mpcginfo);
                                fsm_event(grpptr->fsm,MPCG_EVENT_XID7DONE,dev);
                                break;
                        }
                        ctcmpc_validate_xid(mpcginfo);
                        break;
        }

        kfree(mpcginfo);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif
        return;

}

static void 
ctcmpc_action_attn(fsm_instance *fsm,int event, void *arg)
{
        channel     *ch         = (channel *)arg;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        mpc_group   *grpptr     = privptr->mpcg;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x  \nGrpState:%s ChState:%s\n", 
               __FUNCTION__,ch->devno,
               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
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", __FUNCTION__,ch->devno);
#endif
        return;

}

static void 
ctcmpc_action_attnbusy(fsm_instance *fsm,int event, void *arg)
{
        channel     *ch         = (channel *)arg;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        mpc_group   *grpptr     = privptr->mpcg;

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s() %04x  \nGrpState:%s ChState:%s\n", 
               dev->name,
               __FUNCTION__,ch->devno,
               fsm_getstate_str(grpptr->fsm),
               fsm_getstate_str(ch->fsm));
#endif


        fsm_deltimer(&ch->timer);

        switch(fsm_getstate(grpptr->fsm))
        {
                case MPCG_STATE_XID0IOWAIT:
                        /* vtam wants to be primary..start yside xid exchanges*/
                        /* will only rcv  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
        printk(KERN_INFO "%s exit:  %s() %04x\n", 
               dev->name,__FUNCTION__,ch->devno);
#endif
        return;

}

static void 
ctcmpc_action_resend(fsm_instance *fsm,int event, void *arg)
{
        channel     *ch         = (channel *)arg;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        mpc_group   *grpptr     = privptr->mpcg;

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s() %04x  \nGrpState:%s ChState:%s\n", 
               dev->name,__FUNCTION__,ch->devno,
               fsm_getstate_str(grpptr->fsm),
               fsm_getstate_str(ch->fsm));
#endif
        fsm_event(grpptr->fsm, MPCG_EVENT_XID0DO, ch);

#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s() %04x\n", 
               dev->name,__FUNCTION__,ch->devno);
#endif
        return;

}


static int 
ctcmpc_send_qllc_discontact(net_device *dev)
{
        int               rc = 0, space = 0;
        __u32         new_len = 0;
        struct sk_buff    *skb;
        qllc              *qllcptr;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n",
               __FUNCTION__);
#endif

        if(dev == NULL)
        {
                printk(KERN_INFO "%s() dev=NULL\n",
                       __FUNCTION__);
                rc = 1;
                goto done;
        }

        ctc_priv *privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "%s() privptr=NULL\n",
                       __FUNCTION__);
                rc = 1;
                goto done;
        }

        mpc_group       *grpptr = privptr->mpcg;
        if(grpptr == NULL)
        {
                printk(KERN_INFO "%s() grpptr=NULL\n",
                       __FUNCTION__);
                rc = 1;
                goto done;
        }
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc: %s() GROUP STATE: %s\n", 
               __FUNCTION__,mpcg_state_names[grpptr->saved_state]);
#endif


        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(qllc);
                        if((qllcptr = (qllc *)kmalloc(sizeof(qllc), 
                                                      gfp_type() | GFP_DMA)) 
                           == NULL)
                        {
                                printk(KERN_INFO "qllc: Out of memory"
                                       " in send_qllc\n");
                                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 ctcmpc_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 "%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 DEBUGSEQ
                        printk(KERN_INFO "%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;

#ifdef DEBUGDATA
                        dumpit((char *)skb->data,(sizeof(qllc)+4));
#endif
                        netif_rx(skb);
                        break;
                default: break;

        }

        done:

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return(rc);

}

static void 
ctcmpc_send_sweep(fsm_instance *fsm,int event, void *arg)
{
        channel           *ach        = (channel *)arg;
        net_device        *dev        = ach->netdev;
        ctc_priv          *privptr    = (ctc_priv *)dev->priv;
        mpc_group         *grpptr     = privptr->mpcg;
        int               rc          = 0;
        sk_buff           *skb;
        unsigned long     saveflags;
        channel           *wch        = privptr->channel[WRITE];        
        channel           *rch        = privptr->channel[READ];  


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc cp:%i enter:  %s() %04x\n", 
               smp_processor_id(),__FUNCTION__,ach->devno);
#endif

        if(grpptr->in_sweep == 0)
                goto done;

#ifdef DEBUGSEQ
        printk(KERN_INFO "%s 1: ToVTAM_th_seq= %08x\n" ,
               __FUNCTION__,wch->th_seq_num);
        printk(KERN_INFO "%s 1: FromVTAM_th_seq= %08x\n" ,
               __FUNCTION__,rch->th_seq_num);
#endif


        if(fsm_getstate(wch->fsm) != CH_STATE_TXIDLE)
        {
                /* give the previous do_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 (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,21))
        if(set_normalized_cda(&wch->ccw[4], virt_to_phys(skb->data)))
        {
#else
        if(set_normalized_cda(&wch->ccw[4], skb->data))
        {
#endif
                grpptr->in_sweep = 0;
                ctcmpc_clear_busy(dev);
                fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);     
                goto done;
        } else
                skb_queue_tail(&wch->io_queue, skb);

        /* send out the sweep */
        wch->ccw[4].count = skb->len;

        th_sweep *header = (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(ccw1_t) * 3);
#endif

#ifdef DEBUGSEQ
        printk(KERN_INFO "%s(): sweep packet\n", __FUNCTION__);
        dumpit((char *)header,TH_SWEEP_LENGTH);
#endif


        fsm_addtimer(&wch->timer,CTC_TIMEOUT_5SEC,CH_EVENT_TIMER, wch);
        fsm_newstate(wch->fsm, CH_STATE_TX);

        s390irq_spin_lock_irqsave(wch->irq, saveflags);
        wch->prof.send_stamp = xtime;
        rc = do_IO(wch->irq, &wch->ccw[3], (intparm_t)wch, 0xff, 0);
        s390irq_spin_unlock_irqrestore(wch->irq, 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 DEBUGSEQ
        printk(KERN_INFO "%s 2: ToVTAM_th_seq= %08x\n" ,
               __FUNCTION__,wch->th_seq_num);
        printk(KERN_INFO "%s 2: FromVTAM_th_seq= %08x\n" ,
               __FUNCTION__,rch->th_seq_num);
#endif

        if(rc != 0)
                ccw_check_return_code(wch, rc);

        done:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", __FUNCTION__,ach->devno);
#endif
        return;

}


static void 
ctcmpc_rcvd_sweep_resp(mpcg_info *mpcginfo)
{
        channel           *rch        = mpcginfo->ch;
        net_device        *dev        = rch->netdev;
        ctc_priv          *privptr    = (ctc_priv *)dev->priv;
        mpc_group         *grpptr     = privptr->mpcg;
        channel           *ch         = privptr->channel[WRITE];        

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

#ifdef DEBUGSEQ
        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
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

        return;

}


static void 
ctcmpc_send_sweep_req(channel *rch)
{
        net_device        *dev        = rch->netdev;
        ctc_priv          *privptr    = (ctc_priv *)dev->priv;
        mpc_group         *grpptr     = privptr->mpcg;
        th_sweep          *header;
        struct sk_buff    *sweep_skb;
        int               rc          = 0;
        channel           *ch         = privptr->channel[WRITE];        


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#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 = (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
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

        return;
}

static void 
ctcmpc_send_sweep_resp(channel *rch)
{
        net_device        *dev        = rch->netdev;
        ctc_priv          *privptr    = (ctc_priv *)dev->priv;
        mpc_group         *grpptr     = privptr->mpcg;
        int               rc          = 0;
        th_sweep          *header;
        struct sk_buff    *sweep_skb;
        channel           *ch         = privptr->channel[WRITE];


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", 
               __FUNCTION__,rch->devno);
#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 = (th_sweep *)kmalloc(sizeof(struct th_sweep_t), 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
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

        return;

}

static void 
ctcmpc_rcvd_sweep_req(mpcg_info *mpcginfo)
{
        channel     *rch        = mpcginfo->ch;
        net_device  *dev        = rch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        mpc_group   *grpptr     = privptr->mpcg;
        channel     *ch         = privptr->channel[WRITE];      


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#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 DEBUGSEQ
        dumpit((char *)mpcginfo->sweep,TH_SWEEP_LENGTH);
#endif

        grpptr->sweep_req_pend_num --;

        ctcmpc_send_sweep_resp(ch);

        kfree(mpcginfo);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif
        return;
}

static void 
ctcmpc_action_go_ready(fsm_instance *fsm,int event, void *arg)
{
        net_device     *dev = (net_device *)arg;

        if(dev == NULL)
        {
                printk(KERN_INFO "%s() dev=NULL\n",
                       __FUNCTION__);
                return;
        }

#ifdef DEBUG
        printk(KERN_INFO "%s enter:  %s()\n", 
               dev->name,__FUNCTION__);
#endif

        ctc_priv *privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "%s() privptr=NULL\n",
                       __FUNCTION__);
                return;
        }

        mpc_group       *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);

#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()\n", dev->name,__FUNCTION__);
#endif
        return;

        done:
        fsm_event(grpptr->fsm,MPCG_EVENT_INOP,dev);


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit: failure occurred: %s()\n", 
               __FUNCTION__);
#endif

}


static void 
ctcmpc_group_ready(unsigned long adev)
{
        net_device *dev = (net_device *)adev;


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        if(dev == NULL)
        {
                printk(KERN_INFO "%s() dev=NULL\n",__FUNCTION__);
                return;
        }

        ctc_priv *privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "%s() privptr=NULL\n",__FUNCTION__);
                return;
        }

        mpc_group       *grpptr = privptr->mpcg;
        if(grpptr == NULL)
        {
                printk(KERN_INFO "%s() grpptr=NULL\n",__FUNCTION__);
                return;
        }

        printk(KERN_NOTICE "%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 */
        channel *ch = privptr->channel[READ];
        ch->pdu_seq = 0;
#ifdef DEBUGSEQ
        printk(KERN_INFO "%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;



#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return;

}

/****************************************************************/
/* Increment the MPC Group Active Channel Counts                */
/****************************************************************/
static int 
ctcmpc_channel_action(channel *ch, int direction, int action)
{
        net_device  *dev     = ch->netdev;
        ctc_priv    *privptr;
        mpc_group   *grpptr  = NULL;
        int         rc = 0;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif


        if(dev == NULL)
        {
                printk(KERN_INFO "mpc_channel_action %i dev=NULL, irq=%d\n",
                       action,ch->irq);
                rc = 1;
                goto done;
        }

        privptr = (ctc_priv *)dev->priv;
        if(privptr == NULL)
        {
                printk(KERN_INFO "mpc_channel_action%i privptr=NULL, dev=%s\n",
                       action,dev->name);
                rc = 2;
                goto done;
        }

        grpptr = privptr->mpcg;

        if(grpptr == NULL)
        {
                printk(KERN_INFO "mpc_channel_action%i mpcgroup=NULL, dev=%s\n",
                       action,dev->name);
                rc = 3;
                goto done;
        }
#ifdef DEBUG
        printk(KERN_INFO
               "ctcmpc enter : %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]);
#endif

        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 
                                               "Couldn't alloc ch xid_skb\n");
                                        fsm_event(grpptr->fsm,
                                                  MPCG_EVENT_INOP,dev);
                                        return 1;
                                }

                                ch->xid_skb_data = ch->xid_skb->data;
                                ch->xid_th = (th_header *)ch->xid_skb->data;
                                skb_put(ch->xid_skb,TH_HEADER_LENGTH);
                                ch->xid = (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 
                                               "%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
        printk(KERN_INFO
               "ctcmpc leave: %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]);
#endif

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", 
               __FUNCTION__,ch->devno);
#endif

        return(rc);

}

/**
 * The minimum required active read or write channels 
 * are no longer available.  Game over.
 */

/**
 * The MPC Group Station FSM
 *   22 events
 */
static const fsm_node mpcg_fsm[] = {
        {  MPCG_STATE_RESET,     MPCG_EVENT_INOP,     ctcmpc_action_go_inop},
        {  MPCG_STATE_INOP,      MPCG_EVENT_INOP,     fsm_action_nop},

        {  MPCG_STATE_FLOWC,     MPCG_EVENT_INOP,     ctcmpc_action_go_inop},

        {  MPCG_STATE_READY,     MPCG_EVENT_DISCONC,  ctcmpc_action_discontact},
        {  MPCG_STATE_READY,     MPCG_EVENT_INOP,     ctcmpc_action_go_inop},

        {  MPCG_STATE_XID2INITW, MPCG_EVENT_XID0DO,   ctcmpc_action_doxid0},
        {  MPCG_STATE_XID2INITW, MPCG_EVENT_XID2,     ctcmpc_action_rcvd_xid0},
        {  MPCG_STATE_XID2INITW, MPCG_EVENT_INOP,     ctcmpc_action_go_inop},
        {  MPCG_STATE_XID2INITW, MPCG_EVENT_TIMER,    ctcmpc_action_timeout},
        {  MPCG_STATE_XID2INITW, MPCG_EVENT_DOIO,     ctcmpc_action_yside_xid},

        {  MPCG_STATE_XID2INITX, MPCG_EVENT_XID0DO,   ctcmpc_action_doxid0},
        {  MPCG_STATE_XID2INITX, MPCG_EVENT_XID2,     ctcmpc_action_rcvd_xid0},
        {  MPCG_STATE_XID2INITX, MPCG_EVENT_INOP,     ctcmpc_action_go_inop},
        {  MPCG_STATE_XID2INITX, MPCG_EVENT_TIMER,    ctcmpc_action_timeout},
        {  MPCG_STATE_XID2INITX, MPCG_EVENT_DOIO,     ctcmpc_action_yside_xid},

        {  MPCG_STATE_XID7INITW, MPCG_EVENT_XID2DONE, ctcmpc_action_doxid7},
        {  MPCG_STATE_XID7INITW, MPCG_EVENT_DISCONC,  ctcmpc_action_discontact},
        {  MPCG_STATE_XID7INITW, MPCG_EVENT_XID2,     ctcmpc_action_rcvd_xid7},
        {  MPCG_STATE_XID7INITW, MPCG_EVENT_INOP,     ctcmpc_action_go_inop},
        {  MPCG_STATE_XID7INITW, MPCG_EVENT_TIMER,    ctcmpc_action_timeout},
        {  MPCG_STATE_XID7INITW, MPCG_EVENT_XID7DONE, ctcmpc_action_doxid7},
        {  MPCG_STATE_XID7INITW, MPCG_EVENT_DOIO,     ctcmpc_action_yside_xid},

        {  MPCG_STATE_XID7INITX, MPCG_EVENT_DISCONC,  ctcmpc_action_discontact},
        {  MPCG_STATE_XID7INITX, MPCG_EVENT_XID2,     ctcmpc_action_rcvd_xid7},
        {  MPCG_STATE_XID7INITX, MPCG_EVENT_INOP,     ctcmpc_action_go_inop},
        {  MPCG_STATE_XID7INITX, MPCG_EVENT_XID7DONE, ctcmpc_action_doxid7},
        {  MPCG_STATE_XID7INITX, MPCG_EVENT_TIMER,    ctcmpc_action_timeout},
        {  MPCG_STATE_XID7INITX, MPCG_EVENT_DOIO,     ctcmpc_action_yside_xid},

        {  MPCG_STATE_XID0IOWAIT, MPCG_EVENT_XID0DO,  ctcmpc_action_doxid0},
        {  MPCG_STATE_XID0IOWAIT, MPCG_EVENT_DISCONC, ctcmpc_action_discontact},
        {  MPCG_STATE_XID0IOWAIT, MPCG_EVENT_XID2,    ctcmpc_action_rcvd_xid0},
        {  MPCG_STATE_XID0IOWAIT, MPCG_EVENT_INOP,    ctcmpc_action_go_inop},
        {  MPCG_STATE_XID0IOWAIT, MPCG_EVENT_TIMER,   ctcmpc_action_timeout},
        {  MPCG_STATE_XID0IOWAIT, MPCG_EVENT_DOIO,    ctcmpc_action_xside_xid},

        {  MPCG_STATE_XID0IOWAIX, MPCG_EVENT_XID0DO,  ctcmpc_action_doxid0},
        {  MPCG_STATE_XID0IOWAIX, MPCG_EVENT_DISCONC, ctcmpc_action_discontact},
        {  MPCG_STATE_XID0IOWAIX, MPCG_EVENT_XID2,    ctcmpc_action_rcvd_xid0},
        {  MPCG_STATE_XID0IOWAIX, MPCG_EVENT_INOP,    ctcmpc_action_go_inop},
        {  MPCG_STATE_XID0IOWAIX, MPCG_EVENT_TIMER,   ctcmpc_action_timeout},
        {  MPCG_STATE_XID0IOWAIX, MPCG_EVENT_DOIO,    ctcmpc_action_xside_xid},

        {  MPCG_STATE_XID7INITI,  MPCG_EVENT_XID2DONE,ctcmpc_action_doxid7},
        {  MPCG_STATE_XID7INITI,  MPCG_EVENT_XID2,    ctcmpc_action_rcvd_xid7},
        {  MPCG_STATE_XID7INITI,  MPCG_EVENT_DISCONC, ctcmpc_action_discontact},
        {  MPCG_STATE_XID7INITI,  MPCG_EVENT_INOP,    ctcmpc_action_go_inop},
        {  MPCG_STATE_XID7INITI,  MPCG_EVENT_TIMER,   ctcmpc_action_timeout},
        {  MPCG_STATE_XID7INITI,  MPCG_EVENT_XID7DONE,ctcmpc_action_doxid7},
        {  MPCG_STATE_XID7INITI,  MPCG_EVENT_DOIO,    ctcmpc_action_xside_xid},

        {  MPCG_STATE_XID7INITZ,  MPCG_EVENT_XID2,    ctcmpc_action_rcvd_xid7},
        {  MPCG_STATE_XID7INITZ,  MPCG_EVENT_XID7DONE,ctcmpc_action_doxid7},
        {  MPCG_STATE_XID7INITZ,  MPCG_EVENT_DISCONC, ctcmpc_action_discontact},
        {  MPCG_STATE_XID7INITZ,  MPCG_EVENT_INOP,    ctcmpc_action_go_inop},
        {  MPCG_STATE_XID7INITZ,  MPCG_EVENT_TIMER,   ctcmpc_action_timeout},
        {  MPCG_STATE_XID7INITZ,  MPCG_EVENT_DOIO,    ctcmpc_action_xside_xid},

        {  MPCG_STATE_XID7INITF,  MPCG_EVENT_INOP,    ctcmpc_action_go_inop},
        {  MPCG_STATE_XID7INITF,  MPCG_EVENT_XID7DONE,ctcmpc_action_go_ready},

};

static const int MPCG_FSM_LEN = sizeof(mpcg_fsm) / sizeof(fsm_node);


/**
 * 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 packed skb.
 */
static __inline__ void 
ctcmpc_unpack_skb(channel *ch, struct sk_buff *pskb)
{
        net_device *dev  = ch->netdev;
        ctc_priv *privptr = (ctc_priv *)dev->priv;
        mpc_group *grpptr = privptr->mpcg;
        pdu *curr_pdu;
        mpcg_info *mpcginfo;
        int pdu_last_seen = 0;
        __u32 new_len;
        struct sk_buff *skb;
        int sendrc = 0;

#ifdef DEBUG
        printk(KERN_INFO "%s cp:%i enter:%s() %04x\n", 
               dev->name,smp_processor_id(),__FUNCTION__,ch->devno);
#endif

        th_header *header = (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
        printk(KERN_INFO "%s(): th_header\n", __FUNCTION__);
        dumpit((char *)header,TH_HEADER_LENGTH);
        printk(KERN_INFO "%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
//                        printk(KERN_INFO "%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 DEBUGSEQ
                        printk(KERN_INFO "%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 DEBUGSEQ
                printk(KERN_INFO "%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 = (pdu *)pskb->data;
#ifdef DEBUGDATA
                                printk(KERN_INFO "%s(): pdu_header\n", 
                                       __FUNCTION__);
                                dumpit((char *)pskb->data,PDU_HEADER_LENGTH);
                                printk(KERN_INFO "%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
//				printk(KERN_INFO "%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 
                                               "%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
                                               "%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 DEBUGSEQ
                                printk(KERN_INFO "%s: ToDCM_pdu_seq= %08x\n" ,
                                       __FUNCTION__,ch->pdu_seq);
#endif

#ifdef DEBUGDATA
                                __u32 out_len;
                                if(skb->len > 32) out_len = 32;
                                else out_len = skb->len;
                                printk(KERN_INFO "%s(): skb:%0lx skb len:%d \n",
                                        __FUNCTION__,
                                       (unsigned long)skb,
                                       skb->len);
                                printk(KERN_INFO "%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 = (mpcg_info *)kmalloc(sizeof(mpcg_info), 
                                                    gfp_type())) == NULL)
                {
                        spin_unlock(&ch->segment_lock);
                        goto done;
                }

                mpcginfo->ch = ch;
                mpcginfo->th = header;
                mpcginfo->skb = pskb;

#ifdef DEBUG
                printk(KERN_INFO "%s(): It's not PDU it may be control pkt\n", 
                       __FUNCTION__);
#endif
                /*  it's a sweep?   */
                th_sweep *sweep = (th_sweep *) pskb->data;
                mpcginfo->sweep = sweep;
                if(header->th_ch_flag == TH_SWEEP_REQ)
                        ctcmpc_rcvd_sweep_req(mpcginfo);
                else
                        if(header->th_ch_flag == TH_SWEEP_RESP)
                        ctcmpc_rcvd_sweep_resp(mpcginfo);
                else
                {
                        if(header->th_blk_flag == TH_DATA_IS_XID)
                        {
                                xid2 *thisxid = (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++;
#ifdef DEBUGDATA
                                        ctcmpc_dump_skb(pskb, -8);
#endif
                                        kfree(mpcginfo);
                                }
                        }
                }
        }
        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
        printk(KERN_INFO "%s exit:  %s() %04x\n", 
               dev->name,__FUNCTION__,ch->devno);
#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)
{
        channel           *ch         = (channel *)thischan;
        struct sk_buff    *peek_skb   = NULL;
        struct sk_buff    *skb;
        struct sk_buff    *same_skb   = NULL;
        net_device        *dev        = ch->netdev;
        ctc_priv          *privptr    = (ctc_priv *)dev->priv;
        mpc_group         *grpptr     = privptr->mpcg;

#ifdef DEBUG
        printk(KERN_INFO "%s cp:%i enter:  %s()  %04x\n", 
               dev->name,smp_processor_id(),__FUNCTION__,ch->devno);
#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
        printk(KERN_INFO "%s exit:  %s()  %04x\n", 
               dev->name,__FUNCTION__,ch->devno);
#endif

        return;
}

/**
 * Check return code of a preceeding do_IO, 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 (channel *ch, int return_code)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter: cp:%i %s()\n", 
               smp_processor_id(),__FUNCTION__);
#endif


        switch(return_code)
        {
                case 0:
                        fsm_event(ch->fsm, CH_EVENT_IO_SUCCESS, ch);
                        break;
                case -EBUSY:
                        printk(KERN_INFO "ch-%04x: Busy !\n", ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_IO_EBUSY, ch);
                        break;
                case -ENODEV:
                        printk(KERN_EMERG
                               "ch-%04x: Invalid device called for IO\n",
                               ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_IO_ENODEV, ch);
                        break;
                case -EIO:
                        printk(KERN_EMERG
                               "ch-%04x: Status pending... \n", ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_IO_EIO, ch);
                        break;
                default:
                        printk(KERN_EMERG
                               "ch-%04x: Unknown error in do_IO %04x\n",
                               ch->devno, return_code);
                        fsm_event(ch->fsm, CH_EVENT_IO_UNKNOWN, ch);
        }


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif


}

/**
 * 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 (channel *ch, unsigned char sense)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        if(sense & SNS0_INTERVENTION_REQ)
        {
                if(sense & 0x01)
                {
                        printk(KERN_INFO
                               "ch-%04x: Interface disc. or Sel. reset "
                               "(remote)\n", ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_UC_RCRESET, ch);
                } else
                {
                        printk(KERN_INFO "ch-%04x: System reset (remote)\n",
                               ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_UC_RSRESET, ch);
                }
        } else if(sense & SNS0_EQUIPMENT_CHECK)
        {
                if(sense & SNS0_BUS_OUT_CHECK)
                {
                        printk(KERN_INFO
                               "ch-%04x: Hardware malfunction (remote)\n",
                               ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_UC_HWFAIL, ch);
                } else
                {
                        printk(KERN_INFO
                               "ch-%04x: Read-data parity error (remote)\n",
                               ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_UC_RXPARITY, ch);
                }
        } else if(sense & SNS0_BUS_OUT_CHECK)
        {
                if(sense & 0x04)
                {
                        printk(KERN_INFO
                               "ch-%04x: Data-streaming timeout)\n",
                               ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_UC_TXTIMEOUT, ch);
                } else
                {
                        printk(KERN_INFO
                               "ch-%04x: Data-transfer parity error\n",
                               ch->devno);
                        fsm_event(ch->fsm, CH_EVENT_UC_TXPARITY, ch);
                }
        } else if(sense & SNS0_CMD_REJECT)
        {
                printk(KERN_INFO "ch-%04x: Command reject\n",
                       ch->devno);
        } else if(sense == 0)
        {
                printk(KERN_INFO "ch-%04x: Unit check ZERO\n", ch->devno);
                fsm_event(ch->fsm, CH_EVENT_UC_ZERO, ch);
        } else
        {
                printk(KERN_INFO
                       "ch-%04x: Unit Check with sense code: %02x\n",
                       ch->devno, sense);
                fsm_event(ch->fsm, CH_EVENT_UC_UNKNOWN, ch);
        }


#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif
}

static void 
ctcmpc_purge_skb_queue(struct sk_buff_head *q)
{
        struct sk_buff *skb;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        while((skb = skb_dequeue(q)))
        {
                atomic_dec(&skb->users);

                dev_kfree_skb_any(skb);
        }

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

}

static __inline__ int 
ctcmpc_checkalloc_buffer(channel *ch, int warn)
{

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        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)
                                printk(KERN_INFO
                                       "ch-%04x: Couldn't alloc %s trans_skb\n",
                                       ch->devno,
                                       (CHANNEL_DIRECTION(ch->flags) == READ) ?
                                       "RX" : "TX");
                        return -ENOMEM;
                }
                ch->ccw[1].count = ch->max_bufsize;

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,21))
                if(set_normalized_cda(&ch->ccw[1],
                                      virt_to_phys(ch->trans_skb->data)))
                {
#else 
                if(set_normalized_cda(&ch->ccw[1],ch->trans_skb->data))
                {
#endif
                        dev_kfree_skb_any(ch->trans_skb);
                        ch->trans_skb = NULL;
                        if(warn)
                                printk(KERN_INFO
                                       "ch-%04x: set_normalized_cda for %s "
                                       "trans_skb failed, dropping packets\n",
                                       ch->devno,
                                       (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;
        }
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return 0;
}

/**
 * Actions for channel - statemachines.
 *****************************************************************************/

/**
 * Normal data has been sent. 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)
{
        channel        *ch = (channel *)arg;
        net_device     *dev = ch->netdev;
        ctc_priv       *privptr = (ctc_priv *)dev->priv;
        mpc_group      *grpptr  = privptr->mpcg;
        struct sk_buff *skb;
        int            first = 1;
        int            i;
#ifdef DEBUGDATA
        __u32                out_len = 0;
#endif


#ifdef DEBUG
        printk(KERN_INFO "%s cp:%i enter:  %s()\n", 
               dev->name,smp_processor_id(),__FUNCTION__);
#endif


        struct timeval done_stamp = xtime;
        unsigned long duration = 
        (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 +
        done_stamp.tv_usec - ch->prof.send_stamp.tv_usec;
        if(duration > ch->prof.tx_time)
                ch->prof.tx_time = duration;

        if(ch->devstat->rescnt != 0)
                printk(KERN_INFO "%s: TX not complete, remaining %d bytes\n",
                       dev->name, ch->devstat->rescnt);

        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;
                th_header       *header;
                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
                printk(KERN_INFO "%s(): building trans_skb from collect_q \n", 
                       __FUNCTION__);
#endif

                __u32 data_space = grpptr->group_max_buflen - TH_HEADER_LENGTH;

#ifdef DEBUGDATA
                printk(KERN_INFO "%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 = (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;
                        printk(KERN_INFO "%s():trans_skb len:%04x \n", 
                               __FUNCTION__,ch->trans_skb->len);
                        if(skb->len > 32) out_len = 32;
                        else out_len = skb->len;
                        printk(KERN_INFO "%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);
                        sk_buff *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; 
                header = (th_header *)kmalloc(TH_HEADER_LENGTH, gfp_type());

                if(!header)
                {
                        printk(KERN_WARNING ": 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 DEBUGSEQ
                printk(KERN_INFO "%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
                printk(KERN_INFO "%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;
                printk(KERN_INFO "%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 (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,21))
                if(set_normalized_cda(&ch->ccw[1],
                                      virt_to_phys(ch->trans_skb->data)))
                {
#else
                if(set_normalized_cda(&ch->ccw[1],ch->trans_skb->data))
                {
#endif
                        dev_kfree_skb_any(ch->trans_skb);
                        ch->trans_skb = NULL;
                        printk(KERN_WARNING "%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(ccw1_t) * 3);
#endif

                rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
                ch->prof.doios_multi++;
                if(rc != 0)
                {
                        ccw_check_return_code(ch, rc);
                }
        } else
        {
                spin_unlock(&ch->collect_lock);
                fsm_newstate(fi, CH_STATE_TXIDLE);
        }

        done:
        ctcmpc_clear_busy(dev);
#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()\n", dev->name,__FUNCTION__);
#endif
        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)
{
        channel *ch = (channel *)arg;
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif
        fsm_deltimer(&ch->timer);
        fsm_newstate(fi, CH_STATE_TXIDLE);
        fsm_event(((ctc_priv *)ch->netdev->priv)->fsm, DEV_EVENT_TXUP,
                  ch->netdev);
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

}

/**
 * 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)
{
        channel           *ch         = (channel *)arg;
        net_device        *dev        = ch->netdev;
        ctc_priv          *privptr    = (ctc_priv *)dev->priv;
        mpc_group         *grpptr     = privptr->mpcg;
        __u32             len         = ch->max_bufsize - ch->devstat->rescnt;
        struct sk_buff    *skb        = ch->trans_skb;
        struct sk_buff    *new_skb;
        int               rc          = 0;
        __u32               block_len;
        unsigned long     saveflags;
        int               gotlock     = 0;


#ifdef DEBUG
        printk(KERN_INFO "%s cp:%i enter:%s() %04x\n", 
               dev->name,smp_processor_id(),__FUNCTION__,ch->devno);
#endif
#ifdef DEBUGDATA
        printk(KERN_INFO "ctcmpc:%s() max_bufsize:%04x rescnt:%04x len:%04x\n", 
               __FUNCTION__,ch->max_bufsize,ch->devstat->rescnt,len);
#endif

        fsm_deltimer(&ch->timer);

        if(skb == NULL)
        {
#ifdef DEBUG
                printk(KERN_INFO "ctcmpc exit:  %s() TRANS_SKB = NULL \n", 
                       __FUNCTION__);
#endif	
                goto again;
        }

        if(len < TH_HEADER_LENGTH)
        {
                printk(KERN_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 exit:%s() NEW_SKB = NULL \n", 
                               __FUNCTION__);
                        printk(KERN_WARNING "%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:
                                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(ccw1_t) * 3);
#endif
                        if(!in_irq())
                        {
                                s390irq_spin_lock_irqsave(ch->irq,saveflags);
                                gotlock = 1;
                        }
                        rc = do_IO(ch->irq, &ch->ccw[0], 
                                   (intparm_t)ch, 0xff, 0);
                        if(gotlock)
                                s390irq_spin_unlock_irqrestore(ch->irq,
                                                               saveflags);
                        if(rc != 0)
                                ccw_check_return_code(ch, rc);
                        break;
                default: 
                        break;
        }
#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %s()  %04x\n", 
               dev->name,__FUNCTION__,ch->devno);
#endif

}

/**
 * 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)
{
        channel *ch = (channel *)arg;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", __FUNCTION__,ch->devno);
#endif

#ifdef DEBUG
        net_device     *dev = ch->netdev;
        mpc_group          *grpptr = ((ctc_priv *)dev->priv)->mpcg;
        printk(KERN_INFO "%s() %04x chstate:%i grpstate:%i chprotocol:%i\n", 
               __FUNCTION__, ch->devno,
               fsm_getstate(fi),
               fsm_getstate(grpptr->fsm),
               ch->protocol);
#endif

        if(fsm_getstate(fi) == CH_STATE_TXIDLE)
                printk(KERN_INFO "ch-%04x: remote side issued READ?, "
                       "init ...\n", ch->devno);
        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
                                {
                                        net_device *dev = ch->netdev;
                                        fsm_newstate(fi, CH_STATE_TXIDLE);
                                        fsm_event(((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
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", __FUNCTION__,ch->devno);
#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)
{
        channel    *ch = (channel *)arg;
        net_device *dev = ch->netdev;
        ctc_priv   *privptr = (ctc_priv *)dev->priv;
        mpc_group  *grpptr = privptr->mpcg;
        int        rc;
        unsigned long saveflags;


        fsm_deltimer(&ch->timer);
#ifdef DEBUG
        printk(KERN_INFO "%s cp:%i enter:  %s()\n", 
               dev->name,smp_processor_id(),__FUNCTION__);
#endif

#ifdef DEBUG
        printk(KERN_INFO "%s() %04x chstate:%i grpstate:%i\n", 
               __FUNCTION__, ch->devno,
               fsm_getstate(fi),
               fsm_getstate(grpptr->fsm));
#endif


        fsm_newstate(fi, CH_STATE_RXIDLE);
        /* XID processing complete */
        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(ccw1_t) * 3);
#endif
                        if(event == CH_EVENT_START)
                                s390irq_spin_lock_irqsave(ch->irq, saveflags);
                        rc = do_IO(ch->irq, &ch->ccw[0], 
                                   (intparm_t)ch, 0xff, 0);
                        if(event == CH_EVENT_START)
                                s390irq_spin_unlock_irqrestore(ch->irq, 
                                                               saveflags);
                        if(rc != 0)
                        {
                                fsm_newstate(fi, CH_STATE_RXINIT);
                                ccw_check_return_code(ch, rc);
                                goto done;
                        }
                        break;
                default:
                        break;
        }

        fsm_event(((ctc_priv *)dev->priv)->fsm,
                  DEV_EVENT_RXUP, dev);
        done:
#ifdef DEBUG
        printk(KERN_INFO "%s exit:  %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)
{
        channel *ch = (channel *)arg;
        int     rc;
        unsigned long saveflags;

        fsm_deltimer(&ch->timer);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc cp:%i enter:  %s()\n", 
               smp_processor_id(),__FUNCTION__);
#endif

        fsm_addtimer(&ch->timer, 1500, CH_EVENT_TIMER, ch);
        fsm_newstate(fi, CH_STATE_SETUPWAIT);

#ifdef DEBUGCCW
        dumpit((char *)&ch->ccw[6],sizeof(ccw1_t) * 2);
#endif

        if(event == CH_EVENT_TIMER)
                s390irq_spin_lock_irqsave(ch->irq, saveflags);
        rc = do_IO(ch->irq, &ch->ccw[6], (intparm_t)ch, 0xff, 0);
        if(event == CH_EVENT_TIMER)
                s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
        if(rc != 0)
        {
                fsm_deltimer(&ch->timer);
                fsm_newstate(fi, CH_STATE_STARTWAIT);
                ccw_check_return_code(ch, rc);
        } else
                ch->retry = 0;


#ifdef DEBUG
        printk(KERN_INFO "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)
{
        channel *ch = (channel *)arg;
        unsigned long saveflags;
        int     rc;
        net_device *dev;
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        if(ch == NULL)
        {
                printk(KERN_INFO "ctcmpc_ch_action_start ch=NULL\n");
                goto done;
        }
        if(ch->netdev == NULL)
        {
                printk(KERN_INFO "ctcmpc_ch_action_start dev=NULL, irq=%d\n",
                       ch->irq);
                goto done;
        }
        dev = ch->netdev;

#ifdef DEBUG
        printk(KERN_INFO "%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 | CCW_FLAG_CC;
                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))
                printk(KERN_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;      
        ch->ccw[2].flags    = CCW_FLAG_SLI;
        ch->ccw[2].count    = 0;
        ch->ccw[2].cda      = 0;
        /*************************************************
        ch->ccw[2].cmd_code = CCW_CMD_TIC;	 
        ch->ccw[2].flags    = 0;
        ch->ccw[2].count    = 0;
        ch->ccw[2].cda	    = virt_to_phys(&ch->ccw[15]);
        **************************************************/
        memcpy(&ch->ccw[3], &ch->ccw[0], sizeof(ccw1_t) * 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);
        s390irq_spin_lock_irqsave(ch->irq, saveflags);
        rc = halt_IO(ch->irq, (intparm_t)ch, 0);
        s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
        if(rc != 0)
        {
                fsm_deltimer(&ch->timer);
                ccw_check_return_code(ch, rc);
        }

        done:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\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)
{
        channel *ch = (channel *)arg;
        unsigned long saveflags;
        int     rc;
        int     oldstate;
        int gotlock = 0;

        fsm_deltimer(&ch->timer);
        fsm_deltimer(&ch->sweep_timer);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
        if(event == CH_EVENT_STOP)
        {
                s390irq_spin_lock_irqsave(ch->irq, saveflags);
                gotlock = 1;
        }
        oldstate = fsm_getstate(fi);
        fsm_newstate(fi, CH_STATE_TERM);
        rc = halt_IO (ch->irq, (intparm_t)ch, 0);
        if(gotlock)
                s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
        if(rc != 0)
        {
                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);
                }
        }

#ifdef DEBUG
        printk(KERN_INFO "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)
{
        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;

        fsm_deltimer(&ch->timer);
        fsm_deltimer(&ch->sweep_timer);

#ifdef DEBUG
        printk(KERN_INFO "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 DEBUGSEQ
        printk(KERN_INFO "%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(((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(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
        }

#ifdef DEBUG
        printk(KERN_INFO "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
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        fsm_newstate(fi, CH_STATE_STOPPED);

#ifdef DEBUG
        printk(KERN_INFO "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)
{
        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;

        fsm_deltimer(&ch->timer);
        fsm_deltimer(&ch->sweep_timer);
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        fsm_newstate(fi, CH_STATE_NOTOP);

        ch->th_seg = 0x00;
        ch->th_seq_num = 0x00;

#ifdef DEBUGSEQ
        printk(KERN_INFO "%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(((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(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
        }
#ifdef DEBUG
        printk(KERN_INFO "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)
{
        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;

#ifdef DEBUG
        printk(KERN_INFO "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 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_1SEC, CH_EVENT_TIMER, ch);
//		if (CHANNEL_DIRECTION(ch->flags) == READ) {
//			int rc = halt_IO (ch->irq, (intparm_t)ch, 0);
//			if (rc != 0)
//				ccw_check_return_code(ch, rc);
//		}
                goto done;
        }

        printk(KERN_INFO "%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(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
        } else
        {
                fsm_newstate(fi, CH_STATE_TXERR);
                fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
        }
        done:
#ifdef DEBUG
        printk(KERN_INFO "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
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;

        fsm_deltimer(&ch->timer);
        printk(KERN_INFO "%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);
        oldstate = fsm_getstate(fi);
        fsm_newstate(fi, CH_STATE_STARTWAIT);
        if(event == CH_EVENT_TIMER)
                s390irq_spin_lock_irqsave(ch->irq, saveflags);
        rc = halt_IO (ch->irq, (intparm_t)ch, 0);
        if(event == CH_EVENT_TIMER)
                s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
        if(rc != 0)
        {
                fsm_deltimer(&ch->timer);
                fsm_newstate(fi, oldstate);
                ccw_check_return_code(ch, rc);
        }
#ifdef DEBUG
        printk(KERN_INFO "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)
{
        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        if(event == CH_EVENT_TIMER)
        {
                printk(KERN_INFO "%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(((ctc_priv *)dev->priv)->fsm,
                                  DEV_EVENT_RXDOWN, dev);
                }
        } else
                printk(KERN_INFO "%s: Error during RX init handshake\n",
                       dev->name);
#ifdef DEBUG
        printk(KERN_INFO "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
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;

        fsm_newstate(fi, CH_STATE_RXERR);
        printk(KERN_INFO "%s: RX initialization failed\n", dev->name);
        printk(KERN_INFO "%s: RX <-> RX connection detected\n", dev->name);
        fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

}

/**
 * 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
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;

        if(event == CH_EVENT_TIMER)
        {
                fsm_deltimer(&ch->timer);
                printk(KERN_INFO "%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(((ctc_priv *)dev->priv)->fsm,
                                  DEV_EVENT_TXDOWN, dev);
                }
        } else
                printk(KERN_INFO "%s: Error during TX init handshake\n",
                       dev->name);
#ifdef DEBUG
        printk(KERN_INFO "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)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc cp:%i enter:  %s()\n", 
               smp_processor_id(),__FUNCTION__);
#endif

        channel    *ch = (channel *)arg;
        net_device *dev = ch->netdev;
        unsigned long saveflags;

        ctc_priv *privptr = (ctc_priv *)dev->priv;
        mpc_group *grpptr = privptr->mpcg;

        fsm_deltimer(&ch->timer);


        if(ch->retry++ > 3)
        {
                printk(KERN_INFO "%s: TX retry limit reached\n",
                       dev->name);
                fsm_event(((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;

                printk(KERN_INFO "%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 (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,21))
                        if(set_normalized_cda(&ch->ccw[4],
                                              virt_to_phys(skb->data)))
                        {
#else
                        if(set_normalized_cda(&ch->ccw[4],skb->data))
                        {
#endif
                                printk(KERN_INFO "%s: IDAL alloc failed, "
                                       "restarting channel\n", dev->name);
                                fsm_event(((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)
                                s390irq_spin_lock_irqsave(ch->irq, saveflags);

#ifdef DEBUGCCW
                        dumpit((char *)&ch->ccw[3],sizeof(ccw1_t) * 3);
#endif

                        rc = do_IO(ch->irq, &ch->ccw[3], 
                                   (intparm_t)ch, 0xff, 0);
                        if(event == CH_EVENT_TIMER)
                                s390irq_spin_unlock_irqrestore(ch->irq,
                                                               saveflags);
                        if(rc != 0)
                        {
                                /*not all return codes are bad */
                                /* allow retries to occur      */
                                /*ccw_check will result in ch  */
                                /* down if rc was serious error*/
                                ccw_check_return_code(ch, rc);
                        }
                }
        }
        done:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif
        return;
}
/**
 * Handle TX busy by setting timer to wait for completion
 * then normal retry logic will occur
 *
 * @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_txbusy(fsm_instance *fi, int event, void *arg)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        channel    *ch = (channel *)arg;
        if(ch->retry == 0)
        {
                fsm_deltimer(&ch->timer);
                fsm_addtimer(&ch->timer, 
                             CTC_BUSYWAIT_10SEC, 
                             CH_EVENT_TIMER, 
                             ch);
        }

#ifdef DEBUG
        printk(KERN_INFO "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
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        channel *ch = (channel *)arg;
        net_device *dev = ch->netdev;
        ctc_priv   *privptr = (ctc_priv *)dev->priv;

        fsm_deltimer(&ch->timer);

        printk(KERN_WARNING "%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)
        {
                printk(KERN_INFO "%s: RX I/O error\n", dev->name);
                fsm_newstate(fi, CH_STATE_RXERR);
                fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
        } else
        {
                printk(KERN_INFO "%s: TX I/O error\n", dev->name);
                fsm_newstate(fi, CH_STATE_TXERR);
                fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
        }
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

}

/**
 * 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_FINSTAT,     fsm_action_nop       },
        { 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_fail},
        { 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_txbusy},

        { 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.
 *****************************************************************************/

/**
 * Add a new channel to the list of channels.
 * Keeps the channel list sorted.
 *
 * @param irq   The IRQ to be used by the new channel.
 * @param devno The device number of the new channel.
 * @param type  The type class of the new channel.
 *
 * @return 0 on success, !0 on error.
 */
static int 
ctcmpc_add_channel(int irq, __u16 devno, channel_type_t type)
{
        channel **c = &channels;
        channel *ch;
        char name[10];
        int rc = 0;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif


        if((ch = (channel *)kmalloc(sizeof(channel), gfp_type())) == NULL)
        {
                printk(KERN_INFO "ctcmpc: Out of memory in %s\n",__FUNCTION__);
                rc = -1;
                goto done;
        }
        memset(ch, 0, sizeof(channel));

        ch->discontact_th = (th_header *)kmalloc(TH_HEADER_LENGTH,gfp_type());
        if(ch->discontact_th == NULL)
        {
                kfree(ch);
                printk(KERN_INFO "ctcmpc: Out of memory in %s\n",__FUNCTION__);
                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,
                     ctcmpc_action_send_discontact,
                     (unsigned long)ch);


        tasklet_init(&ch->ch_tasklet,ctcmpc_bh,(unsigned long)ch);
        if((ch->ccw = (ccw1_t *)kmalloc(sizeof(ccw1_t) * 17,
                                        GFP_KERNEL|GFP_DMA)) == NULL)
        {
                kfree(ch);
                printk(KERN_INFO "ctcmpc: Out of memory in %s\n",__FUNCTION__);
                rc = -1;
                goto done;
        }

        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: tic  (to double noops)
         * ccw[3..5] (Channel program for direct write of packets)
         *           3: prepare
         *           4: write (idal allocated on every write).
         *           5: tic  (to double noops)
         * ccw[6..7] (Channel program for initial channel setup):
         *           6: set extended mode
         *           7: 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->irq = irq;
        ch->devno = devno;
        ch->type = type;
        ch->in_mpcgroup = 0;
        sprintf(name, "ch-%04x", devno);
        ch->fsm = init_fsm(name, ch_state_names,
                           ch_event_names, NR_CH_STATES, NR_CH_EVENTS,
                           ch_fsm, CH_FSM_LEN, GFP_KERNEL);
        if(ch->fsm == NULL)
        {
                printk(KERN_INFO
                       "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->devstat = (devstat_t*)kmalloc(sizeof(devstat_t), gfp_type()))
           == NULL)
        {
                printk(KERN_INFO "ctcmpc: Out of memory in %s\n",__FUNCTION__);
                kfree_fsm(ch->fsm);
                kfree(ch);
                rc = -1;
                goto done;
        }
        memset(ch->devstat, 0, sizeof(devstat_t));
        while(*c && ((*c)->devno < devno))
                c = &(*c)->next;
        if((*c)->devno == devno)
        {
                printk(KERN_INFO
                       "ctcmpc: %s: device %04x already in list, "
                       "using old entry\n", __FUNCTION__,(*c)->devno);
                kfree(ch->devstat);
                kfree_fsm(ch->fsm);
                kfree(ch);
                rc = 0;
                goto done;
        }
        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
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return(rc);
}

#ifndef CTC_CHANDEV
/**
 * scan for all channels and create an entry in the channels list
 * for every supported channel.
 */
static void 
channel_scan(void)
{
        static int      print_result = 1;
        int             irq;
        int             nr_mpc = 0;
        s390_dev_info_t di;

        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif


        for(irq = 0; irq < NR_IRQS; irq++)
        {
                if(get_dev_info_by_irq(irq, &di) == 0)
                {
                        if((di.status == DEVSTAT_NOT_OPER) ||
                           (di.status == DEVSTAT_DEVICE_OWNED))
                                continue;
                        switch(channel_type(&di.sid_data))
                        {
                                case channel_type_mpc:
                                        /* CTC/A  or ESCON*/
                                        if(!ctcmpc_add_channel(irq, di.devno,
                                                             channel_type_mpc))
                                                nr_mpc++;
                                        break;
                                default: break;
                        }
                }
        }
        if(print_result)
        {
                if(nr_mpc)
                        printk(KERN_INFO
                               "ctcmpc: %d channel%s found.\n",
                               nr_mpc, (nr_mpc == 1) ? "s" : "");
                else
                        printk(KERN_INFO "ctcmpc: No channel devices found.\n");
        }
        print_result = 0;

        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
        #endif

}
#endif

/**
 * Release a specific channel in the channel list.
 *
 * @param ch Pointer to channel struct to be released.
 */
static void 
channel_free(channel *ch)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        ch->flags &= ~CHANNEL_FLAGS_INUSE;
        fsm_newstate(ch->fsm, CH_STATE_IDLE);
#ifdef DEBUG
        printk(KERN_INFO "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(channel *ch)
{
        channel **c = &channels;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        if(ch == NULL)
                goto done;

#ifndef CTC_CHANDEV
        if(ch->flags & CHANNEL_FLAGS_INUSE)
                FREE_IRQ(ch->irq, ch->devstat);
#endif
        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);
                        goto done;
                }
                c = &((*c)->next);
        }
        done:
#ifdef DEBUG
        printk(KERN_INFO "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 devno Device number 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 channel 
*channel_get(channel_type_t type, int devno, int direction)
{
        channel *ch = channels;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif


#ifdef DEBUG
        printk(KERN_INFO
               "ctcmpc: %s(): searching for ch with devno %d and type %d\n",
               __FUNCTION__, devno, type);
#endif

        while(ch && ((ch->devno != devno) || (ch->type != type)))
        {
#ifdef DEBUG
                printk(KERN_INFO
                       "ctcmpc: %s(): ch=0x%p (devno=%d, type=%d\n",
                       __FUNCTION__, ch, ch->devno, ch->type);
#endif
                ch = ch->next;
        }
#ifdef DEBUG
        printk(KERN_INFO
               "ctcmpc: %s(): ch=0x%pq (devno=%d, type=%d\n",
               __FUNCTION__, ch, ch->devno, ch->type);
#endif
        if(!ch)
        {
                printk(KERN_INFO "ctcmpc: %s(): channel with devno %d "
                       "and type %d not found in channel list\n",
                       __FUNCTION__, devno, 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
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return ch;
}

#ifndef CTC_CHANDEV
/**
 * Get the next free channel from the channel list
 *
 * @param type Type 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 channel 
*channel_get_next(channel_type_t type, int direction)
{
        channel *ch = channels;

        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif

        while(ch && (ch->type != type || (ch->flags & CHANNEL_FLAGS_INUSE)))
                ch = ch->next;
        if(ch)
        {
                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
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
        #endif

        return ch;
}
#endif

/**
 * Return the channel type by name.
 *
 * @param name Name of network interface.
 *
 * @return Type class of channel to be used for that interface.
 */
static channel_type_t inline 
extract_channel_media(char *name)
{
        channel_type_t ret = channel_type_unknown;
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif


        if(name != NULL)
        {
                if(strncmp(name, "mpc", 3) == 0)
                        ret = channel_type_mpc;
        }
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return ret;
}

/**
 * Find a channel in the list by its IRQ.
 *
 * @param irq IRQ to search for.
 *
 * @return Pointer to channel or NULL if no matching channel found.
 */
static channel 
*find_channel_by_irq(int irq)
{
        channel *ch = channels;
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        while(ch && (ch->irq != irq))
                ch = ch->next;
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

        return ch;
}

/**
 * Main IRQ handler.
 *
 * @param irq     The IRQ to handle.
 * @param intparm IRQ params.
 * @param regs    CPU registers.
 */
static void 
ctcmpc_irq_handler (int irq, void *intparm, struct pt_regs *regs)
{

        devstat_t  *devstat = (devstat_t *)intparm;
        channel    *ach = (channel *)devstat->intparm;
        channel    *ch  = NULL;
        net_device *dev;

        /**
         * Check for unsolicited interrupts.
         * If intparm is NULL, then loop over all our known
         * channels and try matching the irq number.
         */
        if(ach == NULL)
        {
                if((ch = find_channel_by_irq(irq)) == NULL)
                {
                        printk(KERN_INFO
                               "ctcmpc: Got unsolicited irq: %04x c-%02x d-%02x"
                               "f-%02x\n", devstat->devno, devstat->cstat,
                               devstat->dstat, devstat->flag);
                        goto done;
                }
        } else
                ch = ach;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s() %04x\n", __FUNCTION__, ch->devno);
#endif


        dev = (net_device *)(ch->netdev);
        if(dev == NULL)
        {
                printk(KERN_CRIT
                       "ctcmpc: %s dev = NULL irq=%d, ch=0x%p\n",
                       __FUNCTION__,irq, ch);
                goto done;
        }


        if(intparm == NULL)
                printk(KERN_INFO "%s: Channel %04x found by IRQ %d\n",
                       dev->name, ch->devno, irq);


#ifdef DEBUG
        printk(KERN_INFO
               "%s: cp:%i interrupt for device: %04x received "
               "in state:%s c-%02x d-%02x "
               "f-%02x\n", 
               dev->name, 
               smp_processor_id(),
               devstat->devno, 
               fsm_getstate_str(ch->fsm),
               devstat->cstat,
               devstat->dstat, 
               devstat->flag);
#endif

        /* Check for good subchannel return code, otherwise error message */
        if(devstat->cstat)
        {
                fsm_event(ch->fsm, CH_EVENT_SC_UNKNOWN, ch);
                printk(KERN_INFO
                       "%s: subchannel check for device: %04x - %02x %02x "
                       "%02x\n", dev->name, ch->devno, devstat->cstat,
                       devstat->dstat, devstat->flag);
                goto done;
        }

        /* Check the reason-code of a unit check */
        if(devstat->dstat & DEV_STAT_UNIT_CHECK)
        {
                ccw_unit_check(ch, devstat->ii.sense.data[0]);
                goto done;
        }
        if(devstat->dstat & DEV_STAT_BUSY)
        {
                if(devstat->dstat & DEV_STAT_ATTENTION)
                        fsm_event(ch->fsm, CH_EVENT_ATTNBUSY, ch);
                else
                        fsm_event(ch->fsm, CH_EVENT_BUSY, ch);
                goto done;
        }

        if(devstat->dstat & DEV_STAT_ATTENTION)
        {
                fsm_event(ch->fsm, CH_EVENT_ATTN, ch);
                goto done;
        }

        /* NOT unsolicited irq */
        if(ach)
        {
                if(devstat->dstat & DEV_STAT_DEV_END)
                {
                        if((devstat->dstat & DEV_STAT_CHN_END) ||
                           (devstat->flag & DEVSTAT_HALT_FUNCTION))
                        {
                                fsm_event(ch->fsm, CH_EVENT_FINSTAT, ch);
                                goto done;
                        } else
                        {
                                /* do_IO has not really completed */
                                fsm_event(ch->fsm, CH_EVENT_IRQ, ch);
                                goto done;
                        }
                }
        }

        if(devstat->flag & DEVSTAT_FINAL_STATUS)
                fsm_event(ch->fsm, CH_EVENT_FINSTAT, ch);
        else
                fsm_event(ch->fsm, CH_EVENT_IRQ, ch);

        done:
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s() %04x\n", __FUNCTION__, ch->devno);
#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 net_device * upon call.
 */
static void 
dev_action_start(fsm_instance *fi, int event, void *arg)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        net_device *dev = (net_device *)arg;
        ctc_priv   *privptr = (ctc_priv *)dev->priv;
        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++)
        {
                channel *ch = privptr->channel[direction];
                fsm_event(ch->fsm, CH_EVENT_START, ch);
        }
#ifdef DEBUG
        printk(KERN_INFO "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 net_device * upon call.
 */
static void 
dev_action_stop(fsm_instance *fi, int event, void *arg)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        net_device *dev = (net_device *)arg;
        ctc_priv   *privptr = (ctc_priv *)dev->priv;
        mpc_group  * grpptr = privptr->mpcg;
        int        direction;

        fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
        for(direction = READ; direction <= WRITE; direction++)
        {
                channel *ch = privptr->channel[direction];
                fsm_event(ch->fsm, CH_EVENT_STOP, ch);
                ch->th_seq_num = 0x00;

#ifdef DEBUGSEQ
                printk(KERN_INFO "%s: CH_th_seq= %08x\n" ,
                       __FUNCTION__,ch->th_seq_num);
#endif
        }
        fsm_newstate(grpptr->fsm, MPCG_STATE_RESET);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#endif

}

static void 
dev_action_restart(fsm_instance *fi, int event, void *arg)
{
        net_device *dev = (net_device *)arg;
        ctc_priv   *privptr = dev->priv;
        mpc_group  * grpptr = privptr->mpcg;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        printk(KERN_DEBUG "ctcmpc: Restarting %s 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
        printk(KERN_INFO "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 net_device * upon call.
 */
static void 
dev_action_chup(fsm_instance *fi, int event, void *arg)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        net_device *dev = (net_device *)arg;
        ctc_priv  *privptr = (ctc_priv *)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);
                                printk(KERN_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);
                                printk(KERN_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)
                ctcmpc_channel_action(privptr->channel[READ],
                                      READ,MPC_CHANNEL_ADD);
        else
                ctcmpc_channel_action(privptr->channel[WRITE],
                                      WRITE,MPC_CHANNEL_ADD);


#ifdef DEBUG
        printk(KERN_INFO "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 net_device * upon call.
 */
static void 
dev_action_chdown(fsm_instance *fi, int event, void *arg)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        net_device *dev = (net_device *)arg;
        ctc_priv   *privptr = (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)
                ctcmpc_channel_action(privptr->channel[READ],
                                      READ,MPC_CHANNEL_REMOVE);
        else
                ctcmpc_channel_action(privptr->channel[WRITE],
                                      WRITE,MPC_CHANNEL_REMOVE);


#ifdef DEBUG
        printk(KERN_INFO "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(channel *ch, struct sk_buff *skb)
{

        unsigned long saveflags;
        pdu      *p_header;
        int       rc = 0;
        net_device  *dev        = ch->netdev;
        ctc_priv    *privptr    = (ctc_priv *)dev->priv;
        mpc_group   *grpptr     = privptr->mpcg;
#ifdef DEBUGDATA
        __u32                out_len = 0;
#endif



#ifdef DEBUG
        printk(KERN_INFO "%s cp:%i enter:  %s() state: %s \n", 
               dev->name,
               smp_processor_id(),
               __FUNCTION__,
               fsm_getstate_str(ch->fsm));
#endif

        if((fsm_getstate(ch->fsm) != CH_STATE_TXIDLE) || 
           (grpptr->in_sweep))
        {
                spin_lock_irqsave(&ch->collect_lock, saveflags);
                atomic_inc(&skb->users);
                p_header = (pdu *)kmalloc(PDU_HEADER_LENGTH, gfp_type());

                if(!p_header)
                {
                        printk(KERN_WARNING ": 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;
                printk(KERN_INFO 
                       "%s(): Putting on collect_q - skb len:%04x \n", 
                       __FUNCTION__,skb->len);
                printk(KERN_INFO 
                       "%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 
                                       ": OUT OF MEMORY IN %s(): 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_any(skb);
                                skb = nskb;
                        }
                }

                p_header = (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);
                        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 = (pdu *)skb->data;  
                p_header->pdu_flag |= PDU_LAST; 

                ch->prof.txlen += skb->len - PDU_HEADER_LENGTH;

                th_header *header;
                header = (th_header *)kmalloc(TH_HEADER_LENGTH, gfp_type());

                if(!header)
                {
                        printk(KERN_WARNING 
                               "ctcmpc: OUT OF MEMORY IN %s(): 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 DEBUGSEQ
                printk(KERN_INFO "%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;
                printk(KERN_INFO "%s(): skb len: %04x \n", 
                       __FUNCTION__,skb->len);
                printk(KERN_INFO 
                       "%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 (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,21))
                if(set_normalized_cda(&ch->ccw[4], virt_to_phys(skb->data)))
                {
#else
                if(set_normalized_cda(&ch->ccw[4], skb->data))
                {
#endif
                        /**
                         * idal allocation failed, try via copying to
                         * trans_skb. trans_skb usually has a pre-allocated
                         * idal.
                         */
                        if(ctcmpc_checkalloc_buffer(ch, 1))
                        {
                                /**
                                 * We are toast.  Data lost.
                                 */
                                atomic_dec(&skb->users);
                                dev_kfree_skb_any(skb);
                                printk(KERN_WARNING 
                                       "ctcmpc: OUT OF MEMORY IN %s():"
                                       " 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_any(skb);
                        ccw_idx = 0;
#ifdef DEBUGDATA
                        if(ch->trans_skb->len > 32) out_len = 32;
                        else out_len = ch->trans_skb->len;
                        printk(KERN_INFO 
                               "%s(): TRANS skb len: %d \n", 
                               __FUNCTION__,ch->trans_skb->len);
                        printk(KERN_INFO 
                               "%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(ccw1_t) * 3);
#endif

                s390irq_spin_lock_irqsave(ch->irq, saveflags);
                ch->prof.send_stamp = xtime;


                rc = do_IO(ch->irq, &ch->ccw[ccw_idx], (intparm_t)ch, 0xff, 0);

                s390irq_spin_unlock_irqrestore(ch->irq, saveflags);

                if(ccw_idx == 3)
                        ch->prof.doios_single++;
                if(rc != 0)
                {
                        /* Not all rc from do_IO are bad.  ccw_check will     */
                        /* handle cases in which this data will not be retried*/
                        ccw_check_return_code(ch, rc);
                } else
                {
                        if(ccw_idx == 0)
                        {
                                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
        printk(KERN_INFO "%s exit:  %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(net_device *dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        MOD_INC_USE_COUNT;
        /* for MPC this is called from ctc_mpc_alloc_channel */
        /*fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_START, dev);*/
#ifdef DEBUG
        printk(KERN_INFO "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(net_device *dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        SET_DEVICE_START(dev, 0);
        /*Now called from mpc close only         */
        /*fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_STOP, dev);*/
        MOD_DEC_USE_COUNT;
#ifdef DEBUG
        printk(KERN_INFO "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, net_device *dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:%s() skb:%0lx\n", 
               __FUNCTION__,(unsigned long)skb);
#endif

        int len;
        ctc_priv  *privptr = (ctc_priv *)dev->priv;
        mpc_group *grpptr  = privptr->mpcg;

        /**
         * Some sanity checks ...
         */
        if(skb == NULL)
        {
                printk(KERN_INFO 
                       "%s: NULL sk_buff passed - EMPTY PACKET DROPPED\n", 
                       dev->name);
                privptr->stats.tx_dropped++;
                goto done;
        }
        if(skb_headroom(skb) < (TH_HEADER_LENGTH + PDU_HEADER_LENGTH))
        {
                printk(KERN_INFO
                       "%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;
                sk_buff *newskb = __dev_alloc_skb(len,gfp_type() | GFP_DMA);

                if(!newskb)
                {
                        printk(KERN_WARNING 
                               "OUT OF MEMORY - in %s Data Lost\n",
                               __FUNCTION__);
                        printk(KERN_WARNING 
                               "%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 
                       "%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 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;
        }

        dev->trans_start = jiffies;
        if(transmit_skb(privptr->channel[WRITE], skb) != 0)
        {
                printk(KERN_WARNING 
                       "ctcmpc: DEVICE ERROR in %s(): Data Lost \n",
                       __FUNCTION__);
                printk(KERN_WARNING 
                       "%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
        printk(KERN_INFO "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(net_device *dev, int new_mtu)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        ctc_priv  *privptr = (ctc_priv *)dev->priv;

        if((new_mtu < 576) || (new_mtu > 65527) ||
           (new_mtu > (privptr->channel[READ]->max_bufsize -
                       TH_HEADER_LENGTH )))
                return -EINVAL;
        dev->mtu = new_mtu;
        /* TH plus 4 byte sequence number on outbound path */
        dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH;

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:  %s()\n", __FUNCTION__);
#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(net_device *dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        return &((ctc_priv *)dev->priv)->stats;
}

/**
 * procfs related structures and routines
 *****************************************************************************/

static net_device 
*find_netdev_by_ino(unsigned long ino)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        channel *ch = channels;
        net_device *dev = NULL;
        ctc_priv *privptr;

        while(ch)
        {
                if(ch->netdev != dev)
                {
                        dev = ch->netdev;
                        privptr = (ctc_priv *)dev->priv;

                        if((privptr->proc_ctrl_entry->low_ino == ino) ||
                           (privptr->proc_stat_entry->low_ino == ino))
                                return dev;
                }
                ch = ch->next;
        }
        return NULL;
}

#define CTRL_BUFSIZE 40

static int 
ctcmpc_ctrl_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        file->private_data = kmalloc(CTRL_BUFSIZE, GFP_KERNEL);
        if(file->private_data == NULL)
                return -ENOMEM;
        MOD_INC_USE_COUNT;
        return 0;
}

static int 
ctcmpc_ctrl_close(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        kfree(file->private_data);
        MOD_DEC_USE_COUNT;
        return 0;
}

static ssize_t 
ctcmpc_ctrl_write(struct file *file, const char *buf, size_t count,
                  loff_t *off)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
        net_device   *dev;
        ctc_priv     *privptr;
        char         *e;
        int          bs1;
        char         tmp[40];

        return 0;
        /* This function is currently disabled in this MPC environment */
        if(!(dev = find_netdev_by_ino(ino)))
                return -ENODEV;
        if(off != &file->f_pos)
                return -ESPIPE;

        privptr = (ctc_priv *)dev->priv;

        if(count >= 39)
                return -EINVAL;

        if(copy_from_user(tmp, buf, count))
                return -EFAULT;
        tmp[count+1] = '\0';
        bs1 = simple_strtoul(tmp, &e, 0);

        if((bs1 > CTC_BUFSIZE_LIMIT) ||
           (e && (!isspace(*e))))
                return -EINVAL;
        if((dev->flags & IFF_RUNNING) &&
           (bs1 < (dev->mtu + TH_HEADER_LENGTH )))
                return -EINVAL;
        if(bs1 < (576 + TH_HEADER_LENGTH ))
                return -EINVAL;


        privptr->channel[READ]->max_bufsize =
        privptr->channel[WRITE]->max_bufsize = bs1;
        if(!(dev->flags & IFF_RUNNING))
                dev->mtu = bs1 - TH_HEADER_LENGTH ;
        privptr->channel[READ]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED;
        privptr->channel[WRITE]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED;

        return count;
}

static ssize_t 
ctcmpc_ctrl_read(struct file *file, char *buf, size_t count,
                 loff_t *off)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
        char *sbuf = (char *)file->private_data;
        net_device *dev;
        ctc_priv *privptr;
        ssize_t ret = 0;
        char *p = sbuf;
        int l;

        if(!(dev = find_netdev_by_ino(ino)))
                return -ENODEV;
        if(off != &file->f_pos)
                return -ESPIPE;

        privptr = (ctc_priv *)dev->priv;

        if(file->f_pos == 0)
                sprintf(sbuf, "%d\n", privptr->channel[READ]->max_bufsize);

        l = strlen(sbuf);
        p = sbuf;
        if(file->f_pos < l)
        {
                p += file->f_pos;
                l = strlen(p);
                ret = (count > l) ? l : count;
                if(copy_to_user(buf, p, ret))
                        return -EFAULT;
        }
        file->f_pos += ret;
        return ret;
}

#define STATS_BUFSIZE 2048

static int 
ctcmpc_stat_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        file->private_data = kmalloc(STATS_BUFSIZE, GFP_KERNEL);
        if(file->private_data == NULL)
                return -ENOMEM;
        MOD_INC_USE_COUNT;
        return 0;
}

static int 
ctcmpc_stat_close(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        kfree(file->private_data);
        MOD_DEC_USE_COUNT;
        return 0;
}

static ssize_t 
ctcmpc_stat_write(struct file *file, const char *buf, size_t count,
                  loff_t *off)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
        net_device *dev;
        ctc_priv *privptr;

        if(!(dev = find_netdev_by_ino(ino)))
                return -ENODEV;
        privptr = (ctc_priv *)dev->priv;
        privptr->channel[WRITE]->prof.maxmulti = 0;
        privptr->channel[WRITE]->prof.maxcqueue = 0;
        privptr->channel[WRITE]->prof.doios_single = 0;
        privptr->channel[WRITE]->prof.doios_multi = 0;
        privptr->channel[WRITE]->prof.txlen = 0;
        privptr->channel[WRITE]->prof.tx_time = 0;
        return count;
}

static ssize_t 
ctcmpc_stat_read(struct file *file, char *buf, size_t count,
                 loff_t *off)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
        char *sbuf = (char *)file->private_data;
        net_device *dev;
        ctc_priv *privptr;
        ssize_t ret = 0;
        char *p = sbuf;
        int l;

        if(!(dev = find_netdev_by_ino(ino)))
                return -ENODEV;
        if(off != &file->f_pos)
                return -ESPIPE;

        privptr = (ctc_priv *)dev->priv;

        if(file->f_pos == 0)
        {
                p += sprintf(p, "Device FSM state: %s\n",
                             fsm_getstate_str(privptr->fsm));
                p += sprintf(p, "RX channel FSM state: %s\n",
                             fsm_getstate_str(privptr->channel[READ]->fsm));
                p += sprintf(p, "TX channel FSM state: %s\n",
                             fsm_getstate_str(privptr->channel[WRITE]->fsm));
                p += sprintf(p, "Max. TX buffer used: %ld\n",
                             privptr->channel[WRITE]->prof.maxmulti);
                p += sprintf(p, "Max. chained SKBs: %ld\n",
                             privptr->channel[WRITE]->prof.maxcqueue);
                p += sprintf(p, "TX single write ops: %ld\n",
                             privptr->channel[WRITE]->prof.doios_single);
                p += sprintf(p, "TX multi write ops: %ld\n",
                             privptr->channel[WRITE]->prof.doios_multi);
                p += sprintf(p, "Netto bytes written: %ld\n",
                             privptr->channel[WRITE]->prof.txlen);
                p += sprintf(p, "Max. TX IO-time: %ld\n",
                             privptr->channel[WRITE]->prof.tx_time);
        }
        l = strlen(sbuf);
        p = sbuf;
        if(file->f_pos < l)
        {
                p += file->f_pos;
                l = strlen(p);
                ret = (count > l) ? l : count;
                if(copy_to_user(buf, p, ret))
                        return -EFAULT;
        }
        file->f_pos += ret;
        return ret;
}

static struct file_operations ctcmpc_stat_fops = {
        read:    ctcmpc_stat_read,
        write:   ctcmpc_stat_write,
        open:    ctcmpc_stat_open,
        release: ctcmpc_stat_close,
};

static struct file_operations ctcmpc_ctrl_fops = {
        read:    ctcmpc_ctrl_read,
        write:   ctcmpc_ctrl_write,
        open:    ctcmpc_ctrl_open,
        release: ctcmpc_ctrl_close,
};

static struct inode_operations ctcmpc_stat_iops = {
};
static struct inode_operations ctcmpc_ctrl_iops = {
};

static struct proc_dir_entry stat_entry = {
        0,                           /* low_ino */
        10,                          /* namelen */
        "statistics",                /* name    */
        S_IFREG | S_IRUGO | S_IWUSR, /* mode    */
        1,                           /* nlink   */
        0,                           /* uid     */
        0,                           /* gid     */
        0,                           /* size    */
        &ctcmpc_stat_iops               /* ops     */
};

static struct proc_dir_entry ctrl_entry = {
        0,                           /* low_ino */
        10,                          /* namelen */
        "buffersize",                /* name    */
        S_IFREG | S_IRUSR | S_IWUSR, /* mode    */
        1,                           /* nlink   */
        0,                           /* uid     */
        0,                           /* gid     */
        0,                           /* size    */
        &ctcmpc_ctrl_iops               /* ops     */
};

static struct proc_dir_entry *ctcmpc_dir = NULL;
static struct proc_dir_entry *ctcmpc_template = NULL;

/**
 * Create the driver's main directory /proc/net/ctc
 */
static void 
ctcmpc_proc_create_main(void)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        /**
         * If not registered, register main proc dir-entry now
         */
        if(!ctcmpc_dir)
                ctcmpc_dir = proc_mkdir("mpc", proc_net);

#ifdef DEBUG
        printk(KERN_INFO "ctcmpc exit:%s() ctcmpc_dir:%lx\n", 
               __FUNCTION__,(unsigned long)ctcmpc_dir);
#endif

}

#ifdef MODULE
/**
 * Destroy /proc/net/ctc
 */
static void 
ctcmpc_proc_destroy_main(void)
{
        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif

        if(ctcmpc_dir)
                remove_proc_entry("mpc", proc_net);
}
#endif /*MODULE*/

/**
 * Create a device specific subdirectory in /proc/net/ctc/ with the
 * same name like the device. In that directory, create 2 entries
 * "statistics" and "buffersize".
 *
 * @param dev The device for which the subdirectory should be created.
 *
 */
static void 
ctcmpc_proc_create_sub(net_device *dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        ctc_priv *privptr = (ctc_priv *)dev->priv;

        privptr->proc_dentry = proc_mkdir(dev->name, ctcmpc_dir);
        privptr->proc_stat_entry =
        create_proc_entry("statistics",
                          S_IFREG | S_IRUSR | S_IWUSR,
                          privptr->proc_dentry);
        privptr->proc_stat_entry->proc_fops = &ctcmpc_stat_fops;
        privptr->proc_stat_entry->proc_iops = &ctcmpc_stat_iops;
        privptr->proc_ctrl_entry =
        create_proc_entry("buffersize",
                          S_IFREG | S_IRUSR | S_IWUSR,
                          privptr->proc_dentry);
        privptr->proc_ctrl_entry->proc_fops = &ctcmpc_ctrl_fops;
        privptr->proc_ctrl_entry->proc_iops = &ctcmpc_ctrl_iops;
        privptr->proc_registered = 1;
}


/**
 * Destroy a device specific subdirectory.
 *
 * @param privptr Pointer to device private data.
 */
static void 
ctcmpc_proc_destroy_sub(ctc_priv *privptr)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        if(!privptr->proc_registered)
                return;
        remove_proc_entry("statistics", privptr->proc_dentry);
        remove_proc_entry("buffersize", privptr->proc_dentry);
        remove_proc_entry(privptr->proc_dentry->name, ctcmpc_dir);
        privptr->proc_registered = 0;
}



#ifndef CTC_CHANDEV
/**
 * Setup related routines
 *****************************************************************************/

/**
 * Parse a portion of the setup string describing a single device or option
 * providing the following syntax:
 *
 * [Device/OptionName[:int1][:int2][:int3]]
 *
 *
 * @param setup    Pointer to a pointer to the remainder of the parameter
 *                 string to be parsed. On return, the content of this
 *                 pointer is updated to point to the first character after
 *                 the parsed portion (e.g. possible start of next portion)
 *                 NOTE: The string pointed to must be writeable, since a
 *                 \0 is written for termination of the device/option name.
 *
 * @param dev_name Pointer to a pointer to the name of the device whose
 *                 parameters are parsed. On return, this is set to the
 *                 name of the device/option.
 *
 * @param ints     Pointer to an array of integer parameters. On return,
 *                 element 0 is set to the number of parameters found.
 *
 * @param maxip    Maximum number of ints to parse.
 *                 (ints[] must have size maxip+1)
 *
 * @return     0 if string "setup" was empty, !=0 otherwise
 */
static int 
parse_opts(char **setup, char **dev_name, int *ints, int maxip)
{
        char *cur = *setup;
        int i = 1;
        int rc = 0;
        int in_name = 1;
        int noauto = 0;

        #ifdef DEBUG
        printk(KERN_INFO
               "ctcmpc: parse_opts(): *setup='%s', maxip=%d\n", *setup, maxip);
        #endif
        if(*setup)
        {
                *dev_name = *setup;

                if(strncmp(cur, "noauto", 6) && strncmp(cur, "mpc", 3))
                {
                        if((*setup = strchr(cur, ':')))
                                *(*setup)++ = '\0';
                        printk(KERN_INFO
                               "ctcmpc: Invalid device name or option '%s'\n",
                               cur);
                        return 1;
                }
                switch(*cur)
                {
                        case 'c':
                                cur += 3;
                                break;
                        case 'm':
                                cur += 3;
                                break;
                        case 'e':
                                cur += 5;
                                break;
                        case 'n':
                                cur += 6;
                                *cur++ = '\0';
                                noauto = 1;
                }
                if(!noauto)
                {
                        while(cur &&
                              (*cur == '-' || isdigit(*cur)) &&
                              i <= maxip)
                        {
                                if(in_name)
                                {
                                        cur++;
                                        if(*cur == ':')
                                        {
                                                *cur++ = '\0';
                                                in_name = 0;
                                        }
                                } else
                                {
                                        ints[i++] =
                                        simple_strtoul(cur, NULL, 0);
        #ifdef DEBUG
                                        printk(KERN_INFO
                                               "ctcmpc: %s: ints[%d]=%d\n",
                                               __FUNCTION__,
                                               i-1, ints[i-1]);
        #endif
                                        if((cur = strchr(cur, ':')) != NULL)
                                                cur++;
                                }
                        }
                }
                ints[0] = i - 1;
                *setup = cur;
                if(cur && (*cur == ':'))
                        (*setup)++;
                rc = 1;
        }
        return rc;
}

/**
 *
 * Allocate one param struct
 *
 * If the driver is loaded as a module this functions is called during
 *   module set up and we can allocate the struct by using kmalloc()
 *
 * If the driver is statically linked into the kernel this function is called
 * when kmalloc() is not yet available so we must allocate from a static array
 *
 */
        #ifdef MODULE
                #define alloc_param() ((param *)kmalloc(sizeof(param), 
                GFP_KERNEL));
        #else
static param parms_array[MAX_STATIC_DEVICES];
static param *next_param = parms_array;
                #define alloc_param() \
        ((next_param<parms_array+MAX_STATIC_DEVICES)?next_param++:NULL)
        #endif /*MODULE*/

/**
 * Returns commandline parameter using device name as key.
 *
 * @param name Name of interface to get parameters from.
 *
 * @return Pointer to corresponting param struct, NULL if not found.
 */
static param 
*find_param(char *name)
{
        param *p = params;

        while(p && strcmp(p->name, name))
                p = p->next;
        return p;
}

/**
 * maximum number of integer parametes that may be specified
 * for one device in the setup string
 */
        #define CTC_MAX_INTPARMS 3

/**
 * Parse configuration options for all interfaces.
 *
 * This function is called from two possible locations:
 *  - If built as module, this function is called from init_module().
 *  - If built in monolithic kernel, this function is called from within
 *    init/main.c.
 * Parsing is always done here.
 *
 * Valid parameters are:
 *
 *
 *   [NAME[:0xRRRR[:0xWWWW[:P]]]]
 *
 *     where P       is the channel protocol (always 0)
 *	      0xRRRR is the cu number for the read channel
 *	      0xWWWW is the cu number for the write channel
 *	      NAME   is either mpc0 ... mpcN  for sna MPC
 *                 or     noauto
 *                 which switches off auto-detection of channels.
 *
 * @param setup    The parameter string to parse. MUST be writeable!
 * @param ints     Pointer to an array of ints. 
 */
        #ifdef MODULE
static void ctcmpc_setup(char *setup)
                #define ctcmpc_setup_return return
        #else /*MODULE*/
static int __init ctcmpc_setup(char *setup)
                #define ctcmpc_setup_return return(1)
        #endif /*MODULE*/
{
        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif

        int write_dev;
        int read_dev;
        int proto;
        param *par;
        char *dev_name;
        int ints[CTC_MAX_INTPARMS+1];

        while(parse_opts(&setup, &dev_name, ints, CTC_MAX_INTPARMS))
        {
                write_dev = -1;
                read_dev = -1;
                proto = CTC_PROTO_MPC;
        #ifdef DEBUG
                printk(KERN_INFO
                       "ctcmpc: ctcmpc_setup(): setup='%s' dev_name='%s',"
                       " ints[0]=%d)\n",
                       setup, dev_name, ints[0]);
        #endif /*DEBUG*/
                if(dev_name == NULL)
                {
                        /**
                         * happens if device name is not specified in
                         * parameter line (cf. init/main.c:get_options()
                         */
                        printk(KERN_INFO
                               "ctcmpc: %s(): Device name not specified\n",
                               __FUNCTION__);
                        ctcmpc_setup_return;
                }

        #ifdef DEBUG
                printk(KERN_INFO "name=´%s´ argc=%d\n", dev_name, ints[0]);
        #endif

                if(strcmp(dev_name, "noauto") == 0)
                {
                        printk(KERN_INFO "ctcmpc: autoprobing disabled\n");
                        ctc_no_auto = 1;
                        continue;
                }

                if(find_param(dev_name) != NULL)
                {
                        printk(KERN_INFO
                               "ctcmpc: Definition for device %s already set. "
                               "Ignoring second definition\n", dev_name);
                        continue;
                }

                switch(ints[0])
                {
                        case 3: /* protocol type passed */
                                proto = ints[3];
                                if(proto != CTC_PROTO_MPC)
                                {
                                        printk(KERN_INFO
                                               "%s: wrong protocol type "
                                               "passed\n", dev_name);
                                        ctcmpc_setup_return;
                                }
                        case 2: /* write channel passed */
                                write_dev = ints[2];
                        case 1: /* read channel passed */
                                read_dev = ints[1];
                                if(write_dev == -1)
                                        write_dev = read_dev + 1;
                                break;
                        default:
                                printk(KERN_INFO
                                       "ctcmpc: wrong number of parameter "
                                       "passed (is: %d, expected: [1..3]\n",
                                       ints[0]);
                                ctcmpc_setup_return;
                }
                par = alloc_param();
                if(!par)
                {
        #ifdef MODULE
                        printk(KERN_INFO
                               "ctcmpc: Couldn't allocate setup param block\n");
        #else
                        printk(KERN_INFO
                               "ctcmpc: Number of device definitions in "
                               " kernel commandline exceeds builtin limit "
                               " of %d devices.\n", MAX_STATIC_DEVICES);
        #endif
                        ctcmpc_setup_return;
                }
                par->read_dev = read_dev;
                par->write_dev = write_dev;
                par->proto = proto;
                strncpy(par->name, dev_name, MAX_PARAM_NAME_LEN);
                par->next = params;
                params = par;
        #ifdef DEBUG
                printk(KERN_INFO "%s: protocol=%x read=%04x write=%04x\n",
                       dev_name, proto, read_dev, write_dev);
        #endif
        }
        ctcmpc_setup_return;
}

__setup("mpc=", ctcmpc_setup);
#endif /* !CTC_CHANDEV */


static void
ctcmpc_netdev_unregister(net_device *dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        ctc_priv *privptr;

        if(!dev)
                return;
        privptr = (ctc_priv *)dev->priv;
        unregister_netdev(dev);
}

static int
ctcmpc_netdev_register(net_device *dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        return register_netdev(dev);
}

static void
ctcmpc_free_netdevice(net_device *dev, int free_dev)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        ctc_priv *privptr;
        mpc_group *grpptr;

        if(!dev)
                return;
        privptr = (ctc_priv *)dev->priv;
        if(privptr)
        {
                grpptr = privptr->mpcg;
                if(privptr->fsm)
                        kfree_fsm(privptr->fsm);
                ctcmpc_proc_destroy_sub(privptr);
                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);
                }
                kfree(privptr);
        }
#ifdef MODULE
        if(free_dev)
                kfree(dev);
#endif
}

#ifdef CTC_CHANDEV
static int
ctcmpc_shutdown(net_device *dev)
{
        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif

        ctc_priv *privptr;

        if(!dev)
                return 0;
        privptr = (ctc_priv *)dev->priv;
        channel_remove(privptr->channel[READ]);
        channel_remove(privptr->channel[WRITE]);
        ctcmpc_free_netdevice(dev, 0);
        return 0;
}
#endif

/**
 * Initialize everything of the net device except the name and the
 * channel structs.
 */
static net_device *
ctcmpc_init_netdevice(net_device *dev, int alloc_device)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

        ctc_priv *privptr;
        mpc_group *grpptr;
        int      priv_size;
        if(alloc_device)
        {
                dev = kmalloc(sizeof(net_device)
                              , GFP_KERNEL);
                if(!dev)
                        return NULL;
#ifdef DEBUG
                printk(KERN_INFO "kmalloc dev:  %s()\n", __FUNCTION__);
#endif

                memset(dev, 0, sizeof(net_device));
        }
        priv_size = sizeof(ctc_priv) + sizeof(ctcmpc_template) +
                    sizeof(stat_entry) + sizeof(ctrl_entry);
        dev->priv = kmalloc(priv_size, GFP_KERNEL);
        if(dev->priv == NULL)
        {
                if(alloc_device)
                        kfree(dev);
                return NULL;
        }

        memset(dev->priv, 0, priv_size);
        privptr = (ctc_priv *)dev->priv;

        privptr->proc_dentry = (struct proc_dir_entry *)
                               (((char *)privptr) + sizeof(ctc_priv));
        privptr->proc_stat_entry = (struct proc_dir_entry *)
                                   (((char *)privptr) + sizeof(ctc_priv) +
                                    sizeof(ctcmpc_template));
        privptr->proc_ctrl_entry = (struct proc_dir_entry *)
                                   (((char *)privptr) + sizeof(ctc_priv) +
                                    sizeof(ctcmpc_template) + 
                                    sizeof(stat_entry));
        memcpy(privptr->proc_dentry, &ctcmpc_template, sizeof(ctcmpc_template));
        memcpy(privptr->proc_stat_entry, &stat_entry, sizeof(stat_entry));
        memcpy(privptr->proc_ctrl_entry, &ctrl_entry, sizeof(ctrl_entry));

        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)
        {
                kfree(privptr);
                privptr = 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(mpc_group),GFP_KERNEL);
        if(privptr->mpcg == NULL)
        {
                kfree(privptr);
                privptr = NULL;
                if(alloc_device)
                        kfree(dev);
                return NULL;
        }
        grpptr = privptr->mpcg;
        memset(grpptr, 0, sizeof(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;
                kfree(privptr);
                privptr = 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;
                kfree(privptr);
                privptr = 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 = (th_header *)grpptr->xid_skb->data;
        memcpy(skb_put(grpptr->xid_skb,
                       TH_HEADER_LENGTH),
               &thnorm,
               TH_HEADER_LENGTH);

        privptr->xid = grpptr->xid = (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;
                kfree(privptr);
                privptr = NULL;
                if(alloc_device)
                        kfree(dev);
                return NULL;
        }

        grpptr->rcvd_xid_data =  grpptr->rcvd_xid_skb->data;
        grpptr->rcvd_xid_th =   (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,
                     ctcmpc_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;
        SET_DEVICE_START(dev, 1);
        dev_init_buffers(dev);
        dev->flags               = IFF_POINTOPOINT | IFF_NOARP;
        return dev;
}

#ifdef CTC_CHANDEV
static void
ctcmpc_chandev_msck_notify(void *dev, int msck_irq,
                           chandev_msck_status prevstatus,
                           chandev_msck_status newstatus)
{

        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif

        net_device *device = (net_device *)dev;
        ctc_priv *privptr;
        int direction;

        if(!dev)
                return;

        privptr = device->priv;
        if(prevstatus == chandev_status_revalidate)
                for(direction = READ; direction <= WRITE; direction++)
                {
                        channel *ch = privptr->channel[direction];
                        if(ch->irq == msck_irq)
                        {
                                s390_dev_info_t devinfo;

                                if(get_dev_info_by_irq(ch->irq, &devinfo))
                                        ch->devno = devinfo.devno;
                                else
                                        printk(KERN_INFO
                                               "ctcmpc_chandev_msck_notify: "
                                               "get_dev_info_by_irq failed for "
                                               "irq %d\n", ch->irq);
                        }
                }
        switch(newstatus)
        {
                case chandev_status_not_oper:
                case chandev_status_no_path:
                case chandev_status_gone:
                        for(direction = READ; direction <= WRITE; direction++)
                        {
                                channel *ch = privptr->channel[direction];
                                fsm_event(ch->fsm, CH_EVENT_MC_FAIL, ch);
                        }
                        printk(KERN_INFO
                               "ctcmpc: %s channel deactivated\n", 
                               device->name);
                        break;
                case chandev_status_all_chans_good:
                        for(direction = READ; direction <= WRITE; direction++)
                        {
                                channel *ch = privptr->channel[direction];
                                fsm_event(ch->fsm, CH_EVENT_MC_GOOD, ch);
                        }
                        printk(KERN_INFO
                               "ctcmpc: %s channel activated\n", device->name);
                        break;
                default:
                        break;
        }
}

/**
 *
 * Setup an interface.
 *
 * Like ctcmpc_setup(),ctcmpc_probe()can be called from two different locations:
 *  - If built as module, it is called from within init_module().
 *  - If built in monolithic kernel, it is called from within generic network
 *    layer during initialization for every corresponding device, declared in
 *    drivers/net/Space.c
 *
 * @param dev Pointer to net_device to be initialized.
 *
 * @returns 0 on success, !0 on failure.
 */
static int 
ctcmpc_chandev_probe(chandev_probeinfo *info)
{
        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif

        int               devno[2];
        __u16             proto;
        int               rc;
        int               direction;
        channel_type_t    type;
        ctc_priv          *privptr;
        net_device        *dev;

        ctcmpc_proc_create_main();


        switch(info->chan_type)
        {
                case chandev_type_ctcmpc:
                        type = channel_type_mpc;
                        break;
                case chandev_type_escon:
                        type = channel_type_escon;
                        break;
                default:
                        printk(KERN_INFO "ctcmpc_chandev_probe called with "
                               "unsupported channel type %d\n", 
                               info->chan_type);
                        return -ENODEV;
        }
        devno[READ]  = info->read.devno;
        devno[WRITE] = info->write.devno;
        proto        = info->port_protocol_no;

        if(ctcmpc_add_channel(info->read.irq, info->read.devno, type))
                return -ENOMEM;
        if(ctcmpc_add_channel(info->write.irq, info->write.devno, type))
                return -ENOMEM;

        dev = ctcmpc_init_netdevice(NULL, 1);


        if(!dev)
        {
                printk(KERN_INFO "ctcmpc_init_netdevice failed\n");
                return -ENODEV;
        }

        chandev_build_device_name(info, dev->name, "mpc", 1);

        privptr = (ctc_priv *)dev->priv;
        privptr->protocol = proto;
        privptr->xid = (xid2 *)kmalloc(sizeof(xid2), GFP_KERNEL);
        if(privptr->xid == NULL)
        {
                printk(KERN_INFO "ctcmpc: Out of memory in chandev_probe\n");
                return -1;
        }
        for(direction = READ; direction <= WRITE; direction++)
        {
                privptr->channel[direction] =
                channel_get(type, devno[direction], direction);
                if(privptr->channel[direction] == NULL)
                {
                        if(direction == WRITE)
                        {
                                FREE_IRQ(privptr->channel[READ]->irq,
                                         privptr->channel[READ]->devstat);
                                channel_free(privptr->channel[READ]);
                        }
                        ctcmpc_free_netdevice(dev, 1);
                        return -ENODEV;
                }
                privptr->channel[direction]->netdev = dev;
                privptr->channel[direction]->protocol = proto;
                /*todo...who put this 35 here....make it a define*/
                privptr->channel[direction]->max_bufsize = 
                        (MPC_BUFSIZE_DEFAULT - 35);
                rc = REQUEST_IRQ(privptr->channel[direction]->irq,
                                 (void *)ctcmpc_irq_handler, SA_INTERRUPT,
                                 dev->name,
                                 privptr->channel[direction]->devstat);
                if(rc)
                {
                        printk(KERN_INFO
                               "%s: requested irq %d is busy rc=%02x\n",
                               dev->name, privptr->channel[direction]->irq,
                               rc);
                        if(direction == WRITE)
                        {
                                FREE_IRQ(privptr->channel[READ]->irq,
                                         privptr->channel[READ]->devstat);
                                channel_free(privptr->channel[READ]);
                        }
                        channel_free(privptr->channel[direction]);
                        ctcmpc_free_netdevice(dev, 1);
                        return -EBUSY;
                }
        }
        if(ctcmpc_netdev_register(dev) != 0)
        {
                ctcmpc_free_netdevice(dev, 1);
                return -ENODEV;
        }

        /**
         * register subdir in /proc/net/mpc
         */
        ctcmpc_proc_create_sub(dev);
        strncpy(privptr->fsm->name, dev->name, sizeof(privptr->fsm->name));
        activated++;

        print_banner();

        printk(KERN_INFO
               "%s: read: ch %04x (irq %04x), "
               "write: ch %04x (irq %04x) proto: %d\n",
               dev->name, privptr->channel[READ]->devno,
               privptr->channel[READ]->irq, privptr->channel[WRITE]->devno,
               privptr->channel[WRITE]->irq, proto);

        chandev_initdevice(info, dev, 0, dev->name,
                           (proto == CTC_PROTO_MPC)
                           ? chandev_category_serial_device :
                           chandev_category_network_device,
                           (chandev_unregfunc)ctcmpc_netdev_unregister);
        return 0;
}
#else /* ! CHANDEV */
/**
 *
 * Setup an interface.
 *
 * Like ctcmpc_setup(),ctcmpc_probe()can be called from two different locations:
 *  - If built as module, it is called from within init_module().
 *  - If built in monolithic kernel, it is called from within generic network
 *    layer during initialization for every corresponding device, declared in
 *    drivers/net/Space.c
 *
 * @param dev Pointer to net_device to be initialized.
 *
 * @returns 0 on success, !0 on failure.
 */
int 
ctcmpc_probe(net_device *dev)
{
        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif

        int            devno[2];
        __u16          proto;
        int            rc;
        int            direction;
        channel_type_t type;
        ctc_priv       *privptr;
        param          *par;

        ctcmpc_proc_create_main();

        /**
         * Scan for available channels only the first time,
         * ctcmpc_probe gets control.
         */
        if(channels == NULL)
                channel_scan();

        type = extract_channel_media(dev->name);
        if(type == channel_type_unknown)
                return -ENODEV;

        par = find_param(dev->name);
        if(par)
        {
                devno[READ] = par->read_dev;
                devno[WRITE] = par->write_dev;
                proto = par->proto;
        } else
        {
                if(ctc_no_auto)
                        return -ENODEV;
                else
                {
                        devno[READ] = -1;
                        devno[WRITE] = -1;
                        proto = CTC_PROTO_MPC;    
                }
        }

        #ifndef MODULE
        if(ctcmpc_init_netdevice(dev, 0) == NULL)
                return -ENODEV;
        #endif
        privptr = (ctc_priv *)dev->priv;
        privptr->protocol = proto;

        for(direction = READ; direction <= WRITE; direction++)
        {
                if((ctc_no_auto == 0) || (devno[direction] == -1))
                        privptr->channel[direction] =
                        channel_get_next(type, direction);
                else
                        privptr->channel[direction] =
                        channel_get(type, devno[direction], direction);
                if(privptr->channel[direction] == NULL)
                {
                        if(direction == WRITE)
                        {
                                FREE_IRQ(privptr->channel[READ]->irq,
                                         privptr->channel[READ]->devstat);
                                channel_free(privptr->channel[READ]);
                        }
                        ctcmpc_free_netdevice(dev, 1);
                        return -ENODEV;
                }
                privptr->channel[direction]->netdev = dev;
                privptr->channel[direction]->protocol = proto;
                privptr->channel[direction]->max_bufsize = 
                        (MPC_BUFSIZE_DEFAULT - 35);
                rc = REQUEST_IRQ(privptr->channel[direction]->irq,
                                 (void *)ctcmpc_irq_handler, SA_INTERRUPT,
                                 dev->name,
                                 privptr->channel[direction]->devstat);
                if(rc)
                {
                        printk(KERN_INFO
                               "%s: requested irq %d is busy rc=%02x\n",
                               dev->name, privptr->channel[direction]->irq,
                               rc);
                        if(direction == WRITE)
                        {
                                FREE_IRQ(privptr->channel[READ]->irq,
                                         privptr->channel[READ]->devstat);
                                channel_free(privptr->channel[READ]);
                        }
                        channel_free(privptr->channel[direction]);
                        ctcmpc_free_netdevice(dev, 1);
                        return -EBUSY;
                }
        }

        /**
         * register subdir in /proc/net/ctc
         */
        ctcmpc_proc_create_sub(dev);

        print_banner();

        printk(KERN_INFO
               "%s: read: ch %04x (irq %04x), "
               "write: ch %04x (irq %04x) proto: %d\n",
               dev->name, privptr->channel[READ]->devno,
               privptr->channel[READ]->irq, privptr->channel[WRITE]->devno,
               privptr->channel[WRITE]->irq, proto);

        return 0;
}
#endif

/**
 * Module related routines
 *****************************************************************************/

#ifdef MODULE
/**
 * 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.
 */
void 
cleanup_module(void)
{
        #ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
        #endif


        /* we are called if all interfaces are down only, so no need
         * to bother around with locking stuff
         */
        #ifndef CTC_CHANDEV
        while(channels)
        {
                if((channels->flags & CHANNEL_FLAGS_INUSE) &&
                   (channels->netdev != NULL))
                {
                        net_device *dev = channels->netdev;
                        ctc_priv *privptr = (ctc_priv *)dev->priv;

                        if(privptr)
                        {
                                privptr->channel[READ]->netdev = NULL;
                                privptr->channel[WRITE]->netdev = NULL;
                        }
                        channels->netdev = NULL;
                        ctcmpc_netdev_unregister(dev);
                        ctcmpc_free_netdevice(dev, 1);
                }
                channel_remove(channels);
        }
        channels = NULL;
        #endif

        ctcmpc_proc_destroy_main();
        #ifdef CTC_CHANDEV
        chandev_unregister(ctcmpc_chandev_probe, 1);
        #endif
        printk(KERN_INFO "MPC driver unloaded\n");
}

        #define ctcmpc_init init_module
#endif /*MODULE*/

/**
 * Initialize module.
 * This is called just after the module is loaded.
 *
 * @return 0 on success, !0 on error.
 */
int 
ctcmpc_init(void)
{
#ifdef DEBUG
        printk(KERN_INFO "ctcmpc enter:  %s()\n", __FUNCTION__);
#endif

#ifndef CTC_CHANDEV
        int   cnt;
        int   activated;
        param *par;
#endif
        int   ret = 0;
        int   probed = 0;

        print_banner();

#if defined(DEBUG) && !defined(CTC_CHANDEV)
        printk(KERN_INFO
               "ctcmpc: init_module(): got string '%s'\n", mpc);
#endif

#ifndef CTC_CHANDEV
        #ifdef MODULE
        ctcmpc_setup(mpc);
        #endif
        par = params;
#endif

        activated = 0;

#ifdef CTC_CHANDEV
        chandev_register_and_probe(ctcmpc_chandev_probe,
                                   (chandev_shutdownfunc)ctcmpc_shutdown,
                                   ctcmpc_chandev_msck_notify,
                                   chandev_type_ctcmpc|chandev_type_escon);
#else /* CTC_CHANDEV */
        net_device *dev = NULL;
        char       *bname = "mpc";

        cnt = 0;
        do
        {
                dev = ctcmpc_init_netdevice(NULL, 1);
                if(!dev)
                {
                        ret = -ENOMEM;
                        break;
                }
                if(par && par->name)
                {
                        char *p;
                        int  n;

                        sprintf(dev->name, "%s", par->name);
                        par = par->next;
                        for(p = dev->name; p && *p; p++)
                                if(isdigit(*p))
                                        break;
                        if(p && *p)
                        {
                                n = simple_strtoul(p, NULL, 0);
                                if(n >= cnt)
                                        cnt = n + 1;
                        }
                } else
                {
                        if(ctc_no_auto)
                        {
                                ctcmpc_free_netdevice(dev, 1);
                                dev = NULL;
                                break;
                        }
                        sprintf(dev->name, "%s%d", bname,
                                cnt++);
                }
        #ifdef DEBUG
                printk(KERN_INFO "ctcmpc: %s(): probing for device %s\n",
                       __FUNCTION__, dev->name);
        #endif			
                probed = 1;
                if(ctcmpc_probe(dev) == 0)
                {
                        ctc_priv *privptr = (ctc_priv *)dev->priv;
        #ifdef DEBUG
                        printk(KERN_INFO
                               "ctcmpc: %s(): probing succeeded\n",
                               __FUNCTION__);
                        printk(KERN_INFO
                               "ctcmpc: %s(): registering device %s\n",
                               __FUNCTION__, dev->name);
        #endif
                        if(ctcmpc_netdev_register(dev) != 0)
                        {
                                printk(KERN_INFO
                                       "ctcmpc: Couldn't register %s\n",
                                       dev->name);
                                FREE_IRQ(
                                        privptr->channel[READ]->irq,
                                        privptr->channel[READ]->devstat);
                                FREE_IRQ(
                                        privptr->channel[WRITE]->irq,
                                        privptr->channel[WRITE]->devstat);
                                channel_free(privptr->channel[READ]);
                                channel_free(privptr->channel[WRITE]);
                                ctcmpc_free_netdevice(dev, 1);
                                dev = NULL;
                        } else
                        {
        #ifdef DEBUG
                                printk(KERN_INFO
                                       "ctcmpc: %s(): register succeed\n",
                                       __FUNCTION__);
        #endif			
                                activated++;
                        }
                } else
                {
        #ifdef DEBUG
                        printk(KERN_INFO
                               "ctcmpc: %s(): probing failed\n",
                               __FUNCTION__);
        #endif			
                        dev = NULL;
                }
        } while(dev && (ret == 0));
#endif /* CHANDEV */
#if !defined(CTC_CHANDEV) && defined(MODULE)
        if(!activated)
        {
                printk(KERN_INFO "ctcmpc: No devices registered\n");
                ret = -ENODEV;
        }
#endif
        if(ret)
        {
#if defined(CTC_CHANDEV) && defined(MODULE)
                chandev_unregister(ctcmpc_chandev_probe, 0);
#endif
#ifdef MODULE
                if(probed)
                        ctcmpc_proc_destroy_main();
#endif
        }
        return ret;
}

#ifndef MODULE
__initcall(ctcmpc_init);
#endif /* MODULE */

/* --- This is the END my friend --- */
