Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/s390/net/ctcmain.c b/drivers/s390/net/ctcmain.c
new file mode 100644
index 0000000..7266bf5
--- /dev/null
+++ b/drivers/s390/net/ctcmain.c
@@ -0,0 +1,3304 @@
+/*
+ * $Id: ctcmain.c,v 1.72 2005/03/17 10:51:52 ptiedem Exp $
+ *
+ * CTC / ESCON network driver
+ *
+ * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Fritz Elfert (elfert@de.ibm.com, felfert@millenux.com)
+ * Fixes by : Jochen Röhrig (roehrig@de.ibm.com)
+ *            Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+	      Peter Tiedemann (ptiedem@de.ibm.com)
+ * Driver Model stuff by : Cornelia Huck <cohuck@de.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.72 $
+ *
+ */
+
+#undef DEBUG
+
+#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/bitops.h>
+
+#include <linux/signal.h>
+#include <linux/string.h>
+
+#include <linux/ip.h>
+#include <linux/if_arp.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/ctype.h>
+#include <net/dst.h>
+
+#include <asm/io.h>
+#include <asm/ccwdev.h>
+#include <asm/ccwgroup.h>
+#include <asm/uaccess.h>
+
+#include <asm/idals.h>
+
+#include "ctctty.h"
+#include "fsm.h"
+#include "cu3088.h"
+#include "ctcdbug.h"
+
+MODULE_AUTHOR("(C) 2000 IBM Corp. by Fritz Elfert (felfert@millenux.com)");
+MODULE_DESCRIPTION("Linux for S/390 CTC/Escon Driver");
+MODULE_LICENSE("GPL");
+
+/**
+ * CCW commands, used in this driver.
+ */
+#define CCW_CMD_WRITE		0x01
+#define CCW_CMD_READ		0x02
+#define CCW_CMD_SET_EXTENDED	0xc3
+#define CCW_CMD_PREPARE		0xe3
+
+#define CTC_PROTO_S390          0
+#define CTC_PROTO_LINUX         1
+#define CTC_PROTO_LINUX_TTY     2
+#define CTC_PROTO_OS390         3
+#define CTC_PROTO_MAX           3
+
+#define CTC_BUFSIZE_LIMIT       65535
+#define CTC_BUFSIZE_DEFAULT     32768
+
+#define CTC_TIMEOUT_5SEC        5000
+
+#define CTC_INITIAL_BLOCKLEN    2
+
+#define READ			0
+#define WRITE			1
+
+#define CTC_ID_SIZE             BUS_ID_SIZE+3
+
+
+struct ctc_profile {
+	unsigned long maxmulti;
+	unsigned long maxcqueue;
+	unsigned long doios_single;
+	unsigned long doios_multi;
+	unsigned long txlen;
+	unsigned long tx_time;
+	struct timespec send_stamp;
+};
+
+/**
+ * Definition of one channel
+ */
+struct channel {
+
+	/**
+	 * Pointer to next channel in list.
+	 */
+	struct channel *next;
+	char id[CTC_ID_SIZE];
+	struct ccw_device *cdev;
+
+	/**
+	 * Type of this channel.
+	 * CTC/A or Escon for valid channels.
+	 */
+	enum channel_types type;
+
+	/**
+	 * Misc. flags. See CHANNEL_FLAGS_... below
+	 */
+	__u32 flags;
+
+	/**
+	 * The protocol of this channel
+	 */
+	__u16 protocol;
+
+	/**
+	 * I/O and irq related stuff
+	 */
+	struct ccw1 *ccw;
+	struct irb *irb;
+
+	/**
+	 * RX/TX buffer size
+	 */
+	int max_bufsize;
+
+	/**
+	 * Transmit/Receive buffer.
+	 */
+	struct sk_buff *trans_skb;
+
+	/**
+	 * Universal I/O queue.
+	 */
+	struct sk_buff_head io_queue;
+
+	/**
+	 * 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;
+
+	/**
+	 * The finite state machine of this channel
+	 */
+	fsm_instance *fsm;
+
+	/**
+	 * The corresponding net_device this channel
+	 * belongs to.
+	 */
+	struct net_device *netdev;
+
+	struct ctc_profile prof;
+
+	unsigned char *trans_skb_data;
+
+	__u16 logflags;
+};
+
+#define CHANNEL_FLAGS_READ            0
+#define CHANNEL_FLAGS_WRITE           1
+#define CHANNEL_FLAGS_INUSE           2
+#define CHANNEL_FLAGS_BUFSIZE_CHANGED 4
+#define CHANNEL_FLAGS_FAILED          8
+#define CHANNEL_FLAGS_WAITIRQ        16
+#define CHANNEL_FLAGS_RWMASK 1
+#define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK)
+
+#define LOG_FLAG_ILLEGALPKT  1
+#define LOG_FLAG_ILLEGALSIZE 2
+#define LOG_FLAG_OVERRUN     4
+#define LOG_FLAG_NOMEM       8
+
+#define CTC_LOGLEVEL_INFO     1
+#define CTC_LOGLEVEL_NOTICE   2
+#define CTC_LOGLEVEL_WARN     4
+#define CTC_LOGLEVEL_EMERG    8
+#define CTC_LOGLEVEL_ERR     16
+#define CTC_LOGLEVEL_DEBUG   32
+#define CTC_LOGLEVEL_CRIT    64
+
+#define CTC_LOGLEVEL_DEFAULT \
+(CTC_LOGLEVEL_INFO | CTC_LOGLEVEL_NOTICE | CTC_LOGLEVEL_WARN | CTC_LOGLEVEL_CRIT)
+
+#define CTC_LOGLEVEL_MAX     ((CTC_LOGLEVEL_CRIT<<1)-1)
+
+static int loglevel = CTC_LOGLEVEL_DEFAULT;
+
+#define ctc_pr_debug(fmt, arg...) \
+do { if (loglevel & CTC_LOGLEVEL_DEBUG) printk(KERN_DEBUG fmt,##arg); } while (0)
+
+#define ctc_pr_info(fmt, arg...) \
+do { if (loglevel & CTC_LOGLEVEL_INFO) printk(KERN_INFO fmt,##arg); } while (0)
+
+#define ctc_pr_notice(fmt, arg...) \
+do { if (loglevel & CTC_LOGLEVEL_NOTICE) printk(KERN_NOTICE fmt,##arg); } while (0)
+
+#define ctc_pr_warn(fmt, arg...) \
+do { if (loglevel & CTC_LOGLEVEL_WARN) printk(KERN_WARNING fmt,##arg); } while (0)
+
+#define ctc_pr_emerg(fmt, arg...) \
+do { if (loglevel & CTC_LOGLEVEL_EMERG) printk(KERN_EMERG fmt,##arg); } while (0)
+
+#define ctc_pr_err(fmt, arg...) \
+do { if (loglevel & CTC_LOGLEVEL_ERR) printk(KERN_ERR fmt,##arg); } while (0)
+
+#define ctc_pr_crit(fmt, arg...) \
+do { if (loglevel & CTC_LOGLEVEL_CRIT) printk(KERN_CRIT fmt,##arg); } while (0)
+
+/**
+ * Linked list of all detected channels.
+ */
+static struct channel *channels = NULL;
+
+struct ctc_priv {
+	struct net_device_stats stats;
+	unsigned long tbusy;
+	/**
+	 * The finite state machine of this interface.
+	 */
+	fsm_instance *fsm;
+	/**
+	 * The protocol of this device
+	 */
+	__u16 protocol;
+ 	/**
+ 	 * Timer for restarting after I/O Errors
+ 	 */
+ 	fsm_timer               restart_timer;
+
+	int buffer_size;
+
+	struct channel *channel[2];
+};
+
+/**
+ * Definition of our link level header.
+ */
+struct ll_header {
+	__u16 length;
+	__u16 type;
+	__u16 unused;
+};
+#define LL_HEADER_LENGTH (sizeof(struct ll_header))
+
+/**
+ * Compatibility macros for busy handling
+ * of network devices.
+ */
+static __inline__ void
+ctc_clear_busy(struct net_device * dev)
+{
+	clear_bit(0, &(((struct ctc_priv *) dev->priv)->tbusy));
+	if (((struct ctc_priv *)dev->priv)->protocol != CTC_PROTO_LINUX_TTY)
+		netif_wake_queue(dev);
+}
+
+static __inline__ int
+ctc_test_and_set_busy(struct net_device * dev)
+{
+	if (((struct ctc_priv *)dev->priv)->protocol != CTC_PROTO_LINUX_TTY)
+		netif_stop_queue(dev);
+	return test_and_set_bit(0, &((struct ctc_priv *) dev->priv)->tbusy);
+}
+
+/**
+ * Print Banner.
+ */
+static void
+print_banner(void)
+{
+	static int printed = 0;
+	char vbuf[] = "$Revision: 1.72 $";
+	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 driver Version%s"
+#ifdef DEBUG
+		    " (DEBUG-VERSION, " __DATE__ __TIME__ ")"
+#endif
+		    " initialized\n", version);
+	printed = 1;
+}
+
+/**
+ * Return type of a detected device.
+ */
+static enum channel_types
+get_channel_type(struct ccw_device_id *id)
+{
+	enum channel_types type = (enum channel_types) id->driver_info;
+
+	if (type == channel_type_ficon)
+		type = channel_type_escon;
+
+	return type;
+}
+
+/**
+ * States of the interface statemachine.
+ */
+enum dev_states {
+	DEV_STATE_STOPPED,
+	DEV_STATE_STARTWAIT_RXTX,
+	DEV_STATE_STARTWAIT_RX,
+	DEV_STATE_STARTWAIT_TX,
+	DEV_STATE_STOPWAIT_RXTX,
+	DEV_STATE_STOPWAIT_RX,
+	DEV_STATE_STOPWAIT_TX,
+	DEV_STATE_RUNNING,
+	/**
+	 * MUST be always the last element!!
+	 */
+	NR_DEV_STATES
+};
+
+static const char *dev_state_names[] = {
+	"Stopped",
+	"StartWait RXTX",
+	"StartWait RX",
+	"StartWait TX",
+	"StopWait RXTX",
+	"StopWait RX",
+	"StopWait TX",
+	"Running",
+};
+
+/**
+ * Events of the interface statemachine.
+ */
+enum dev_events {
+	DEV_EVENT_START,
+	DEV_EVENT_STOP,
+	DEV_EVENT_RXUP,
+	DEV_EVENT_TXUP,
+	DEV_EVENT_RXDOWN,
+	DEV_EVENT_TXDOWN,
+	DEV_EVENT_RESTART,
+	/**
+	 * MUST be always the last element!!
+	 */
+	NR_DEV_EVENTS
+};
+
+static const char *dev_event_names[] = {
+	"Start",
+	"Stop",
+	"RX up",
+	"TX up",
+	"RX down",
+	"TX down",
+	"Restart",
+};
+
+/**
+ * Events of the channel statemachine
+ */
+enum ch_events {
+	/**
+	 * Events, representing return code of
+	 * I/O operations (ccw_device_start, ccw_device_halt et al.)
+	 */
+	CH_EVENT_IO_SUCCESS,
+	CH_EVENT_IO_EBUSY,
+	CH_EVENT_IO_ENODEV,
+	CH_EVENT_IO_EIO,
+	CH_EVENT_IO_UNKNOWN,
+
+	CH_EVENT_ATTNBUSY,
+	CH_EVENT_ATTN,
+	CH_EVENT_BUSY,
+
+	/**
+	 * Events, representing unit-check
+	 */
+	CH_EVENT_UC_RCRESET,
+	CH_EVENT_UC_RSRESET,
+	CH_EVENT_UC_TXTIMEOUT,
+	CH_EVENT_UC_TXPARITY,
+	CH_EVENT_UC_HWFAIL,
+	CH_EVENT_UC_RXPARITY,
+	CH_EVENT_UC_ZERO,
+	CH_EVENT_UC_UNKNOWN,
+
+	/**
+	 * Events, representing subchannel-check
+	 */
+	CH_EVENT_SC_UNKNOWN,
+
+	/**
+	 * Events, representing machine checks
+	 */
+	CH_EVENT_MC_FAIL,
+	CH_EVENT_MC_GOOD,
+
+	/**
+	 * Event, representing normal IRQ
+	 */
+	CH_EVENT_IRQ,
+	CH_EVENT_FINSTAT,
+
+	/**
+	 * Event, representing timer expiry.
+	 */
+	CH_EVENT_TIMER,
+
+	/**
+	 * Events, representing commands from upper levels.
+	 */
+	CH_EVENT_START,
+	CH_EVENT_STOP,
+
+	/**
+	 * MUST be always the last element!!
+	 */
+	NR_CH_EVENTS,
+};
+
+static const char *ch_event_names[] = {
+	"ccw_device success",
+	"ccw_device busy",
+	"ccw_device enodev",
+	"ccw_device ioerr",
+	"ccw_device unknown",
+
+	"Status ATTN & BUSY",
+	"Status ATTN",
+	"Status BUSY",
+
+	"Unit check remote reset",
+	"Unit check remote system reset",
+	"Unit check TX timeout",
+	"Unit check TX parity",
+	"Unit check Hardware failure",
+	"Unit check RX parity",
+	"Unit check ZERO",
+	"Unit check Unknown",
+
+	"SubChannel check Unknown",
+
+	"Machine check failure",
+	"Machine check operational",
+
+	"IRQ normal",
+	"IRQ final",
+
+	"Timer",
+
+	"Start",
+	"Stop",
+};
+
+/**
+ * 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,
+
+	/**
+	 * 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",
+};
+
+#ifdef DEBUG
+/**
+ * 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
+ctc_dump_skb(struct sk_buff *skb, int offset)
+{
+	unsigned char *p = skb->data;
+	__u16 bl;
+	struct ll_header *header;
+	int i;
+
+	if (!(loglevel & CTC_LOGLEVEL_DEBUG))
+		return;
+	p += offset;
+	bl = *((__u16 *) p);
+	p += 2;
+	header = (struct ll_header *) p;
+	p -= 2;
+
+	printk(KERN_DEBUG "dump:\n");
+	printk(KERN_DEBUG "blocklen=%d %04x\n", bl, bl);
+
+	printk(KERN_DEBUG "h->length=%d %04x\n", header->length,
+	       header->length);
+	printk(KERN_DEBUG "h->type=%04x\n", header->type);
+	printk(KERN_DEBUG "h->unused=%04x\n", header->unused);
+	if (bl > 16)
+		bl = 16;
+	printk(KERN_DEBUG "data: ");
+	for (i = 0; i < bl; i++)
+		printk("%02x%s", *p++, (i % 16) ? " " : "\n<7>");
+	printk("\n");
+}
+#else
+static inline void
+ctc_dump_skb(struct sk_buff *skb, int offset)
+{
+}
+#endif
+
+/**
+ * Unpack a just received skb and hand it over to
+ * upper layers.
+ *
+ * @param ch The channel where this skb has been received.
+ * @param pskb The received skb.
+ */
+static __inline__ void
+ctc_unpack_skb(struct channel *ch, struct sk_buff *pskb)
+{
+	struct net_device *dev = ch->netdev;
+	struct ctc_priv *privptr = (struct ctc_priv *) dev->priv;
+	__u16 len = *((__u16 *) pskb->data);
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	skb_put(pskb, 2 + LL_HEADER_LENGTH);
+	skb_pull(pskb, 2);
+	pskb->dev = dev;
+	pskb->ip_summed = CHECKSUM_UNNECESSARY;
+	while (len > 0) {
+		struct sk_buff *skb;
+		struct ll_header *header = (struct ll_header *) pskb->data;
+
+		skb_pull(pskb, LL_HEADER_LENGTH);
+		if ((ch->protocol == CTC_PROTO_S390) &&
+		    (header->type != ETH_P_IP)) {
+
+#ifndef DEBUG
+		        if (!(ch->logflags & LOG_FLAG_ILLEGALPKT)) {
+#endif
+				/**
+				 * Check packet type only if we stick strictly
+				 * to S/390's protocol of OS390. This only
+				 * supports IP. Otherwise allow any packet
+				 * type.
+				 */
+				ctc_pr_warn(
+					"%s Illegal packet type 0x%04x received, dropping\n",
+					dev->name, header->type);
+				ch->logflags |= LOG_FLAG_ILLEGALPKT;
+#ifndef DEBUG
+			}
+#endif
+#ifdef DEBUG
+			ctc_dump_skb(pskb, -6);
+#endif
+			privptr->stats.rx_dropped++;
+			privptr->stats.rx_frame_errors++;
+			return;
+		}
+		pskb->protocol = ntohs(header->type);
+		if (header->length <= LL_HEADER_LENGTH) {
+#ifndef DEBUG
+		        if (!(ch->logflags & LOG_FLAG_ILLEGALSIZE)) {
+#endif
+				ctc_pr_warn(
+				       "%s Illegal packet size %d "
+				       "received (MTU=%d blocklen=%d), "
+				       "dropping\n", dev->name, header->length,
+				       dev->mtu, len);
+				ch->logflags |= LOG_FLAG_ILLEGALSIZE;
+#ifndef DEBUG
+			}
+#endif
+#ifdef DEBUG
+			ctc_dump_skb(pskb, -6);
+#endif
+			privptr->stats.rx_dropped++;
+			privptr->stats.rx_length_errors++;
+			return;
+		}
+		header->length -= LL_HEADER_LENGTH;
+		len -= LL_HEADER_LENGTH;
+		if ((header->length > skb_tailroom(pskb)) ||
+		    (header->length > len)) {
+#ifndef DEBUG
+		        if (!(ch->logflags & LOG_FLAG_OVERRUN)) {
+#endif
+				ctc_pr_warn(
+					"%s Illegal packet size %d "
+					"(beyond the end of received data), "
+					"dropping\n", dev->name, header->length);
+				ch->logflags |= LOG_FLAG_OVERRUN;
+#ifndef DEBUG
+			}
+#endif
+#ifdef DEBUG
+			ctc_dump_skb(pskb, -6);
+#endif
+			privptr->stats.rx_dropped++;
+			privptr->stats.rx_length_errors++;
+			return;
+		}
+		skb_put(pskb, header->length);
+		pskb->mac.raw = pskb->data;
+		len -= header->length;
+		skb = dev_alloc_skb(pskb->len);
+		if (!skb) {
+#ifndef DEBUG
+		        if (!(ch->logflags & LOG_FLAG_NOMEM)) {
+#endif
+				ctc_pr_warn(
+					"%s Out of memory in ctc_unpack_skb\n",
+					dev->name);
+				ch->logflags |= LOG_FLAG_NOMEM;
+#ifndef DEBUG
+			}
+#endif
+			privptr->stats.rx_dropped++;
+			return;
+		}
+		memcpy(skb_put(skb, pskb->len), pskb->data, pskb->len);
+		skb->mac.raw = skb->data;
+		skb->dev = pskb->dev;
+		skb->protocol = pskb->protocol;
+		pskb->ip_summed = CHECKSUM_UNNECESSARY;
+		if (ch->protocol == CTC_PROTO_LINUX_TTY)
+			ctc_tty_netif_rx(skb);
+		else
+			netif_rx_ni(skb);
+		/**
+		 * Successful rx; reset logflags
+		 */
+		ch->logflags = 0;
+		dev->last_rx = jiffies;
+		privptr->stats.rx_packets++;
+		privptr->stats.rx_bytes += skb->len;
+		if (len > 0) {
+			skb_pull(pskb, header->length);
+			if (skb_tailroom(pskb) < LL_HEADER_LENGTH) {
+#ifndef DEBUG
+				if (!(ch->logflags & LOG_FLAG_OVERRUN)) {
+#endif
+					ctc_pr_warn(
+						"%s Overrun in ctc_unpack_skb\n",
+						dev->name);
+					ch->logflags |= LOG_FLAG_OVERRUN;
+#ifndef DEBUG
+				}
+#endif
+				return;
+			}
+			skb_put(pskb, LL_HEADER_LENGTH);
+		}
+	}
+}
+
+/**
+ * Check return code of a preceeding ccw_device call, halt_IO etc...
+ *
+ * @param ch          The channel, the error belongs to.
+ * @param return_code The error code to inspect.
+ */
+static void inline
+ccw_check_return_code(struct channel *ch, int return_code, char *msg)
+{
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	switch (return_code) {
+		case 0:
+			fsm_event(ch->fsm, CH_EVENT_IO_SUCCESS, ch);
+			break;
+		case -EBUSY:
+			ctc_pr_warn("%s (%s): Busy !\n", ch->id, msg);
+			fsm_event(ch->fsm, CH_EVENT_IO_EBUSY, ch);
+			break;
+		case -ENODEV:
+			ctc_pr_emerg("%s (%s): Invalid device called for IO\n",
+				     ch->id, msg);
+			fsm_event(ch->fsm, CH_EVENT_IO_ENODEV, ch);
+			break;
+		case -EIO:
+			ctc_pr_emerg("%s (%s): Status pending... \n",
+				     ch->id, msg);
+			fsm_event(ch->fsm, CH_EVENT_IO_EIO, ch);
+			break;
+		default:
+			ctc_pr_emerg("%s (%s): Unknown error in do_IO %04x\n",
+				     ch->id, msg, return_code);
+			fsm_event(ch->fsm, CH_EVENT_IO_UNKNOWN, ch);
+	}
+}
+
+/**
+ * Check sense of a unit check.
+ *
+ * @param ch    The channel, the sense code belongs to.
+ * @param sense The sense code to inspect.
+ */
+static void inline
+ccw_unit_check(struct channel *ch, unsigned char sense)
+{
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	if (sense & SNS0_INTERVENTION_REQ) {
+		if (sense & 0x01) {
+			if (ch->protocol != CTC_PROTO_LINUX_TTY)
+				ctc_pr_debug("%s: Interface disc. or Sel. reset "
+					"(remote)\n", ch->id);
+			fsm_event(ch->fsm, CH_EVENT_UC_RCRESET, ch);
+		} else {
+			ctc_pr_debug("%s: System reset (remote)\n", ch->id);
+			fsm_event(ch->fsm, CH_EVENT_UC_RSRESET, ch);
+		}
+	} else if (sense & SNS0_EQUIPMENT_CHECK) {
+		if (sense & SNS0_BUS_OUT_CHECK) {
+			ctc_pr_warn("%s: Hardware malfunction (remote)\n",
+				    ch->id);
+			fsm_event(ch->fsm, CH_EVENT_UC_HWFAIL, ch);
+		} else {
+			ctc_pr_warn("%s: Read-data parity error (remote)\n",
+				    ch->id);
+			fsm_event(ch->fsm, CH_EVENT_UC_RXPARITY, ch);
+		}
+	} else if (sense & SNS0_BUS_OUT_CHECK) {
+		if (sense & 0x04) {
+			ctc_pr_warn("%s: Data-streaming timeout)\n", ch->id);
+			fsm_event(ch->fsm, CH_EVENT_UC_TXTIMEOUT, ch);
+		} else {
+			ctc_pr_warn("%s: Data-transfer parity error\n", ch->id);
+			fsm_event(ch->fsm, CH_EVENT_UC_TXPARITY, ch);
+		}
+	} else if (sense & SNS0_CMD_REJECT) {
+		ctc_pr_warn("%s: Command reject\n", ch->id);
+	} else if (sense == 0) {
+		ctc_pr_debug("%s: Unit check ZERO\n", ch->id);
+		fsm_event(ch->fsm, CH_EVENT_UC_ZERO, ch);
+	} else {
+		ctc_pr_warn("%s: Unit Check with sense code: %02x\n",
+			    ch->id, sense);
+		fsm_event(ch->fsm, CH_EVENT_UC_UNKNOWN, ch);
+	}
+}
+
+static void
+ctc_purge_skb_queue(struct sk_buff_head *q)
+{
+	struct sk_buff *skb;
+
+	DBF_TEXT(trace, 5, __FUNCTION__);
+
+	while ((skb = skb_dequeue(q))) {
+		atomic_dec(&skb->users);
+		dev_kfree_skb_irq(skb);
+	}
+}
+
+static __inline__ int
+ctc_checkalloc_buffer(struct channel *ch, int warn)
+{
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	if ((ch->trans_skb == NULL) ||
+	    (ch->flags & CHANNEL_FLAGS_BUFSIZE_CHANGED)) {
+		if (ch->trans_skb != NULL)
+			dev_kfree_skb(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)
+				ctc_pr_warn(
+					"%s: Couldn't alloc %s trans_skb\n",
+					ch->id,
+					(CHANNEL_DIRECTION(ch->flags) == READ) ?
+					"RX" : "TX");
+			return -ENOMEM;
+		}
+		ch->ccw[1].count = ch->max_bufsize;
+		if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) {
+			dev_kfree_skb(ch->trans_skb);
+			ch->trans_skb = NULL;
+			if (warn)
+				ctc_pr_warn(
+					"%s: set_normalized_cda for %s "
+					"trans_skb failed, dropping packets\n",
+					ch->id,
+					(CHANNEL_DIRECTION(ch->flags) == READ) ?
+					"RX" : "TX");
+			return -ENOMEM;
+		}
+		ch->ccw[1].count = 0;
+		ch->trans_skb_data = ch->trans_skb->data;
+		ch->flags &= ~CHANNEL_FLAGS_BUFSIZE_CHANGED;
+	}
+	return 0;
+}
+
+/**
+ * Dummy NOP action for statemachines
+ */
+static void
+fsm_action_nop(fsm_instance * fi, int event, void *arg)
+{
+}
+
+/**
+ * Actions for channel - statemachines.
+ *****************************************************************************/
+
+/**
+ * Normal data has been send. Free the corresponding
+ * skb (it's in io_queue), reset dev->tbusy and
+ * revert to idle state.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ch_action_txdone(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+	struct ctc_priv *privptr = dev->priv;
+	struct sk_buff *skb;
+	int first = 1;
+	int i;
+	unsigned long duration;
+	struct timespec done_stamp = xtime;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+
+	duration =
+	    (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 +
+	    (done_stamp.tv_nsec - ch->prof.send_stamp.tv_nsec) / 1000;
+	if (duration > ch->prof.tx_time)
+		ch->prof.tx_time = duration;
+
+	if (ch->irb->scsw.count != 0)
+		ctc_pr_debug("%s: TX not complete, remaining %d bytes\n",
+			     dev->name, ch->irb->scsw.count);
+	fsm_deltimer(&ch->timer);
+	while ((skb = skb_dequeue(&ch->io_queue))) {
+		privptr->stats.tx_packets++;
+		privptr->stats.tx_bytes += skb->len - LL_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) {
+		int rc;
+
+		if (ctc_checkalloc_buffer(ch, 1)) {
+			spin_unlock(&ch->collect_lock);
+			return;
+		}
+		ch->trans_skb->tail = ch->trans_skb->data = ch->trans_skb_data;
+		ch->trans_skb->len = 0;
+		if (ch->prof.maxmulti < (ch->collect_len + 2))
+			ch->prof.maxmulti = ch->collect_len + 2;
+		if (ch->prof.maxcqueue < skb_queue_len(&ch->collect_queue))
+			ch->prof.maxcqueue = skb_queue_len(&ch->collect_queue);
+		*((__u16 *) skb_put(ch->trans_skb, 2)) = ch->collect_len + 2;
+		i = 0;
+		while ((skb = skb_dequeue(&ch->collect_queue))) {
+			memcpy(skb_put(ch->trans_skb, skb->len), skb->data,
+			       skb->len);
+			privptr->stats.tx_packets++;
+			privptr->stats.tx_bytes += skb->len - LL_HEADER_LENGTH;
+			atomic_dec(&skb->users);
+			dev_kfree_skb_irq(skb);
+			i++;
+		}
+		ch->collect_len = 0;
+		spin_unlock(&ch->collect_lock);
+		ch->ccw[1].count = ch->trans_skb->len;
+		fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+		ch->prof.send_stamp = xtime;
+		rc = ccw_device_start(ch->cdev, &ch->ccw[0],
+				      (unsigned long) ch, 0xff, 0);
+		ch->prof.doios_multi++;
+		if (rc != 0) {
+			privptr->stats.tx_dropped += i;
+			privptr->stats.tx_errors += i;
+			fsm_deltimer(&ch->timer);
+			ccw_check_return_code(ch, rc, "chained TX");
+		}
+	} else {
+		spin_unlock(&ch->collect_lock);
+		fsm_newstate(fi, CH_STATE_TXIDLE);
+	}
+	ctc_clear_busy(dev);
+}
+
+/**
+ * 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
+ch_action_txidle(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	fsm_newstate(fi, CH_STATE_TXIDLE);
+	fsm_event(((struct ctc_priv *) ch->netdev->priv)->fsm, DEV_EVENT_TXUP,
+		  ch->netdev);
+}
+
+/**
+ * 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
+ch_action_rx(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+	struct ctc_priv *privptr = dev->priv;
+	int len = ch->max_bufsize - ch->irb->scsw.count;
+	struct sk_buff *skb = ch->trans_skb;
+	__u16 block_len = *((__u16 *) skb->data);
+	int check_len;
+	int rc;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	if (len < 8) {
+		ctc_pr_debug("%s: got packet with length %d < 8\n",
+			     dev->name, len);
+		privptr->stats.rx_dropped++;
+		privptr->stats.rx_length_errors++;
+		goto again;
+	}
+	if (len > ch->max_bufsize) {
+		ctc_pr_debug("%s: got packet with length %d > %d\n",
+			     dev->name, len, ch->max_bufsize);
+		privptr->stats.rx_dropped++;
+		privptr->stats.rx_length_errors++;
+		goto again;
+	}
+
+	/**
+	 * VM TCP seems to have a bug sending 2 trailing bytes of garbage.
+	 */
+	switch (ch->protocol) {
+		case CTC_PROTO_S390:
+		case CTC_PROTO_OS390:
+			check_len = block_len + 2;
+			break;
+		default:
+			check_len = block_len;
+			break;
+	}
+	if ((len < block_len) || (len > check_len)) {
+		ctc_pr_debug("%s: got block length %d != rx length %d\n",
+			     dev->name, block_len, len);
+#ifdef DEBUG
+		ctc_dump_skb(skb, 0);
+#endif
+		*((__u16 *) skb->data) = len;
+		privptr->stats.rx_dropped++;
+		privptr->stats.rx_length_errors++;
+		goto again;
+	}
+	block_len -= 2;
+	if (block_len > 0) {
+		*((__u16 *) skb->data) = block_len;
+		ctc_unpack_skb(ch, skb);
+	}
+ again:
+	skb->data = skb->tail = ch->trans_skb_data;
+	skb->len = 0;
+	if (ctc_checkalloc_buffer(ch, 1))
+		return;
+	ch->ccw[1].count = ch->max_bufsize;
+	rc = ccw_device_start(ch->cdev, &ch->ccw[0], (unsigned long) ch, 0xff, 0);
+	if (rc != 0)
+		ccw_check_return_code(ch, rc, "normal RX");
+}
+
+static void ch_action_rxidle(fsm_instance * fi, int event, void *arg);
+
+/**
+ * Initialize connection by sending a __u16 of value 0.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ch_action_firstio(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	int rc;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+
+	if (fsm_getstate(fi) == CH_STATE_TXIDLE)
+		ctc_pr_debug("%s: remote side issued READ?, init ...\n", ch->id);
+	fsm_deltimer(&ch->timer);
+	if (ctc_checkalloc_buffer(ch, 1))
+		return;
+	if ((fsm_getstate(fi) == CH_STATE_SETUPWAIT) &&
+	    (ch->protocol == CTC_PROTO_OS390)) {
+		/* OS/390 resp. z/OS */
+		if (CHANNEL_DIRECTION(ch->flags) == READ) {
+			*((__u16 *) ch->trans_skb->data) = CTC_INITIAL_BLOCKLEN;
+			fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC,
+				     CH_EVENT_TIMER, ch);
+			ch_action_rxidle(fi, event, arg);
+		} else {
+			struct net_device *dev = ch->netdev;
+			fsm_newstate(fi, CH_STATE_TXIDLE);
+			fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+				  DEV_EVENT_TXUP, dev);
+		}
+		return;
+	}
+
+	/**
+	 * Don´t setup a timer for receiving the initial RX frame
+	 * if in compatibility mode, since VM TCP delays the initial
+	 * frame until it has some data to send.
+	 */
+	if ((CHANNEL_DIRECTION(ch->flags) == WRITE) ||
+	    (ch->protocol != CTC_PROTO_S390))
+		fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+
+	*((__u16 *) ch->trans_skb->data) = CTC_INITIAL_BLOCKLEN;
+	ch->ccw[1].count = 2;	/* Transfer only length */
+
+	fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ)
+		     ? CH_STATE_RXINIT : CH_STATE_TXINIT);
+	rc = ccw_device_start(ch->cdev, &ch->ccw[0], (unsigned long) ch, 0xff, 0);
+	if (rc != 0) {
+		fsm_deltimer(&ch->timer);
+		fsm_newstate(fi, CH_STATE_SETUPWAIT);
+		ccw_check_return_code(ch, rc, "init IO");
+	}
+	/**
+	 * If in compatibility mode since we don´t setup a timer, we
+	 * also signal RX channel up immediately. This enables us
+	 * to send packets early which in turn usually triggers some
+	 * reply from VM TCP which brings up the RX channel to it´s
+	 * final state.
+	 */
+	if ((CHANNEL_DIRECTION(ch->flags) == READ) &&
+	    (ch->protocol == CTC_PROTO_S390)) {
+		struct net_device *dev = ch->netdev;
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_RXUP,
+			  dev);
+	}
+}
+
+/**
+ * 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
+ch_action_rxidle(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+	__u16 buflen;
+	int rc;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	buflen = *((__u16 *) ch->trans_skb->data);
+#ifdef DEBUG
+	ctc_pr_debug("%s: Initial RX count %d\n", dev->name, buflen);
+#endif
+	if (buflen >= CTC_INITIAL_BLOCKLEN) {
+		if (ctc_checkalloc_buffer(ch, 1))
+			return;
+		ch->ccw[1].count = ch->max_bufsize;
+		fsm_newstate(fi, CH_STATE_RXIDLE);
+		rc = ccw_device_start(ch->cdev, &ch->ccw[0],
+				      (unsigned long) ch, 0xff, 0);
+		if (rc != 0) {
+			fsm_newstate(fi, CH_STATE_RXINIT);
+			ccw_check_return_code(ch, rc, "initial RX");
+		} else
+			fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+				  DEV_EVENT_RXUP, dev);
+	} else {
+		ctc_pr_debug("%s: Initial RX count %d not %d\n",
+			     dev->name, buflen, CTC_INITIAL_BLOCKLEN);
+		ch_action_firstio(fi, event, arg);
+	}
+}
+
+/**
+ * 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
+ch_action_setmode(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	int rc;
+	unsigned long saveflags;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+	fsm_newstate(fi, CH_STATE_SETUPWAIT);
+	saveflags = 0;	/* avoids compiler warning with
+			   spin_unlock_irqrestore */
+	if (event == CH_EVENT_TIMER)	// only for timer not yet locked
+		spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+	rc = ccw_device_start(ch->cdev, &ch->ccw[6], (unsigned long) ch, 0xff, 0);
+	if (event == CH_EVENT_TIMER)
+		spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+	if (rc != 0) {
+		fsm_deltimer(&ch->timer);
+		fsm_newstate(fi, CH_STATE_STARTWAIT);
+		ccw_check_return_code(ch, rc, "set Mode");
+	} else
+		ch->retry = 0;
+}
+
+/**
+ * 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
+ch_action_start(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	unsigned long saveflags;
+	int rc;
+	struct net_device *dev;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	if (ch == NULL) {
+		ctc_pr_warn("ch_action_start ch=NULL\n");
+		return;
+	}
+	if (ch->netdev == NULL) {
+		ctc_pr_warn("ch_action_start dev=NULL, id=%s\n", ch->id);
+		return;
+	}
+	dev = ch->netdev;
+
+#ifdef DEBUG
+	ctc_pr_debug("%s: %s channel start\n", dev->name,
+		     (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+#endif
+
+	if (ch->trans_skb != NULL) {
+		clear_normalized_cda(&ch->ccw[1]);
+		dev_kfree_skb(ch->trans_skb);
+		ch->trans_skb = NULL;
+	}
+	if (CHANNEL_DIRECTION(ch->flags) == READ) {
+		ch->ccw[1].cmd_code = CCW_CMD_READ;
+		ch->ccw[1].flags = CCW_FLAG_SLI;
+		ch->ccw[1].count = 0;
+	} else {
+		ch->ccw[1].cmd_code = CCW_CMD_WRITE;
+		ch->ccw[1].flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+		ch->ccw[1].count = 0;
+	}
+	if (ctc_checkalloc_buffer(ch, 0)) {
+		ctc_pr_notice(
+			"%s: Could not allocate %s trans_skb, delaying "
+			"allocation until first transfer\n",
+			dev->name,
+			(CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+	}
+
+	ch->ccw[0].cmd_code = CCW_CMD_PREPARE;
+	ch->ccw[0].flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+	ch->ccw[0].count = 0;
+	ch->ccw[0].cda = 0;
+	ch->ccw[2].cmd_code = CCW_CMD_NOOP;	/* jointed CE + DE */
+	ch->ccw[2].flags = CCW_FLAG_SLI;
+	ch->ccw[2].count = 0;
+	ch->ccw[2].cda = 0;
+	memcpy(&ch->ccw[3], &ch->ccw[0], sizeof (struct ccw1) * 3);
+	ch->ccw[4].cda = 0;
+	ch->ccw[4].flags &= ~CCW_FLAG_IDA;
+
+	fsm_newstate(fi, CH_STATE_STARTWAIT);
+	fsm_addtimer(&ch->timer, 1000, CH_EVENT_TIMER, ch);
+	spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+	rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+	spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+	if (rc != 0) {
+		if (rc != -EBUSY)
+		    fsm_deltimer(&ch->timer);
+		ccw_check_return_code(ch, rc, "initial HaltIO");
+	}
+#ifdef DEBUG
+	ctc_pr_debug("ctc: %s(): leaving\n", __func__);
+#endif
+}
+
+/**
+ * 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
+ch_action_haltio(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	unsigned long saveflags;
+	int rc;
+	int oldstate;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+	saveflags = 0;	/* avoids comp warning with
+			   spin_unlock_irqrestore */
+	if (event == CH_EVENT_STOP)	// only for STOP not yet locked
+		spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+	oldstate = fsm_getstate(fi);
+	fsm_newstate(fi, CH_STATE_TERM);
+	rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+	if (event == CH_EVENT_STOP)
+		spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+	if (rc != 0) {
+		if (rc != -EBUSY) {
+		    fsm_deltimer(&ch->timer);
+		    fsm_newstate(fi, oldstate);
+		}
+		ccw_check_return_code(ch, rc, "HaltIO in ch_action_haltio");
+	}
+}
+
+/**
+ * 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
+ch_action_stopped(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	fsm_newstate(fi, CH_STATE_STOPPED);
+	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) {
+		skb_queue_purge(&ch->io_queue);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_RXDOWN, dev);
+	} else {
+		ctc_purge_skb_queue(&ch->io_queue);
+		spin_lock(&ch->collect_lock);
+		ctc_purge_skb_queue(&ch->collect_queue);
+		ch->collect_len = 0;
+		spin_unlock(&ch->collect_lock);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_TXDOWN, dev);
+	}
+}
+
+/**
+ * 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
+ch_action_stop(fsm_instance * fi, int event, void *arg)
+{
+	fsm_newstate(fi, CH_STATE_STOPPED);
+}
+
+/**
+ * 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
+ch_action_fail(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	fsm_newstate(fi, CH_STATE_NOTOP);
+	if (CHANNEL_DIRECTION(ch->flags) == READ) {
+		skb_queue_purge(&ch->io_queue);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_RXDOWN, dev);
+	} else {
+		ctc_purge_skb_queue(&ch->io_queue);
+		spin_lock(&ch->collect_lock);
+		ctc_purge_skb_queue(&ch->collect_queue);
+		ch->collect_len = 0;
+		spin_unlock(&ch->collect_lock);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_TXDOWN, dev);
+	}
+}
+
+/**
+ * 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
+ch_action_setuperr(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(setup, 3, __FUNCTION__);
+	/**
+	 * Special case: Got UC_RCRESET on setmode.
+	 * This means that remote side isn't setup. In this case
+	 * simply retry after some 10 secs...
+	 */
+	if ((fsm_getstate(fi) == CH_STATE_SETUPWAIT) &&
+	    ((event == CH_EVENT_UC_RCRESET) ||
+	     (event == CH_EVENT_UC_RSRESET))) {
+		fsm_newstate(fi, CH_STATE_STARTRETRY);
+		fsm_deltimer(&ch->timer);
+		fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+		if (CHANNEL_DIRECTION(ch->flags) == READ) {
+			int rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+			if (rc != 0)
+				ccw_check_return_code(
+					ch, rc, "HaltIO in ch_action_setuperr");
+		}
+		return;
+	}
+
+	ctc_pr_debug("%s: Error %s during %s channel setup state=%s\n",
+		     dev->name, ch_event_names[event],
+		     (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX",
+		     fsm_getstate_str(fi));
+	if (CHANNEL_DIRECTION(ch->flags) == READ) {
+		fsm_newstate(fi, CH_STATE_RXERR);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_RXDOWN, dev);
+	} else {
+		fsm_newstate(fi, CH_STATE_TXERR);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_TXDOWN, dev);
+	}
+}
+
+/**
+ * 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
+ch_action_restart(fsm_instance * fi, int event, void *arg)
+{
+	unsigned long saveflags;
+	int oldstate;
+	int rc;
+
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	ctc_pr_debug("%s: %s channel restart\n", dev->name,
+		     (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+	fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+	oldstate = fsm_getstate(fi);
+	fsm_newstate(fi, CH_STATE_STARTWAIT);
+	saveflags = 0;	/* avoids compiler warning with
+			   spin_unlock_irqrestore */
+	if (event == CH_EVENT_TIMER)	// only for timer not yet locked
+		spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+	rc = ccw_device_halt(ch->cdev, (unsigned long) ch);
+	if (event == CH_EVENT_TIMER)
+		spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+	if (rc != 0) {
+		if (rc != -EBUSY) {
+		    fsm_deltimer(&ch->timer);
+		    fsm_newstate(fi, oldstate);
+		}
+		ccw_check_return_code(ch, rc, "HaltIO in ch_action_restart");
+	}
+}
+
+/**
+ * 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
+ch_action_rxiniterr(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(setup, 3, __FUNCTION__);
+	if (event == CH_EVENT_TIMER) {
+		fsm_deltimer(&ch->timer);
+		ctc_pr_debug("%s: Timeout during RX init handshake\n", dev->name);
+		if (ch->retry++ < 3)
+			ch_action_restart(fi, event, arg);
+		else {
+			fsm_newstate(fi, CH_STATE_RXERR);
+			fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+				  DEV_EVENT_RXDOWN, dev);
+		}
+	} else
+		ctc_pr_warn("%s: Error during RX init handshake\n", dev->name);
+}
+
+/**
+ * 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
+ch_action_rxinitfail(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(setup, 3, __FUNCTION__);
+	fsm_newstate(fi, CH_STATE_RXERR);
+	ctc_pr_warn("%s: RX initialization failed\n", dev->name);
+	ctc_pr_warn("%s: RX <-> RX connection detected\n", dev->name);
+	fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+}
+
+/**
+ * Handle RX Unit check remote reset (remote disconnected)
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ch_action_rxdisc(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct channel *ch2;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	ctc_pr_debug("%s: Got remote disconnect, re-initializing ...\n",
+		     dev->name);
+
+	/**
+	 * Notify device statemachine
+	 */
+	fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+	fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
+
+	fsm_newstate(fi, CH_STATE_DTERM);
+	ch2 = ((struct ctc_priv *) dev->priv)->channel[WRITE];
+	fsm_newstate(ch2->fsm, CH_STATE_DTERM);
+
+	ccw_device_halt(ch->cdev, (unsigned long) ch);
+	ccw_device_halt(ch2->cdev, (unsigned long) ch2);
+}
+
+/**
+ * Handle error during TX channel initialization.
+ *
+ * @param fi    An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from channel * upon call.
+ */
+static void
+ch_action_txiniterr(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(setup, 2, __FUNCTION__);
+	if (event == CH_EVENT_TIMER) {
+		fsm_deltimer(&ch->timer);
+		ctc_pr_debug("%s: Timeout during TX init handshake\n", dev->name);
+		if (ch->retry++ < 3)
+			ch_action_restart(fi, event, arg);
+		else {
+			fsm_newstate(fi, CH_STATE_TXERR);
+			fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+				  DEV_EVENT_TXDOWN, dev);
+		}
+	} else
+		ctc_pr_warn("%s: Error during TX init handshake\n", dev->name);
+}
+
+/**
+ * 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
+ch_action_txretry(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+	unsigned long saveflags;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	if (ch->retry++ > 3) {
+		ctc_pr_debug("%s: TX retry failed, restarting channel\n",
+			     dev->name);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_TXDOWN, dev);
+		ch_action_restart(fi, event, arg);
+	} else {
+		struct sk_buff *skb;
+
+		ctc_pr_debug("%s: TX retry %d\n", dev->name, ch->retry);
+		if ((skb = skb_peek(&ch->io_queue))) {
+			int rc = 0;
+
+			clear_normalized_cda(&ch->ccw[4]);
+			ch->ccw[4].count = skb->len;
+			if (set_normalized_cda(&ch->ccw[4], skb->data)) {
+				ctc_pr_debug(
+					"%s: IDAL alloc failed, chan restart\n",
+					dev->name);
+				fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+					  DEV_EVENT_TXDOWN, dev);
+				ch_action_restart(fi, event, arg);
+				return;
+			}
+			fsm_addtimer(&ch->timer, 1000, CH_EVENT_TIMER, ch);
+			saveflags = 0;	/* avoids compiler warning with
+					   spin_unlock_irqrestore */
+			if (event == CH_EVENT_TIMER) // only for TIMER not yet locked
+				spin_lock_irqsave(get_ccwdev_lock(ch->cdev),
+						  saveflags);
+			rc = ccw_device_start(ch->cdev, &ch->ccw[3],
+					      (unsigned long) ch, 0xff, 0);
+			if (event == CH_EVENT_TIMER)
+				spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev),
+						       saveflags);
+			if (rc != 0) {
+				fsm_deltimer(&ch->timer);
+				ccw_check_return_code(ch, rc, "TX in ch_action_txretry");
+				ctc_purge_skb_queue(&ch->io_queue);
+			}
+		}
+	}
+
+}
+
+/**
+ * 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
+ch_action_iofatal(fsm_instance * fi, int event, void *arg)
+{
+	struct channel *ch = (struct channel *) arg;
+	struct net_device *dev = ch->netdev;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	fsm_deltimer(&ch->timer);
+	if (CHANNEL_DIRECTION(ch->flags) == READ) {
+		ctc_pr_debug("%s: RX I/O error\n", dev->name);
+		fsm_newstate(fi, CH_STATE_RXERR);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_RXDOWN, dev);
+	} else {
+		ctc_pr_debug("%s: TX I/O error\n", dev->name);
+		fsm_newstate(fi, CH_STATE_TXERR);
+		fsm_event(((struct ctc_priv *) dev->priv)->fsm,
+			  DEV_EVENT_TXDOWN, dev);
+	}
+}
+
+static void 
+ch_action_reinit(fsm_instance *fi, int event, void *arg)
+{
+ 	struct channel *ch = (struct channel *)arg;
+ 	struct net_device *dev = ch->netdev;
+ 	struct ctc_priv *privptr = dev->priv;
+ 
+	DBF_TEXT(trace, 4, __FUNCTION__);
+ 	ch_action_iofatal(fi, event, arg);
+ 	fsm_addtimer(&privptr->restart_timer, 1000, DEV_EVENT_RESTART, dev);
+}
+
+
+/**
+ * The statemachine for a channel.
+ */
+static const fsm_node ch_fsm[] = {
+	{CH_STATE_STOPPED,    CH_EVENT_STOP,       fsm_action_nop       },
+	{CH_STATE_STOPPED,    CH_EVENT_START,      ch_action_start      },
+	{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,       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,    ch_action_start      },
+
+	{CH_STATE_STARTWAIT,  CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_STARTWAIT,  CH_EVENT_START,      fsm_action_nop       },
+	{CH_STATE_STARTWAIT,  CH_EVENT_FINSTAT,    ch_action_setmode    },
+	{CH_STATE_STARTWAIT,  CH_EVENT_TIMER,      ch_action_setuperr   },
+	{CH_STATE_STARTWAIT,  CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
+	{CH_STATE_STARTWAIT,  CH_EVENT_IO_EIO,     ch_action_reinit     },
+	{CH_STATE_STARTWAIT,  CH_EVENT_MC_FAIL,    ch_action_fail       },
+
+	{CH_STATE_STARTRETRY, CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_STARTRETRY, CH_EVENT_TIMER,      ch_action_setmode    },
+	{CH_STATE_STARTRETRY, CH_EVENT_FINSTAT,    fsm_action_nop       },
+	{CH_STATE_STARTRETRY, CH_EVENT_MC_FAIL,    ch_action_fail       },
+
+	{CH_STATE_SETUPWAIT,  CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_START,      fsm_action_nop       },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_FINSTAT,    ch_action_firstio    },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_UC_RCRESET, ch_action_setuperr   },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_UC_RSRESET, ch_action_setuperr   },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_TIMER,      ch_action_setmode    },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_IO_EIO,     ch_action_reinit     },
+	{CH_STATE_SETUPWAIT,  CH_EVENT_MC_FAIL,    ch_action_fail       },
+
+	{CH_STATE_RXINIT,     CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_RXINIT,     CH_EVENT_START,      fsm_action_nop       },
+	{CH_STATE_RXINIT,     CH_EVENT_FINSTAT,    ch_action_rxidle     },
+	{CH_STATE_RXINIT,     CH_EVENT_UC_RCRESET, ch_action_rxiniterr  },
+	{CH_STATE_RXINIT,     CH_EVENT_UC_RSRESET, ch_action_rxiniterr  },
+	{CH_STATE_RXINIT,     CH_EVENT_TIMER,      ch_action_rxiniterr  },
+	{CH_STATE_RXINIT,     CH_EVENT_ATTNBUSY,   ch_action_rxinitfail },
+	{CH_STATE_RXINIT,     CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
+	{CH_STATE_RXINIT,     CH_EVENT_IO_EIO,     ch_action_reinit     },
+	{CH_STATE_RXINIT,     CH_EVENT_UC_ZERO,    ch_action_firstio    },
+	{CH_STATE_RXINIT,     CH_EVENT_MC_FAIL,    ch_action_fail       },
+
+	{CH_STATE_RXIDLE,     CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_RXIDLE,     CH_EVENT_START,      fsm_action_nop       },
+	{CH_STATE_RXIDLE,     CH_EVENT_FINSTAT,    ch_action_rx         },
+	{CH_STATE_RXIDLE,     CH_EVENT_UC_RCRESET, ch_action_rxdisc     },
+//      {CH_STATE_RXIDLE,     CH_EVENT_UC_RSRESET, ch_action_rxretry    },
+	{CH_STATE_RXIDLE,     CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
+	{CH_STATE_RXIDLE,     CH_EVENT_IO_EIO,     ch_action_reinit     },
+	{CH_STATE_RXIDLE,     CH_EVENT_MC_FAIL,    ch_action_fail       },
+	{CH_STATE_RXIDLE,     CH_EVENT_UC_ZERO,    ch_action_rx         },
+
+	{CH_STATE_TXINIT,     CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_TXINIT,     CH_EVENT_START,      fsm_action_nop       },
+	{CH_STATE_TXINIT,     CH_EVENT_FINSTAT,    ch_action_txidle     },
+	{CH_STATE_TXINIT,     CH_EVENT_UC_RCRESET, ch_action_txiniterr  },
+	{CH_STATE_TXINIT,     CH_EVENT_UC_RSRESET, ch_action_txiniterr  },
+	{CH_STATE_TXINIT,     CH_EVENT_TIMER,      ch_action_txiniterr  },
+	{CH_STATE_TXINIT,     CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
+	{CH_STATE_TXINIT,     CH_EVENT_IO_EIO,     ch_action_reinit     },
+	{CH_STATE_TXINIT,     CH_EVENT_MC_FAIL,    ch_action_fail       },
+
+	{CH_STATE_TXIDLE,     CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_TXIDLE,     CH_EVENT_START,      fsm_action_nop       },
+	{CH_STATE_TXIDLE,     CH_EVENT_FINSTAT,    ch_action_firstio    },
+	{CH_STATE_TXIDLE,     CH_EVENT_UC_RCRESET, fsm_action_nop       },
+	{CH_STATE_TXIDLE,     CH_EVENT_UC_RSRESET, fsm_action_nop       },
+	{CH_STATE_TXIDLE,     CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
+	{CH_STATE_TXIDLE,     CH_EVENT_IO_EIO,     ch_action_reinit     },
+	{CH_STATE_TXIDLE,     CH_EVENT_MC_FAIL,    ch_action_fail       },
+
+	{CH_STATE_TERM,       CH_EVENT_STOP,       fsm_action_nop       },
+	{CH_STATE_TERM,       CH_EVENT_START,      ch_action_restart    },
+	{CH_STATE_TERM,       CH_EVENT_FINSTAT,    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,    ch_action_fail       },
+
+	{CH_STATE_DTERM,      CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_DTERM,      CH_EVENT_START,      ch_action_restart    },
+	{CH_STATE_DTERM,      CH_EVENT_FINSTAT,    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,    ch_action_fail       },
+
+	{CH_STATE_TX,         CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_TX,         CH_EVENT_START,      fsm_action_nop       },
+	{CH_STATE_TX,         CH_EVENT_FINSTAT,    ch_action_txdone     },
+	{CH_STATE_TX,         CH_EVENT_UC_RCRESET, ch_action_txretry    },
+	{CH_STATE_TX,         CH_EVENT_UC_RSRESET, ch_action_txretry    },
+	{CH_STATE_TX,         CH_EVENT_TIMER,      ch_action_txretry    },
+	{CH_STATE_TX,         CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
+	{CH_STATE_TX,         CH_EVENT_IO_EIO,     ch_action_reinit     },
+	{CH_STATE_TX,         CH_EVENT_MC_FAIL,    ch_action_fail       },
+
+	{CH_STATE_RXERR,      CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_TXERR,      CH_EVENT_STOP,       ch_action_haltio     },
+	{CH_STATE_TXERR,      CH_EVENT_MC_FAIL,    ch_action_fail       },
+	{CH_STATE_RXERR,      CH_EVENT_MC_FAIL,    ch_action_fail       },
+};
+
+static const int CH_FSM_LEN = sizeof (ch_fsm) / sizeof (fsm_node);
+
+/**
+ * Functions related to setup and device detection.
+ *****************************************************************************/
+
+static inline int
+less_than(char *id1, char *id2)
+{
+	int dev1, dev2, i;
+
+	for (i = 0; i < 5; i++) {
+		id1++;
+		id2++;
+	}
+	dev1 = simple_strtoul(id1, &id1, 16);
+	dev2 = simple_strtoul(id2, &id2, 16);
+	
+	return (dev1 < dev2);
+}
+
+/**
+ * Add a new channel to the list of channels.
+ * Keeps the channel list sorted.
+ *
+ * @param cdev  The ccw_device to be added.
+ * @param type  The type class of the new channel.
+ *
+ * @return 0 on success, !0 on error.
+ */
+static int
+add_channel(struct ccw_device *cdev, enum channel_types type)
+{
+	struct channel **c = &channels;
+	struct channel *ch;
+
+	DBF_TEXT(trace, 2, __FUNCTION__);
+	if ((ch =
+	     (struct channel *) kmalloc(sizeof (struct channel),
+					GFP_KERNEL)) == NULL) {
+		ctc_pr_warn("ctc: Out of memory in add_channel\n");
+		return -1;
+	}
+	memset(ch, 0, sizeof (struct channel));
+	if ((ch->ccw = (struct ccw1 *) kmalloc(8*sizeof(struct ccw1),
+					       GFP_KERNEL | GFP_DMA)) == NULL) {
+		kfree(ch);
+		ctc_pr_warn("ctc: Out of memory in add_channel\n");
+		return -1;
+	}
+
+	memset(ch->ccw, 0, 8*sizeof(struct ccw1));	// assure all flags and counters are reset
+
+	/**
+	 * "static" ccws are used in the following way:
+	 *
+	 * ccw[0..2] (Channel program for generic I/O):
+	 *           0: prepare
+	 *           1: read or write (depending on direction) with fixed
+	 *              buffer (idal allocated once when buffer is allocated)
+	 *           2: nop
+	 * ccw[3..5] (Channel program for direct write of packets)
+	 *           3: prepare
+	 *           4: write (idal allocated on every write).
+	 *           5: nop
+	 * ccw[6..7] (Channel program for initial channel setup):
+	 *           6: set extended mode
+	 *           7: nop
+	 *
+	 * ch->ccw[0..5] are initialized in ch_action_start because
+	 * the channel's direction is yet unknown here.
+	 */
+	ch->ccw[6].cmd_code = CCW_CMD_SET_EXTENDED;
+	ch->ccw[6].flags = CCW_FLAG_SLI;
+
+	ch->ccw[7].cmd_code = CCW_CMD_NOOP;
+	ch->ccw[7].flags = CCW_FLAG_SLI;
+
+	ch->cdev = cdev;
+	snprintf(ch->id, CTC_ID_SIZE, "ch-%s", cdev->dev.bus_id);
+	ch->type = type;
+	loglevel = CTC_LOGLEVEL_DEFAULT;
+	ch->fsm = init_fsm(ch->id, ch_state_names,
+			   ch_event_names, NR_CH_STATES, NR_CH_EVENTS,
+			   ch_fsm, CH_FSM_LEN, GFP_KERNEL);
+	if (ch->fsm == NULL) {
+		ctc_pr_warn("ctc: Could not create FSM in add_channel\n");
+		kfree(ch->ccw);
+		kfree(ch);
+		return -1;
+	}
+	fsm_newstate(ch->fsm, CH_STATE_IDLE);
+	if ((ch->irb = (struct irb *) kmalloc(sizeof (struct irb),
+					      GFP_KERNEL)) == NULL) {
+		ctc_pr_warn("ctc: Out of memory in add_channel\n");
+		kfree_fsm(ch->fsm);
+		kfree(ch->ccw);
+		kfree(ch);
+		return -1;
+	}
+	memset(ch->irb, 0, sizeof (struct irb));
+	while (*c && less_than((*c)->id, ch->id))
+		c = &(*c)->next;
+	if (*c && (!strncmp((*c)->id, ch->id, CTC_ID_SIZE))) {
+		ctc_pr_debug(
+			"ctc: add_channel: device %s already in list, "
+			"using old entry\n", (*c)->id);
+		kfree(ch->irb);
+		kfree_fsm(ch->fsm);
+		kfree(ch->ccw);
+		kfree(ch);
+		return 0;
+	}
+	fsm_settimer(ch->fsm, &ch->timer);
+	skb_queue_head_init(&ch->io_queue);
+	skb_queue_head_init(&ch->collect_queue);
+	ch->next = *c;
+	*c = ch;
+	return 0;
+}
+
+/**
+ * Release a specific channel in the channel list.
+ *
+ * @param ch Pointer to channel struct to be released.
+ */
+static void
+channel_free(struct channel *ch)
+{
+	ch->flags &= ~CHANNEL_FLAGS_INUSE;
+	fsm_newstate(ch->fsm, CH_STATE_IDLE);
+}
+
+/**
+ * Remove a specific channel in the channel list.
+ *
+ * @param ch Pointer to channel struct to be released.
+ */
+static void
+channel_remove(struct channel *ch)
+{
+	struct channel **c = &channels;
+
+	DBF_TEXT(trace, 2, __FUNCTION__);
+	if (ch == NULL)
+		return;
+
+	channel_free(ch);
+	while (*c) {
+		if (*c == ch) {
+			*c = ch->next;
+			fsm_deltimer(&ch->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(ch->trans_skb);
+			}
+			kfree(ch->ccw);
+			kfree(ch->irb);
+			kfree(ch);
+			return;
+		}
+		c = &((*c)->next);
+	}
+}
+
+/**
+ * Get a specific channel from the channel list.
+ *
+ * @param type Type of channel we are interested in.
+ * @param id Id of channel we are interested in.
+ * @param direction Direction we want to use this channel for.
+ *
+ * @return Pointer to a channel or NULL if no matching channel available.
+ */
+static struct channel
+*
+channel_get(enum channel_types type, char *id, int direction)
+{
+	struct channel *ch = channels;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+#ifdef DEBUG
+	ctc_pr_debug("ctc: %s(): searching for ch with id %s and type %d\n",
+		     __func__, id, type);
+#endif
+
+	while (ch && ((strncmp(ch->id, id, CTC_ID_SIZE)) || (ch->type != type))) {
+#ifdef DEBUG
+		ctc_pr_debug("ctc: %s(): ch=0x%p (id=%s, type=%d\n",
+			     __func__, ch, ch->id, ch->type);
+#endif
+		ch = ch->next;
+	}
+#ifdef DEBUG
+	ctc_pr_debug("ctc: %s(): ch=0x%pq (id=%s, type=%d\n",
+		     __func__, ch, ch->id, ch->type);
+#endif
+	if (!ch) {
+		ctc_pr_warn("ctc: %s(): channel with id %s "
+			    "and type %d not found in channel list\n",
+			    __func__, id, type);
+	} else {
+		if (ch->flags & CHANNEL_FLAGS_INUSE)
+			ch = NULL;
+		else {
+			ch->flags |= CHANNEL_FLAGS_INUSE;
+			ch->flags &= ~CHANNEL_FLAGS_RWMASK;
+			ch->flags |= (direction == WRITE)
+			    ? CHANNEL_FLAGS_WRITE : CHANNEL_FLAGS_READ;
+			fsm_newstate(ch->fsm, CH_STATE_STOPPED);
+		}
+	}
+	return ch;
+}
+
+/**
+ * Return the channel type by name.
+ *
+ * @param name Name of network interface.
+ *
+ * @return Type class of channel to be used for that interface.
+ */
+static enum channel_types inline
+extract_channel_media(char *name)
+{
+	enum channel_types ret = channel_type_unknown;
+
+	if (name != NULL) {
+		if (strncmp(name, "ctc", 3) == 0)
+			ret = channel_type_parallel;
+		if (strncmp(name, "escon", 5) == 0)
+			ret = channel_type_escon;
+	}
+	return ret;
+}
+
+static long
+__ctc_check_irb_error(struct ccw_device *cdev, struct irb *irb)
+{
+	if (!IS_ERR(irb))
+		return 0;
+
+	switch (PTR_ERR(irb)) {
+	case -EIO:
+		ctc_pr_warn("i/o-error on device %s\n", cdev->dev.bus_id);
+//		CTC_DBF_TEXT(trace, 2, "ckirberr");
+//		CTC_DBF_TEXT_(trace, 2, "  rc%d", -EIO);
+		break;
+	case -ETIMEDOUT:
+		ctc_pr_warn("timeout on device %s\n", cdev->dev.bus_id);
+//		CTC_DBF_TEXT(trace, 2, "ckirberr");
+//		CTC_DBF_TEXT_(trace, 2, "  rc%d", -ETIMEDOUT);
+		break;
+	default:
+		ctc_pr_warn("unknown error %ld on device %s\n", PTR_ERR(irb),
+			   cdev->dev.bus_id);
+//		CTC_DBF_TEXT(trace, 2, "ckirberr");
+//		CTC_DBF_TEXT(trace, 2, "  rc???");
+	}
+	return PTR_ERR(irb);
+}
+
+/**
+ * Main IRQ handler.
+ *
+ * @param cdev    The ccw_device the interrupt is for.
+ * @param intparm interruption parameter.
+ * @param irb     interruption response block.
+ */
+static void
+ctc_irq_handler(struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
+{
+	struct channel *ch;
+	struct net_device *dev;
+	struct ctc_priv *priv;
+
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	if (__ctc_check_irb_error(cdev, irb))
+		return;
+
+	/* Check for unsolicited interrupts. */
+	if (!cdev->dev.driver_data) {
+		ctc_pr_warn("ctc: Got unsolicited irq: %s c-%02x d-%02x\n",
+			    cdev->dev.bus_id, irb->scsw.cstat,
+			    irb->scsw.dstat);
+		return;
+	}
+	
+	priv = ((struct ccwgroup_device *)cdev->dev.driver_data)
+		->dev.driver_data;
+
+	/* Try to extract channel from driver data. */
+	if (priv->channel[READ]->cdev == cdev)
+		ch = priv->channel[READ];
+	else if (priv->channel[WRITE]->cdev == cdev)
+		ch = priv->channel[WRITE];
+	else {
+		ctc_pr_err("ctc: Can't determine channel for interrupt, "
+			   "device %s\n", cdev->dev.bus_id);
+		return;
+	}
+	
+	dev = (struct net_device *) (ch->netdev);
+	if (dev == NULL) {
+		ctc_pr_crit("ctc: ctc_irq_handler dev=NULL bus_id=%s, ch=0x%p\n",
+			    cdev->dev.bus_id, ch);
+		return;
+	}
+
+#ifdef DEBUG
+	ctc_pr_debug("%s: interrupt for device: %s received c-%02x d-%02x\n",
+		     dev->name, ch->id, irb->scsw.cstat, irb->scsw.dstat);
+#endif
+
+	/* Copy interruption response block. */
+	memcpy(ch->irb, irb, sizeof(struct irb));
+
+	/* Check for good subchannel return code, otherwise error message */
+	if (ch->irb->scsw.cstat) {
+		fsm_event(ch->fsm, CH_EVENT_SC_UNKNOWN, ch);
+		ctc_pr_warn("%s: subchannel check for device: %s - %02x %02x\n",
+			    dev->name, ch->id, ch->irb->scsw.cstat,
+			    ch->irb->scsw.dstat);
+		return;
+	}
+
+	/* Check the reason-code of a unit check */
+	if (ch->irb->scsw.dstat & DEV_STAT_UNIT_CHECK) {
+		ccw_unit_check(ch, ch->irb->ecw[0]);
+		return;
+	}
+	if (ch->irb->scsw.dstat & DEV_STAT_BUSY) {
+		if (ch->irb->scsw.dstat & DEV_STAT_ATTENTION)
+			fsm_event(ch->fsm, CH_EVENT_ATTNBUSY, ch);
+		else
+			fsm_event(ch->fsm, CH_EVENT_BUSY, ch);
+		return;
+	}
+	if (ch->irb->scsw.dstat & DEV_STAT_ATTENTION) {
+		fsm_event(ch->fsm, CH_EVENT_ATTN, ch);
+		return;
+	}
+	if ((ch->irb->scsw.stctl & SCSW_STCTL_SEC_STATUS) ||
+	    (ch->irb->scsw.stctl == SCSW_STCTL_STATUS_PEND) ||
+	    (ch->irb->scsw.stctl ==
+	     (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND)))
+		fsm_event(ch->fsm, CH_EVENT_FINSTAT, ch);
+	else
+		fsm_event(ch->fsm, CH_EVENT_IRQ, ch);
+
+}
+
+/**
+ * Actions for interface - statemachine.
+ *****************************************************************************/
+
+/**
+ * Startup channels by sending CH_EVENT_START to each channel.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_start(fsm_instance * fi, int event, void *arg)
+{
+	struct net_device *dev = (struct net_device *) arg;
+	struct ctc_priv *privptr = dev->priv;
+	int direction;
+
+	DBF_TEXT(setup, 3, __FUNCTION__);
+	fsm_deltimer(&privptr->restart_timer);
+	fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX);
+	for (direction = READ; direction <= WRITE; direction++) {
+		struct channel *ch = privptr->channel[direction];
+		fsm_event(ch->fsm, CH_EVENT_START, ch);
+	}
+}
+
+/**
+ * Shutdown channels by sending CH_EVENT_STOP to each channel.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_stop(fsm_instance * fi, int event, void *arg)
+{
+	struct net_device *dev = (struct net_device *) arg;
+	struct ctc_priv *privptr = dev->priv;
+	int direction;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
+	for (direction = READ; direction <= WRITE; direction++) {
+		struct channel *ch = privptr->channel[direction];
+		fsm_event(ch->fsm, CH_EVENT_STOP, ch);
+	}
+}
+static void 
+dev_action_restart(fsm_instance *fi, int event, void *arg)
+{
+	struct net_device *dev = (struct net_device *)arg;
+	struct ctc_priv *privptr = dev->priv;
+	
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	ctc_pr_debug("%s: Restarting\n", dev->name);
+	dev_action_stop(fi, event, arg);
+	fsm_event(privptr->fsm, DEV_EVENT_STOP, dev);
+	fsm_addtimer(&privptr->restart_timer, CTC_TIMEOUT_5SEC,
+		     DEV_EVENT_START, dev);
+}
+
+/**
+ * Called from channel statemachine
+ * when a channel is up and running.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_chup(fsm_instance * fi, int event, void *arg)
+{
+	struct net_device *dev = (struct net_device *) arg;
+	struct ctc_priv *privptr = dev->priv;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	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);
+				ctc_pr_info("%s: connected with remote side\n",
+					    dev->name);
+				if (privptr->protocol == CTC_PROTO_LINUX_TTY)
+					ctc_tty_setcarrier(dev, 1);
+				ctc_clear_busy(dev);
+			}
+			break;
+		case DEV_STATE_STARTWAIT_TX:
+			if (event == DEV_EVENT_TXUP) {
+				fsm_newstate(fi, DEV_STATE_RUNNING);
+				ctc_pr_info("%s: connected with remote side\n",
+					    dev->name);
+				if (privptr->protocol == CTC_PROTO_LINUX_TTY)
+					ctc_tty_setcarrier(dev, 1);
+				ctc_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;
+	}
+}
+
+/**
+ * Called from channel statemachine
+ * when a channel has been shutdown.
+ *
+ * @param fi    An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg   Generic pointer, casted from struct net_device * upon call.
+ */
+static void
+dev_action_chdown(fsm_instance * fi, int event, void *arg)
+{
+	struct net_device *dev = (struct net_device *) arg;
+	struct ctc_priv *privptr = dev->priv;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	switch (fsm_getstate(fi)) {
+		case DEV_STATE_RUNNING:
+			if (privptr->protocol == CTC_PROTO_LINUX_TTY)
+				ctc_tty_setcarrier(dev, 0);
+			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;
+	}
+}
+
+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 ctc_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 ctc_tx().
+ *
+ * @return 0 on success, -ERRNO on failure. (Never fails.)
+ */
+static int
+transmit_skb(struct channel *ch, struct sk_buff *skb)
+{
+	unsigned long saveflags;
+	struct ll_header header;
+	int rc = 0;
+
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	if (fsm_getstate(ch->fsm) != CH_STATE_TXIDLE) {
+		int l = skb->len + LL_HEADER_LENGTH;
+
+		spin_lock_irqsave(&ch->collect_lock, saveflags);
+		if (ch->collect_len + l > ch->max_bufsize - 2)
+			rc = -EBUSY;
+		else {
+			atomic_inc(&skb->users);
+			header.length = l;
+			header.type = skb->protocol;
+			header.unused = 0;
+			memcpy(skb_push(skb, LL_HEADER_LENGTH), &header,
+			       LL_HEADER_LENGTH);
+			skb_queue_tail(&ch->collect_queue, skb);
+			ch->collect_len += l;
+		}
+		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);
+		ch->prof.txlen += skb->len;
+		header.length = skb->len + LL_HEADER_LENGTH;
+		header.type = skb->protocol;
+		header.unused = 0;
+		memcpy(skb_push(skb, LL_HEADER_LENGTH), &header,
+		       LL_HEADER_LENGTH);
+		block_len = skb->len + 2;
+		*((__u16 *) skb_push(skb, 2)) = block_len;
+
+		/**
+		 * IDAL support in CTC is broken, so we have to
+		 * care about skb's above 2G ourselves.
+		 */
+		hi = ((unsigned long) skb->tail + LL_HEADER_LENGTH) >> 31;
+		if (hi) {
+			nskb = alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA);
+			if (!nskb) {
+				atomic_dec(&skb->users);
+				skb_pull(skb, LL_HEADER_LENGTH + 2);
+				return -ENOMEM;
+			} else {
+				memcpy(skb_put(nskb, skb->len),
+				       skb->data, skb->len);
+				atomic_inc(&nskb->users);
+				atomic_dec(&skb->users);
+				dev_kfree_skb_irq(skb);
+				skb = nskb;
+			}
+		}
+
+		ch->ccw[4].count = block_len;
+		if (set_normalized_cda(&ch->ccw[4], skb->data)) {
+			/**
+			 * idal allocation failed, try via copying to
+			 * trans_skb. trans_skb usually has a pre-allocated
+			 * idal.
+			 */
+			if (ctc_checkalloc_buffer(ch, 1)) {
+				/**
+				 * Remove our header. It gets added
+				 * again on retransmit.
+				 */
+				atomic_dec(&skb->users);
+				skb_pull(skb, LL_HEADER_LENGTH + 2);
+				return -EBUSY;
+			}
+
+			ch->trans_skb->tail = ch->trans_skb->data;
+			ch->trans_skb->len = 0;
+			ch->ccw[1].count = skb->len;
+			memcpy(skb_put(ch->trans_skb, skb->len), skb->data,
+			       skb->len);
+			atomic_dec(&skb->users);
+			dev_kfree_skb_irq(skb);
+			ccw_idx = 0;
+		} 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);
+		spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags);
+		ch->prof.send_stamp = xtime;
+		rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx],
+				      (unsigned long) ch, 0xff, 0);
+		spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags);
+		if (ccw_idx == 3)
+			ch->prof.doios_single++;
+		if (rc != 0) {
+			fsm_deltimer(&ch->timer);
+			ccw_check_return_code(ch, rc, "single skb TX");
+			if (ccw_idx == 3)
+				skb_dequeue_tail(&ch->io_queue);
+			/**
+			 * Remove our header. It gets added
+			 * again on retransmit.
+			 */
+			skb_pull(skb, LL_HEADER_LENGTH + 2);
+		} else {
+			if (ccw_idx == 0) {
+				struct net_device *dev = ch->netdev;
+				struct ctc_priv *privptr = dev->priv;
+				privptr->stats.tx_packets++;
+				privptr->stats.tx_bytes +=
+				    skb->len - LL_HEADER_LENGTH;
+			}
+		}
+	}
+
+	return rc;
+}
+
+/**
+ * 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
+ctc_open(struct net_device * dev)
+{
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_START, dev);
+	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
+ctc_close(struct net_device * dev)
+{
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	fsm_event(((struct ctc_priv *) dev->priv)->fsm, DEV_EVENT_STOP, dev);
+	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
+ctc_tx(struct sk_buff *skb, struct net_device * dev)
+{
+	int rc = 0;
+	struct ctc_priv *privptr = (struct ctc_priv *) dev->priv;
+
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	/**
+	 * Some sanity checks ...
+	 */
+	if (skb == NULL) {
+		ctc_pr_warn("%s: NULL sk_buff passed\n", dev->name);
+		privptr->stats.tx_dropped++;
+		return 0;
+	}
+	if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) {
+		ctc_pr_warn("%s: Got sk_buff with head room < %ld bytes\n",
+			    dev->name, LL_HEADER_LENGTH + 2);
+		dev_kfree_skb(skb);
+		privptr->stats.tx_dropped++;
+		return 0;
+	}
+
+	/**
+	 * If channels are not running, try to restart them
+	 * and throw away packet. 
+	 */
+	if (fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) {
+		fsm_event(privptr->fsm, DEV_EVENT_START, dev);
+		if (privptr->protocol == CTC_PROTO_LINUX_TTY)
+			return -EBUSY;
+		dev_kfree_skb(skb);
+		privptr->stats.tx_dropped++;
+		privptr->stats.tx_errors++;
+		privptr->stats.tx_carrier_errors++;
+		return 0;
+	}
+
+	if (ctc_test_and_set_busy(dev))
+		return -EBUSY;
+
+	dev->trans_start = jiffies;
+	if (transmit_skb(privptr->channel[WRITE], skb) != 0)
+		rc = 1;
+	ctc_clear_busy(dev);
+	return rc;
+}
+
+/**
+ * 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
+ctc_change_mtu(struct net_device * dev, int new_mtu)
+{
+	struct ctc_priv *privptr = (struct ctc_priv *) dev->priv;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	if ((new_mtu < 576) || (new_mtu > 65527) ||
+	    (new_mtu > (privptr->channel[READ]->max_bufsize -
+			LL_HEADER_LENGTH - 2)))
+		return -EINVAL;
+	dev->mtu = new_mtu;
+	dev->hard_header_len = LL_HEADER_LENGTH + 2;
+	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 *
+ctc_stats(struct net_device * dev)
+{
+	return &((struct ctc_priv *) dev->priv)->stats;
+}
+
+/*
+ * sysfs attributes
+ */
+static ssize_t
+buffer_show(struct device *dev, char *buf)
+{
+	struct ctc_priv *priv;
+
+	priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+	return sprintf(buf, "%d\n",
+			priv->buffer_size);
+}
+
+static ssize_t
+buffer_write(struct device *dev, const char *buf, size_t count)
+{
+	struct ctc_priv *priv;
+	struct net_device *ndev;
+	int bs1;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+	ndev = priv->channel[READ]->netdev;
+	if (!ndev)
+		return -ENODEV;
+	sscanf(buf, "%u", &bs1);
+
+	if (bs1 > CTC_BUFSIZE_LIMIT)
+		return -EINVAL;
+	if ((ndev->flags & IFF_RUNNING) &&
+	    (bs1 < (ndev->mtu + LL_HEADER_LENGTH + 2)))
+		return -EINVAL;
+	if (bs1 < (576 + LL_HEADER_LENGTH + 2))
+		return -EINVAL;
+
+	priv->buffer_size = bs1;
+	priv->channel[READ]->max_bufsize =
+	    priv->channel[WRITE]->max_bufsize = bs1;
+	if (!(ndev->flags & IFF_RUNNING))
+		ndev->mtu = bs1 - LL_HEADER_LENGTH - 2;
+	priv->channel[READ]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED;
+	priv->channel[WRITE]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED;
+
+	return count;
+
+}
+
+static ssize_t
+loglevel_show(struct device *dev, char *buf)
+{
+	struct ctc_priv *priv;
+
+	priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+	return sprintf(buf, "%d\n", loglevel);
+}
+
+static ssize_t
+loglevel_write(struct device *dev, const char *buf, size_t count)
+{
+	struct ctc_priv *priv;
+	int ll1;
+
+	DBF_TEXT(trace, 5, __FUNCTION__);
+	priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+	sscanf(buf, "%i", &ll1);
+
+	if ((ll1 > CTC_LOGLEVEL_MAX) || (ll1 < 0))
+		return -EINVAL;
+	loglevel = ll1;
+	return count;
+}
+
+static void
+ctc_print_statistics(struct ctc_priv *priv)
+{
+	char *sbuf;
+	char *p;
+
+	DBF_TEXT(trace, 4, __FUNCTION__);
+	if (!priv)
+		return;
+	sbuf = (char *)kmalloc(2048, GFP_KERNEL);
+	if (sbuf == NULL)
+		return;
+	p = sbuf;
+
+	p += sprintf(p, "  Device FSM state: %s\n",
+		     fsm_getstate_str(priv->fsm));
+	p += sprintf(p, "  RX channel FSM state: %s\n",
+		     fsm_getstate_str(priv->channel[READ]->fsm));
+	p += sprintf(p, "  TX channel FSM state: %s\n",
+		     fsm_getstate_str(priv->channel[WRITE]->fsm));
+	p += sprintf(p, "  Max. TX buffer used: %ld\n",
+		     priv->channel[WRITE]->prof.maxmulti);
+	p += sprintf(p, "  Max. chained SKBs: %ld\n",
+		     priv->channel[WRITE]->prof.maxcqueue);
+	p += sprintf(p, "  TX single write ops: %ld\n",
+		     priv->channel[WRITE]->prof.doios_single);
+	p += sprintf(p, "  TX multi write ops: %ld\n",
+		     priv->channel[WRITE]->prof.doios_multi);
+	p += sprintf(p, "  Netto bytes written: %ld\n",
+		     priv->channel[WRITE]->prof.txlen);
+	p += sprintf(p, "  Max. TX IO-time: %ld\n",
+		     priv->channel[WRITE]->prof.tx_time);
+
+	ctc_pr_debug("Statistics for %s:\n%s",
+		     priv->channel[WRITE]->netdev->name, sbuf);
+	kfree(sbuf);
+	return;
+}
+
+static ssize_t
+stats_show(struct device *dev, char *buf)
+{
+	struct ctc_priv *priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+	ctc_print_statistics(priv);
+	return sprintf(buf, "0\n");
+}
+
+static ssize_t
+stats_write(struct device *dev, const char *buf, size_t count)
+{
+	struct ctc_priv *priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+	/* Reset statistics */
+	memset(&priv->channel[WRITE]->prof, 0,
+			sizeof(priv->channel[WRITE]->prof));
+	return count;
+}
+
+static DEVICE_ATTR(buffer, 0644, buffer_show, buffer_write);
+static DEVICE_ATTR(loglevel, 0644, loglevel_show, loglevel_write);
+static DEVICE_ATTR(stats, 0644, stats_show, stats_write);
+
+static int
+ctc_add_attributes(struct device *dev)
+{
+//	device_create_file(dev, &dev_attr_buffer);
+	device_create_file(dev, &dev_attr_loglevel);
+	device_create_file(dev, &dev_attr_stats);
+	return 0;
+}
+
+static void
+ctc_remove_attributes(struct device *dev)
+{
+	device_remove_file(dev, &dev_attr_stats);
+	device_remove_file(dev, &dev_attr_loglevel);
+//	device_remove_file(dev, &dev_attr_buffer);
+}
+
+
+static void
+ctc_netdev_unregister(struct net_device * dev)
+{
+	struct ctc_priv *privptr;
+
+	if (!dev)
+		return;
+	privptr = (struct ctc_priv *) dev->priv;
+	if (privptr->protocol != CTC_PROTO_LINUX_TTY)
+		unregister_netdev(dev);
+	else
+		ctc_tty_unregister_netdev(dev);
+}
+
+static int
+ctc_netdev_register(struct net_device * dev)
+{
+	struct ctc_priv *privptr = (struct ctc_priv *) dev->priv;
+	if (privptr->protocol != CTC_PROTO_LINUX_TTY)
+		return register_netdev(dev);
+	else
+		return ctc_tty_register_netdev(dev);
+}
+
+static void
+ctc_free_netdevice(struct net_device * dev, int free_dev)
+{
+	struct ctc_priv *privptr;
+	if (!dev)
+		return;
+	privptr = dev->priv;
+	if (privptr) {
+		if (privptr->fsm)
+			kfree_fsm(privptr->fsm);
+		kfree(privptr);
+	}
+#ifdef MODULE
+	if (free_dev)
+		free_netdev(dev);
+#endif
+}
+
+/**
+ * Initialize everything of the net device except the name and the
+ * channel structs.
+ */
+static struct net_device *
+ctc_init_netdevice(struct net_device * dev, int alloc_device, 
+		   struct ctc_priv *privptr)
+{
+	if (!privptr)
+		return NULL;
+
+	DBF_TEXT(setup, 3, __FUNCTION__);
+	if (alloc_device) {
+		dev = kmalloc(sizeof (struct net_device), GFP_KERNEL);
+		if (!dev)
+			return NULL;
+		memset(dev, 0, sizeof (struct net_device));
+	}
+
+	dev->priv = privptr;
+	privptr->fsm = init_fsm("ctcdev", dev_state_names,
+				dev_event_names, NR_DEV_STATES, NR_DEV_EVENTS,
+				dev_fsm, DEV_FSM_LEN, GFP_KERNEL);
+	if (privptr->fsm == NULL) {
+		if (alloc_device)
+			kfree(dev);
+		return NULL;
+	}
+	fsm_newstate(privptr->fsm, DEV_STATE_STOPPED);
+	fsm_settimer(privptr->fsm, &privptr->restart_timer);
+	if (dev->mtu == 0)
+		dev->mtu = CTC_BUFSIZE_DEFAULT - LL_HEADER_LENGTH - 2;
+	dev->hard_start_xmit = ctc_tx;
+	dev->open = ctc_open;
+	dev->stop = ctc_close;
+	dev->get_stats = ctc_stats;
+	dev->change_mtu = ctc_change_mtu;
+	dev->hard_header_len = LL_HEADER_LENGTH + 2;
+	dev->addr_len = 0;
+	dev->type = ARPHRD_SLIP;
+	dev->tx_queue_len = 100;
+	dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+	SET_MODULE_OWNER(dev);
+	return dev;
+}
+
+static ssize_t
+ctc_proto_show(struct device *dev, char *buf)
+{
+	struct ctc_priv *priv;
+
+	priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+
+	return sprintf(buf, "%d\n", priv->protocol);
+}
+
+static ssize_t
+ctc_proto_store(struct device *dev, const char *buf, size_t count)
+{
+	struct ctc_priv *priv;
+	int value;
+
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	pr_debug("%s() called\n", __FUNCTION__);
+
+	priv = dev->driver_data;
+	if (!priv)
+		return -ENODEV;
+	sscanf(buf, "%u", &value);
+	if ((value < 0) || (value > CTC_PROTO_MAX))
+		return -EINVAL;
+	priv->protocol = value;
+
+	return count;
+}
+
+static DEVICE_ATTR(protocol, 0644, ctc_proto_show, ctc_proto_store);
+
+static ssize_t
+ctc_type_show(struct device *dev, char *buf)
+{
+	struct ccwgroup_device *cgdev;
+
+	cgdev = to_ccwgroupdev(dev);
+	if (!cgdev)
+		return -ENODEV;
+
+	return sprintf(buf, "%s\n", cu3088_type[cgdev->cdev[0]->id.driver_info]);
+}
+
+static DEVICE_ATTR(type, 0444, ctc_type_show, NULL);
+
+static struct attribute *ctc_attr[] = {
+	&dev_attr_protocol.attr,
+	&dev_attr_type.attr,
+	&dev_attr_buffer.attr,
+	NULL,
+};
+
+static struct attribute_group ctc_attr_group = {
+	.attrs = ctc_attr,
+};
+
+static int
+ctc_add_files(struct device *dev)
+{
+	pr_debug("%s() called\n", __FUNCTION__);
+
+	return sysfs_create_group(&dev->kobj, &ctc_attr_group);
+}
+
+static void
+ctc_remove_files(struct device *dev)
+{
+	pr_debug("%s() called\n", __FUNCTION__);
+
+	sysfs_remove_group(&dev->kobj, &ctc_attr_group);
+}
+
+/**
+ * Add ctc specific attributes.
+ * Add ctc private data.
+ * 
+ * @param cgdev pointer to ccwgroup_device just added
+ *
+ * @returns 0 on success, !0 on failure.
+ */
+
+static int
+ctc_probe_device(struct ccwgroup_device *cgdev)
+{
+	struct ctc_priv *priv;
+	int rc;
+
+	pr_debug("%s() called\n", __FUNCTION__);
+	DBF_TEXT(trace, 3, __FUNCTION__);
+
+	if (!get_device(&cgdev->dev))
+		return -ENODEV;
+
+	priv = kmalloc(sizeof (struct ctc_priv), GFP_KERNEL);
+	if (!priv) {
+		ctc_pr_err("%s: Out of memory\n", __func__);
+		put_device(&cgdev->dev);
+		return -ENOMEM;
+	}
+
+	memset(priv, 0, sizeof (struct ctc_priv));
+	rc = ctc_add_files(&cgdev->dev);
+	if (rc) {
+		kfree(priv);
+		put_device(&cgdev->dev);
+		return rc;
+	}
+	priv->buffer_size = CTC_BUFSIZE_DEFAULT;
+	cgdev->cdev[0]->handler = ctc_irq_handler;
+	cgdev->cdev[1]->handler = ctc_irq_handler;
+	cgdev->dev.driver_data = priv;
+
+	return 0;
+}
+
+/**
+ *
+ * Setup an interface.
+ *
+ * @param cgdev  Device to be setup.
+ *
+ * @returns 0 on success, !0 on failure.
+ */
+static int
+ctc_new_device(struct ccwgroup_device *cgdev)
+{
+	char read_id[CTC_ID_SIZE];
+	char write_id[CTC_ID_SIZE];
+	int direction;
+	enum channel_types type;
+	struct ctc_priv *privptr;
+	struct net_device *dev;
+	int ret;
+
+	pr_debug("%s() called\n", __FUNCTION__);
+	DBF_TEXT(setup, 3, __FUNCTION__);
+
+	privptr = cgdev->dev.driver_data;
+	if (!privptr)
+		return -ENODEV;
+
+	type = get_channel_type(&cgdev->cdev[0]->id);
+	
+	snprintf(read_id, CTC_ID_SIZE, "ch-%s", cgdev->cdev[0]->dev.bus_id);
+	snprintf(write_id, CTC_ID_SIZE, "ch-%s", cgdev->cdev[1]->dev.bus_id);
+
+	if (add_channel(cgdev->cdev[0], type))
+		return -ENOMEM;
+	if (add_channel(cgdev->cdev[1], type))
+		return -ENOMEM;
+
+	ret = ccw_device_set_online(cgdev->cdev[0]);
+	if (ret != 0) {
+			printk(KERN_WARNING
+		 	"ccw_device_set_online (cdev[0]) failed with ret = %d\n", ret);
+	}
+
+	ret = ccw_device_set_online(cgdev->cdev[1]);
+	if (ret != 0) {
+			printk(KERN_WARNING
+		 	"ccw_device_set_online (cdev[1]) failed with ret = %d\n", ret);
+	}
+
+	dev = ctc_init_netdevice(NULL, 1, privptr);
+
+	if (!dev) {
+		ctc_pr_warn("ctc_init_netdevice failed\n");
+		goto out;
+	}
+
+	if (privptr->protocol == CTC_PROTO_LINUX_TTY)
+		strlcpy(dev->name, "ctctty%d", IFNAMSIZ);
+	else
+		strlcpy(dev->name, "ctc%d", IFNAMSIZ);
+
+	for (direction = READ; direction <= WRITE; direction++) {
+		privptr->channel[direction] =
+		    channel_get(type, direction == READ ? read_id : write_id,
+				direction);
+		if (privptr->channel[direction] == NULL) {
+			if (direction == WRITE) 
+				channel_free(privptr->channel[READ]);
+
+			ctc_free_netdevice(dev, 1);
+			goto out;
+		}
+		privptr->channel[direction]->netdev = dev;
+		privptr->channel[direction]->protocol = privptr->protocol;
+		privptr->channel[direction]->max_bufsize = privptr->buffer_size;
+	}
+	/* sysfs magic */
+	SET_NETDEV_DEV(dev, &cgdev->dev);
+
+	if (ctc_netdev_register(dev) != 0) {
+		ctc_free_netdevice(dev, 1);
+		goto out;
+	}
+
+	ctc_add_attributes(&cgdev->dev);
+
+	strlcpy(privptr->fsm->name, dev->name, sizeof (privptr->fsm->name));
+
+	print_banner();
+
+	ctc_pr_info("%s: read: %s, write: %s, proto: %d\n",
+		    dev->name, privptr->channel[READ]->id,
+		    privptr->channel[WRITE]->id, privptr->protocol);
+
+	return 0;
+out:
+	ccw_device_set_offline(cgdev->cdev[1]);
+	ccw_device_set_offline(cgdev->cdev[0]);
+
+	return -ENODEV;
+}
+
+/**
+ * Shutdown an interface.
+ *
+ * @param cgdev  Device to be shut down.
+ *
+ * @returns 0 on success, !0 on failure.
+ */
+static int
+ctc_shutdown_device(struct ccwgroup_device *cgdev)
+{
+	struct ctc_priv *priv;
+	struct net_device *ndev;
+		
+	DBF_TEXT(trace, 3, __FUNCTION__);
+	pr_debug("%s() called\n", __FUNCTION__);
+
+	priv = cgdev->dev.driver_data;
+	ndev = NULL;
+	if (!priv)
+		return -ENODEV;
+
+	if (priv->channel[READ]) {
+		ndev = priv->channel[READ]->netdev;
+
+		/* Close the device */
+		ctc_close(ndev);
+		ndev->flags &=~IFF_RUNNING;
+
+		ctc_remove_attributes(&cgdev->dev);
+
+		channel_free(priv->channel[READ]);
+	}
+	if (priv->channel[WRITE])
+		channel_free(priv->channel[WRITE]);
+
+	if (ndev) {
+		ctc_netdev_unregister(ndev);
+		ndev->priv = NULL;
+		ctc_free_netdevice(ndev, 1);
+	}
+
+	if (priv->fsm)
+		kfree_fsm(priv->fsm);
+
+	ccw_device_set_offline(cgdev->cdev[1]);
+	ccw_device_set_offline(cgdev->cdev[0]);
+
+	if (priv->channel[READ])
+		channel_remove(priv->channel[READ]);
+	if (priv->channel[WRITE])
+		channel_remove(priv->channel[WRITE]);
+	
+	priv->channel[READ] = priv->channel[WRITE] = NULL;
+
+	return 0;
+
+}
+
+static void
+ctc_remove_device(struct ccwgroup_device *cgdev)
+{
+	struct ctc_priv *priv;
+
+	pr_debug("%s() called\n", __FUNCTION__);
+	DBF_TEXT(trace, 3, __FUNCTION__);
+
+	priv = cgdev->dev.driver_data;
+	if (!priv)
+		return;
+	if (cgdev->state == CCWGROUP_ONLINE)
+		ctc_shutdown_device(cgdev);
+	ctc_remove_files(&cgdev->dev);
+	cgdev->dev.driver_data = NULL;
+	kfree(priv);
+	put_device(&cgdev->dev);
+}
+
+static struct ccwgroup_driver ctc_group_driver = {
+	.owner       = THIS_MODULE,
+	.name        = "ctc",
+	.max_slaves  = 2,
+	.driver_id   = 0xC3E3C3,
+	.probe       = ctc_probe_device,
+	.remove      = ctc_remove_device,
+	.set_online  = ctc_new_device,
+	.set_offline = ctc_shutdown_device,
+};
+
+/**
+ * Module related routines
+ *****************************************************************************/
+
+/**
+ * Prepare to be unloaded. Free IRQ's and release all resources.
+ * This is called just before this module is unloaded. It is
+ * <em>not</em> called, if the usage count is !0, so we don't need to check
+ * for that.
+ */
+static void __exit
+ctc_exit(void)
+{
+	unregister_cu3088_discipline(&ctc_group_driver);
+	ctc_tty_cleanup();
+	ctc_unregister_dbf_views();
+	ctc_pr_info("CTC driver unloaded\n");
+}
+
+/**
+ * Initialize module.
+ * This is called just after the module is loaded.
+ *
+ * @return 0 on success, !0 on error.
+ */
+static int __init
+ctc_init(void)
+{
+	int ret = 0;
+
+	print_banner();
+
+	ret = ctc_register_dbf_views();
+	if (ret){
+		ctc_pr_crit("ctc_init failed with ctc_register_dbf_views rc = %d\n", ret);
+		return ret;
+	}
+	ctc_tty_init();
+	ret = register_cu3088_discipline(&ctc_group_driver);
+	if (ret) {
+		ctc_tty_cleanup();
+		ctc_unregister_dbf_views();
+	}
+	return ret;
+}
+
+module_init(ctc_init);
+module_exit(ctc_exit);
+
+/* --- This is the END my friend --- */