patch-2.4.8 linux/drivers/s390/net/ctcmain.c

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

diff -u --recursive --new-file v2.4.7/linux/drivers/s390/net/ctcmain.c linux/drivers/s390/net/ctcmain.c
@@ -1,5 +1,5 @@
 /*
- * $Id: ctcmain.c,v 1.17 2001/01/23 14:23:51 felfert Exp $
+ * $Id: ctcmain.c,v 1.46 2001/07/05 17:36:41 felfert Exp $
  *
  * CTC / ESCON network driver
  *
@@ -35,70 +35,15 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  *
- * $Log: ctcmain.c,v $
- * Revision 1.17  2001/01/23 14:23:51  felfert
- * Added ctc based tty.
+ * RELEASE-TAG: CTC/ESCON network driver $Revision: 1.46 $
  *
- * Revision 1.16  2001/01/18 16:10:53  felfert
- * Added fixes by acme@conectiva.com.br.
- *
- * Revision 1.15  2001/01/12 15:40:11  felfert
- * Fixed ITPM# PL030052IME (Unitchecks when using real escon).
- *
- * Revision 1.14  2001/01/11 17:43:52  felfert
- * Fixed ITPM# PL030051IME (Initialization of escon).
- *
- * Revision 1.13  2001/01/11 16:40:26  smolinsk
- * resolved name space conflict with LVM and renamed
- *  dev_info_t to s390_dev_info_t
- * worked around a bug in OSA microcode by stepping back to 2k IDALS in idals.c
- *
- * Revision 1.12  2000/12/27 09:40:45  tonn
- * upgrade to test12
- *
- * Revision 1.11  2000/12/15 19:34:54  bird
- * struct ctc_priv_t: set type of tbusy to "unsigned long"
- *
- * Revision 1.10  2000/12/14 16:49:50  bird
- * ch_action_txretry(): added missing clear_normalized_cda()
- *
- * Revision 1.9  2000/12/14 13:56:53  felfert
- * Eliminated a compiler warning when building in old kernel.
- *
- * Revision 1.8  2000/12/14 13:11:59  felfert
- * static ccws now separately allocated.
- * remove locally allocated ccw for setup.
- *
- * Revision 1.7  2000/12/14 03:32:15  bird
- * Fixes for >2GB memory.   Switch on checksumming.
- *
- * Revision 1.6  2000/12/07 20:08:30  felfert
- * Modified RX channel initialization to be compatible with VM TCP
- *
- * Revision 1.5  2000/12/07 18:15:05  felfert
- * Added workaround against VM TCP bug.
- * Fixed an error message.
- *
- * Revision 1.4  2000/12/06 16:55:57  felfert
- * Removed check for double call of ctc_setup().
- * ctc_setup can now handle mutiple calls.
- *
- * Revision 1.3  2000/12/06 16:48:44  felfert
- * New initialization.
- * Removed old cvs log from 2.2 kernel.
- *
- * Revision 1.2  2000/12/06 14:13:46  felfert
- * New unified configuration.
- *
- * Revision 1.1  2000/11/30 11:21:08  bird
- * Support for new ctc driver
  */
 
 #include <linux/version.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
-#include <linux/malloc.h>
+#include <linux/slab.h>
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/interrupt.h>
@@ -119,14 +64,29 @@
 #include <asm/io.h>
 #include <asm/bitops.h>
 #include <asm/uaccess.h>
+#ifdef CONFIG_CHANDEV
+#define CTC_CHANDEV
+#endif
 
-#define CTC_USE_IDALS 1
-#if CTC_USE_IDALS
+#ifdef CTC_CHANDEV
+#include <asm/chandev.h>
+#define REQUEST_IRQ chandev_request_irq
+#define FREE_IRQ chandev_free_irq
+#else
+#define REQUEST_IRQ request_irq
+#define FREE_IRQ free_irq
+#endif
+
+#if LINUX_VERSION_CODE >= 0x020213
 #  include <asm/idals.h>
 #else
 #  define set_normalized_cda(ccw, addr) ((ccw)->cda = (addr))
 #  define clear_normalized_cda(ccw)
 #endif
+#if LINUX_VERSION_CODE < 0x020400
+#  define s390_dev_info_t dev_info_t
+#  define dev_kfree_skb_irq(a) dev_kfree_skb(a)
+#endif
 
 #include <asm/irq.h>
 
@@ -136,12 +96,14 @@
 #ifdef MODULE
 MODULE_AUTHOR("(C) 2000 IBM Corp. by Fritz Elfert (felfert@millenux.com)");
 MODULE_DESCRIPTION("Linux for S/390 CTC/Escon Driver");
+#ifndef CTC_CHANDEV
 MODULE_PARM(ctc, "s");
 MODULE_PARM_DESC(ctc,
 "One or more definitions in the same format like the kernel param for ctc.\n"
 "E.g.: ctc0:0x700:0x701:0:ctc1:0x702:0x703:0\n");
 
 char *ctc = NULL;
+#endif
 #else
 /**
  * Number of devices in monolithic (not module) driver version.
@@ -162,7 +124,8 @@
 #define CTC_PROTO_S390          0
 #define CTC_PROTO_LINUX         1
 #define CTC_PROTO_LINUX_TTY     2
-#define CTC_PROTO_MAX           2
+#define CTC_PROTO_OS390         3
+#define CTC_PROTO_MAX           3
 
 #define CTC_BUFSIZE_LIMIT       65535
 #define CTC_BUFSIZE_DEFAULT     32768
@@ -206,13 +169,16 @@
 
 typedef enum channel_types channel_type_t;
 
+#ifndef CTC_CHANDEV
 static int ctc_no_auto = 0;
+#endif
 
 /**
  * If running on 64 bit, this must be changed. XXX Why? (bird)
  */
 typedef unsigned long intparm_t;
 
+#ifndef CTC_CHANDEV
 /**
  * Definition of a per device parameter block
  */
@@ -226,6 +192,7 @@
 } param;
 
 static param *params = NULL;
+#endif
 
 typedef struct {
 	unsigned long maxmulti;
@@ -246,7 +213,6 @@
 	 * Pointer to next channel in list.
 	 */
 	struct channel_t    *next;
-
 	__u16               devno;
 	int                 irq;
 
@@ -278,19 +244,14 @@
 	struct tq_struct    tq;
 
 	/**
-	 * RX/TX buffer for init sequence.
-	 */
-	__u16               dummy_buf;
-
-	/**
-	 * RX buffer size
+	 * RX/TX buffer size
 	 */
 	int                 max_bufsize;
 
 	/**
-	 * Receive buffer.
+	 * Transmit/Receive buffer.
 	 */
-	struct sk_buff      *rx_skb;
+	struct sk_buff      *trans_skb;
 
 	/**
 	 * Universal I/O queue.
@@ -313,16 +274,6 @@
 	spinlock_t          collect_lock;
 
 	/**
-	 * Pointer to dynamic allocated CCWs for TX
-	 */
-	ccw1_t              *dccw;
-
-	/**
-	 * Number of dynamic allocated CCWs needed for clearing IDALs.
-	 */
-	int                 dccw_count;
-
-	/**
 	 * Timer for detecting unresposive
 	 * I/O operations.
 	 */
@@ -347,9 +298,10 @@
 	ctc_profile         prof;
 } channel;
 
-#define CHANNEL_FLAGS_READ   0
-#define CHANNEL_FLAGS_WRITE  1
-#define CHANNEL_FLAGS_INUSE  2
+#define CHANNEL_FLAGS_READ            0
+#define CHANNEL_FLAGS_WRITE           1
+#define CHANNEL_FLAGS_INUSE           2
+#define CHANNEL_FLAGS_BUFSIZE_CHANGED 4
 #define CHANNEL_FLAGS_RWMASK 1
 #define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK)
 
@@ -358,6 +310,10 @@
  */
 static channel *channels = NULL;
 
+#ifdef CTC_CHANDEV
+static int activated;
+#endif
+
 typedef struct ctc_priv_t {
 	struct net_device_stats stats;
 #if LINUX_VERSION_CODE >= 0x02032D
@@ -375,6 +331,7 @@
 	struct proc_dir_entry   *proc_dentry;
 	struct proc_dir_entry   *proc_stat_entry;
 	struct proc_dir_entry   *proc_ctrl_entry;
+	int                     proc_registered;
 } ctc_priv;
 
 /**
@@ -425,7 +382,7 @@
  */
 static void print_banner(void) {
 	static int printed = 0;
-	char vbuf[] = "$Revision: 1.17 $";
+	char vbuf[] = "$Revision: 1.46 $";
 	char *version = vbuf;
 
 	if (printed)
@@ -440,6 +397,8 @@
 	printed = 1;
 }
 
+
+#ifndef CTC_CHANDEV
 /**
  * Return type of a detected device.
  */
@@ -502,6 +461,8 @@
 	}
 	return type;
 }
+#endif
+
 
 /**
  * States of the interface statemachine.
@@ -593,6 +554,12 @@
 	CH_EVENT_SC_UNKNOWN,
 
 	/**
+	 * Events, representing machine checks
+	 */
+	CH_EVENT_MC_FAIL,
+	CH_EVENT_MC_GOOD,
+
+	/**
 	 * Event, representing normal IRQ
 	 */
 	CH_EVENT_IRQ,
@@ -637,6 +604,9 @@
 
 	"SubChannel check Unknown",
 
+	"Machine check failure",
+	"Machine check operational",
+
 	"IRQ normal",
 	"IRQ final",
 
@@ -673,6 +643,7 @@
 	CH_STATE_TXERR,
 	CH_STATE_TERM,
 	CH_STATE_DTERM,
+	CH_STATE_NOTOP,
 
 	/**
 	 * MUST be always the last element!!
@@ -696,9 +667,11 @@
 	"TX error",
 	"Terminating",
 	"Restarting",
+	"Not operational",
 };
 
 
+#ifdef DEBUG
 /**
  * Dump header and first 16 bytes of an sk_buff for debugging purposes.
  *
@@ -729,114 +702,120 @@
 		bl = 16;
 	printk(KERN_DEBUG "data: ");
 	for (i = 0; i < bl; i++)
-		printk("%02x ", *p++);
+		printk("%02x%s", *p++, (i % 16) ? " " : "\n<7>");
 	printk("\n");
 }
+#endif
 
 /**
- * Bottom half routine.
+ * Unpack a just received skb and hand it over to
+ * upper layers.
  *
- * @param ch The channel to work on.
+ * @param ch The channel where this skb has been received.
+ * @param pskb The received skb.
  */
-static void ctc_bh(channel *ch)
+static __inline__ void ctc_unpack_skb(channel *ch, struct sk_buff *pskb)
 {
 	net_device     *dev = ch->netdev;
 	ctc_priv       *privptr = (ctc_priv *)dev->priv;
-	struct sk_buff *skb;
 
-	while ((skb = skb_dequeue(&ch->io_queue))) {
-		__u16 len = *((__u16*)skb->data);
+	__u16 len = *((__u16*)pskb->data);
+	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;
+		ll_header *header = (ll_header *)pskb->data;
 
-		skb_put(skb, 2 + LL_HEADER_LENGTH);
-		skb_pull(skb, 2);
-		skb->dev = dev;
-		skb->ip_summed = CHECKSUM_NONE;
-		while (len > 0) {
-			ll_header *header = (ll_header *)skb->data;
-			skb_pull(skb, LL_HEADER_LENGTH);
-			if ((ch->protocol == CTC_PROTO_S390) &&
-			    (header->type != ETH_P_IP)) {
-				/**
-				 * Check packet type only if we stick strictly
-				 * to S/390's protocol of OS390. This only
-				 * supports IP. Otherwise allow any packet
-				 * type.
-				 */
-				printk(KERN_WARNING
-				       "%s Illegal packet type 0x%04x "
-				       "received, dropping\n",
-				       dev->name, header->type);
-				ctc_dump_skb(skb, -6);
-				privptr->stats.rx_dropped++;
-				privptr->stats.rx_frame_errors++;
-				dev_kfree_skb(skb);
-				goto again;
-			}
-			skb->protocol = ntohs(header->type);
-			header->length -= LL_HEADER_LENGTH;
-			if ((header->length > dev->mtu) ||
-			    (header->length == 0)) {
-				printk(KERN_WARNING
-				       "%s Illegal packet size %d "
-				       "received (MTU=%d), "
-				       "dropping\n", dev->name, header->length,
-				       dev->mtu);
-				ctc_dump_skb(skb, -6);
-				privptr->stats.rx_dropped++;
-				privptr->stats.rx_length_errors++;
-				dev_kfree_skb(skb);
-				goto again;
-			}
-			skb_put(skb, header->length);
-			skb->mac.raw = skb->data;
+		skb_pull(pskb, LL_HEADER_LENGTH);
+		if ((ch->protocol == CTC_PROTO_S390) &&
+		    (header->type != ETH_P_IP)) {
 			/**
-			 * Set truesize here to make the kernel's
-			 * socket layer happy. If this is not done,
-			 * the RX-routines of the socket code are dropping
-			 * most of the received packets, because they "think"
-			 * there isn't enough buffer space for the incoming
-			 * data.
+			 * Check packet type only if we stick strictly
+			 * to S/390's protocol of OS390. This only
+			 * supports IP. Otherwise allow any packet
+			 * type.
 			 */
-			skb->truesize = skb->len;
-			len -= (LL_HEADER_LENGTH + header->length);
-			if (len > 0) {
-				/**
-				 * Clone the skb only if there are still
-				 * sub-packets.
-				 */
-				struct sk_buff *skb2 =
-					skb_clone(skb, GFP_ATOMIC);
-				if (!skb2) {
-					printk(KERN_WARNING "%s Out of memory"
-					       " in ctc_bh\n",
-					       dev->name);
-					privptr->stats.rx_dropped++;
-					dev_kfree_skb(skb);
-					goto again;
-				}
-				if (ch->protocol == CTC_PROTO_LINUX_TTY)
-					ctc_tty_netif_rx(skb2);
-				else
-					netif_rx(skb2);
-				privptr->stats.rx_packets++;
-				privptr->stats.rx_bytes += skb2->len;
-				/**
-				 * Advance pointers to next sub-packet.
-				 */
-				skb_pull(skb, header->length);
-				skb_put(skb, LL_HEADER_LENGTH);
-			} else {
-				if (ch->protocol == CTC_PROTO_LINUX_TTY)
-					ctc_tty_netif_rx(skb);
-				else
-					netif_rx(skb);
-				privptr->stats.rx_packets++;
-				privptr->stats.rx_bytes += skb->len;
-			}
+			printk(KERN_WARNING
+			       "%s Illegal packet type 0x%04x "
+			       "received, dropping\n",
+			       dev->name, header->type);
+#ifdef DEBUG
+			ctc_dump_skb(pskb, -6);
+#endif
+			privptr->stats.rx_dropped++;
+			privptr->stats.rx_frame_errors++;
+			return;
+		}
+		pskb->protocol = ntohs(header->type);
+		header->length -= LL_HEADER_LENGTH;
+		if ((header->length == 0) ||
+		    (header->length > skb_tailroom(pskb))) {
+			printk(KERN_WARNING
+			       "%s Illegal packet size %d "
+			       "received (MTU=%d), "
+			       "dropping\n", dev->name, header->length,
+			       dev->mtu);
+#ifdef DEBUG
+			ctc_dump_skb(pskb, -6);
+#endif
+			privptr->stats.rx_dropped++;
+			privptr->stats.rx_length_errors++;
+			return;
+		}
+		if (header->length > skb_tailroom(pskb)) {
+			printk(KERN_WARNING
+			       "%s Illegal packet size %d "
+			       "(beyond the end of received data), "
+			       "dropping\n", dev->name, header->length);
+#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 -= (LL_HEADER_LENGTH + header->length);
+		skb = dev_alloc_skb(pskb->len);
+		if (!skb) {
+			printk(KERN_WARNING
+			       "%s Out of memory in ctc_unpack_skb\n",
+			       dev->name);
+			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(skb);
+		privptr->stats.rx_packets++;
+		privptr->stats.rx_bytes += skb->len;
+		if (len > 0) {
+			skb_pull(pskb, header->length);
+			skb_put(pskb, LL_HEADER_LENGTH);
 		}
-	again:
 	}
-	return;
+}
+
+/**
+ * Bottom half routine.
+ *
+ * @param ch The channel to work on.
+ */
+static void ctc_bh(channel *ch)
+{
+	struct sk_buff *skb;
+
+	while ((skb = skb_dequeue(&ch->io_queue)))
+		ctc_unpack_skb(ch, skb);
 }
 
 /**
@@ -883,9 +862,10 @@
 static void inline ccw_unit_check (channel *ch, unsigned char sense) {
 	if (sense & SNS0_INTERVENTION_REQ) {
 		if (sense & 0x01)  {
-			printk(KERN_DEBUG
-			       "ch-%04x: Interface disc. or Sel. reset "
-			       "(remote)\n", ch->devno);
+			if (ch->protocol != CTC_PROTO_LINUX_TTY)
+				printk(KERN_DEBUG
+				       "ch-%04x: Interface disc. or Sel. reset "
+				       "(remote)\n", ch->devno);
 			fsm_event(ch->fsm, CH_EVENT_UC_RCRESET, ch);
 		} else {
 			printk(KERN_DEBUG "ch-%04x: System reset (remote)\n",
@@ -917,7 +897,8 @@
 			fsm_event(ch->fsm, CH_EVENT_UC_TXPARITY, ch);
 		}
 	} else if (sense & SNS0_CMD_REJECT) {
-			printk(KERN_WARNING "ch-%04x: Command reject\n", ch->devno);
+			printk(KERN_WARNING "ch-%04x: Command reject\n",
+			       ch->devno);
 	} else if (sense == 0) {
 		printk(KERN_DEBUG "ch-%04x: Unit check ZERO\n", ch->devno);
 		fsm_event(ch->fsm, CH_EVENT_UC_ZERO, ch);
@@ -935,8 +916,42 @@
 
 	while ((skb = skb_dequeue(q))) {
 		atomic_dec(&skb->users);
-		dev_kfree_skb(skb);
+		dev_kfree_skb_irq(skb);
+	}
+}
+
+static __inline__ int ctc_checkalloc_buffer(channel *ch, int warn) {
+	if ((ch->trans_skb == NULL) ||
+	    (ch->flags & CHANNEL_FLAGS_BUFSIZE_CHANGED)) {
+		if (ch->trans_skb != NULL)
+			dev_kfree_skb(ch->trans_skb);
+		ch->trans_skb = dev_alloc_skb(ch->max_bufsize);
+		if (ch->trans_skb == NULL) {
+			if (warn)
+				printk(KERN_WARNING
+				       "ch-%04x: Couldn't alloc %s trans_skb\n",
+				       ch->devno,
+				       (CHANNEL_DIRECTION(ch->flags) == READ) ?
+				       "RX" : "TX");
+			return -ENOMEM;
+		}
+		set_normalized_cda(&ch->ccw[1],
+				   virt_to_phys(ch->trans_skb->data));
+		if (ch->ccw[1].cda == 0) {
+			dev_kfree_skb(ch->trans_skb);
+			ch->trans_skb = NULL;
+			if (warn)
+				printk(KERN_WARNING
+				       "ch-%04x: set_normalized_cda for %s "
+				       "trans_skb failed, dropping packets\n",
+				       ch->devno,
+				       (CHANNEL_DIRECTION(ch->flags) == READ) ?
+				       "RX" : "TX");
+			return -ENOMEM;
+		}
+		ch->flags &= ~CHANNEL_FLAGS_BUFSIZE_CHANGED;
 	}
+	return 0;
 }
 
 /**
@@ -988,73 +1003,47 @@
 			first = 0;
 		}
 		atomic_dec(&skb->users);
-		dev_kfree_skb(skb);
+		dev_kfree_skb_irq(skb);
 	}
 	spin_lock(&ch->collect_lock);
-	if (ch->dccw) {
-		for (i = 0; i < ch->dccw_count; i++)
-			clear_normalized_cda(&ch->dccw[i]);
-		kfree(ch->dccw);
-		ch->dccw = NULL;
-	}
+	clear_normalized_cda(&ch->ccw[4]);
 	if (ch->collect_len > 0) {
 		int rc;
 
-		ch->dccw_count = skb_queue_len(&ch->collect_queue) + 1;
-		if (ch->prof.maxmulti < (ch->collect_len + 2))
-			ch->prof.maxmulti = ch->collect_len + 2;
-		if (ch->prof.maxcqueue < ch->dccw_count)
-			ch->prof.maxcqueue = ch->dccw_count;
-
-		ch->dccw = kmalloc(ch->dccw_count *
-				   sizeof(ccw1_t), GFP_ATOMIC|GFP_DMA);
-		if (!ch->dccw) {
+		if (ctc_checkalloc_buffer(ch, 1)) {
 			spin_unlock(&ch->collect_lock);
-			printk(KERN_WARNING
-			       "%s: Unable to alloc dynamic ccws\n",
-			       dev->name);
 			return;
 		}
-
-		ch->dccw[0].cmd_code = CCW_CMD_PREPARE;
-		ch->dccw[0].flags    = CCW_FLAG_SLI | CCW_FLAG_CC;
-		ch->dccw[0].count    = 0;
-		ch->dccw[0].cda	     = 0;
-		i = 1;
+		ch->trans_skb->tail = 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);
+		ch->ccw[1].count = ch->collect_len + 2;
+		*((__u16 *)skb_put(ch->trans_skb, 2)) = ch->collect_len + 2;
+		i = 0;
 		while ((skb = skb_dequeue(&ch->collect_queue))) {
-			ch->prof.txlen += (skb->len - LL_HEADER_LENGTH);
-			if (i == 1)
-				*((__u16 *)skb_push(skb, 2)) =
-					ch->collect_len + 2;
-			ch->dccw[i].cmd_code = CCW_CMD_WRITE;
-			ch->dccw[i].flags    = CCW_FLAG_SLI |
-				((skb_queue_len(&ch->collect_queue))
-				 ? CCW_FLAG_DC : 0);
-			ch->dccw[i].count    = skb->len;
-			set_normalized_cda(&ch->dccw[i],
-					   virt_to_phys(skb->data));
-			skb_queue_tail(&ch->io_queue, skb);
+			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;
 		fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
 		ch->prof.send_stamp = xtime;
-		rc = do_IO(ch->irq, ch->dccw, (intparm_t)ch, 0xff, 0);
+#ifdef DEBUG
+	printk(KERN_DEBUG "ccw[1].cda = %08x\n", ch->ccw[1].cda);
+#endif
+		rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
 		ch->prof.doios_multi++;
 		if (rc != 0) {
+			privptr->stats.tx_dropped += i;
+			privptr->stats.tx_errors += i;
 			fsm_deltimer(&ch->timer);
-			i = 0;
-			while ((skb = skb_dequeue(&ch->io_queue))) {
-				privptr->stats.tx_dropped++;
-				privptr->stats.tx_errors++;
-				atomic_dec(&skb->users);
-				dev_kfree_skb(skb);
-				i++;
-			}
-			kfree(ch->dccw);
-			ch->dccw = NULL;
-			if (i != ch->dccw_count)
-				printk(KERN_WARNING "ctc: i != nccws !!!\n");
 			ccw_check_return_code(ch, rc);
 		}
 	} else
@@ -1096,10 +1085,10 @@
 	net_device     *dev = ch->netdev;
 	ctc_priv       *privptr = dev->priv;
 	int            len = ch->max_bufsize - ch->devstat->rescnt;
-	struct sk_buff *skb = ch->rx_skb;
+	struct sk_buff *skb = ch->trans_skb;
 	__u16          block_len = *((__u16*)skb->data);
 	char           *saved_data = skb->data;
-	int            queued = 0;
+	int            check_len;
 	int            rc;
 
 	fsm_deltimer(&ch->timer);
@@ -1117,17 +1106,26 @@
 		privptr->stats.rx_length_errors++;
 		goto again;
 	}
+
 	/**
 	 * VM TCP seems to have a bug sending 2 trailing bytes of garbage.
 	 */
-	if ((len < block_len) ||
-	    ((len > block_len) && (ch->protocol != CTC_PROTO_S390)) ||
-	    ((len > (block_len + 2)) && (ch->protocol == CTC_PROTO_S390))) {
-		printk(KERN_WARNING
-		       "%s: got block length %d != rx length %d\n", dev->name,
-		       block_len, len);
-		*((__u16*)skb->data) = len;
+	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)) {
+		printk(KERN_WARNING "%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;
@@ -1135,31 +1133,24 @@
 	block_len -= 2;
 	if (block_len > 0) {
 		*((__u16*)skb->data) = block_len;
-		skb_queue_tail(&ch->io_queue, skb);
-		queued++;
+		ctc_unpack_skb(ch, skb);
 	}
  again:
-	if (queued) {
-		queue_task(&ch->tq, &tq_immediate);
-		mark_bh(IMMEDIATE_BH);
-		ch->rx_skb = dev_alloc_skb(ch->max_bufsize);
-		if (ch->rx_skb == NULL) {
-			printk(KERN_WARNING "%s: Couldn't alloc rx_skb in "
-			       "ch_action_rx\n", dev->name);
-			/* TODO: retry after bottom half */
-			return;
-		}
-	} else {
-		skb->data = skb->tail = saved_data;
-		skb->len = 0;
-	}
+	skb->data = skb->tail = saved_data;
+	skb->len = 0;
+	if (ctc_checkalloc_buffer(ch, 1))
+		return;
 	ch->ccw[1].count = ch->max_bufsize;
-	set_normalized_cda(&ch->ccw[1], virt_to_phys(ch->rx_skb->data));
+#ifdef DEBUG
+	printk(KERN_DEBUG "ccw[1].cda = %08x\n", ch->ccw[1].cda);
+#endif
 	rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
 	if (rc != 0)
 		ccw_check_return_code(ch, rc);
 }
 
+static void ch_action_rxidle(fsm_instance *fi, int event, void *arg);
+
 /**
  * Initialize connection by sending a __u16 of value 0.
  *
@@ -1176,6 +1167,25 @@
 		printk(KERN_DEBUG "ch-%04x: remote side issued READ?, "
 		       "init ...\n", ch->devno);
 	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 {
+			net_device *dev = ch->netdev;
+			fsm_newstate(fi, CH_STATE_TXIDLE);
+			fsm_event(((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
@@ -1185,9 +1195,12 @@
 	    (ch->protocol != CTC_PROTO_S390))
 		fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
 
-	ch->dummy_buf    = CTC_INITIAL_BLOCKLEN;
+	*((__u16 *)ch->trans_skb->data) = CTC_INITIAL_BLOCKLEN;
 	ch->ccw[1].count = 2; /* Transfer only length */
-	set_normalized_cda(&ch->ccw[1], virt_to_phys(&ch->dummy_buf));
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "ccw[1].cda = %08x\n", ch->ccw[1].cda);
+#endif
 	fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ)
 		     ? CH_STATE_RXINIT : CH_STATE_TXINIT);
 	rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
@@ -1223,20 +1236,22 @@
 {
 	channel    *ch = (channel *)arg;
 	net_device *dev = ch->netdev;
+	__u16      buflen;
 	int        rc;
 
 	fsm_deltimer(&ch->timer);
-	if (ch->dummy_buf >= CTC_INITIAL_BLOCKLEN) {
-		ch->rx_skb = dev_alloc_skb(ch->max_bufsize);
-		if (ch->rx_skb == NULL) {
-			printk(KERN_WARNING "%s: Couldn't alloc rx_skb in "
-			       "ch_action_rxidle\n", dev->name);
+	buflen = *((__u16 *)ch->trans_skb->data);
+#ifdef DEBUG
+	printk(KERN_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;
-		set_normalized_cda(&ch->ccw[1],
-				   virt_to_phys(ch->rx_skb->data));
 		fsm_newstate(fi, CH_STATE_RXIDLE);
+#ifdef DEBUG
+	printk(KERN_DEBUG "ccw[1].cda = %08x\n", ch->ccw[1].cda);
+#endif
 		rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
 		if (rc != 0) {
 			fsm_newstate(fi, CH_STATE_RXINIT);
@@ -1246,7 +1261,7 @@
 				  DEV_EVENT_RXUP, dev);
 	} else {
 		printk(KERN_DEBUG "%s: Initial RX count %d not %d\n",
-		       dev->name, ch->dummy_buf, CTC_INITIAL_BLOCKLEN);
+		       dev->name, buflen, CTC_INITIAL_BLOCKLEN);
 		ch_action_firstio(fi, event, arg);
 	}
 }
@@ -1269,7 +1284,7 @@
 	fsm_newstate(fi, CH_STATE_SETUPWAIT);
 	if (event == CH_EVENT_TIMER)
 		s390irq_spin_lock_irqsave(ch->irq, saveflags);
-	rc = do_IO(ch->irq, &ch->ccw[3], (intparm_t)ch, 0xff, 0);
+	rc = do_IO(ch->irq, &ch->ccw[6], (intparm_t)ch, 0xff, 0);
 	if (event == CH_EVENT_TIMER)
 		s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
 	if (rc != 0) {
@@ -1303,15 +1318,30 @@
 		       ch->irq);
 		return;
 	}
-
- dev = ch->netdev;
+	dev = ch->netdev;
 
 #ifdef DEBUG
 	printk(KERN_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 (ctc_checkalloc_buffer(ch, 0))
+		printk(KERN_NOTICE
+		       "%s: Could not allocate %s trans_skb, delaying "
+		       "allocation until first transfer\n",
+		       dev->name, 
+		       (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+
+#if LINUX_VERSION_CODE >= 0x020400
 	INIT_LIST_HEAD(&ch->tq.list);
+#else
+	ch->tq.next = NULL;
+#endif
 	ch->tq.sync    = 0;
 	ch->tq.routine = (void *)(void *)ctc_bh;
 	ch->tq.data    = ch;
@@ -1324,17 +1354,18 @@
 		ch->ccw[1].cmd_code = CCW_CMD_READ;
 		ch->ccw[1].flags    = CCW_FLAG_SLI;
 		ch->ccw[1].count    = 0;
-		ch->ccw[1].cda	    = 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;
-		ch->ccw[1].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(ccw1_t) * 3);
+	ch->ccw[4].cda	    = 0;
+
 	fsm_newstate(fi, CH_STATE_STARTWAIT);
 	fsm_addtimer(&ch->timer, 1000, CH_EVENT_TIMER, ch);
 	s390irq_spin_lock_irqsave(ch->irq, saveflags);
@@ -1392,26 +1423,62 @@
 	channel *ch = (channel *)arg;
 	net_device *dev = ch->netdev;
 
-	if (ch->dccw) {
-		printk(KERN_WARNING "ch_action_stopped: dccw !NULL\n");
-		return;
-	}
 	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(((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(((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)
+{
+	channel *ch = (channel *)arg;
+	net_device *dev = ch->netdev;
+
+	fsm_deltimer(&ch->timer);
+	fsm_newstate(fi, CH_STATE_NOTOP);
 	if (CHANNEL_DIRECTION(ch->flags) == READ) {
 		skb_queue_purge(&ch->io_queue);
-		if (ch->rx_skb != NULL) {
-			dev_kfree_skb(ch->rx_skb);
-			ch->rx_skb = NULL;
-		}
 		fsm_event(((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);
-		if (ch->dccw)
-			kfree(ch->dccw);
-		ch->dccw = NULL;
 		ch->collect_len = 0;
 		spin_unlock(&ch->collect_lock);
 		fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
@@ -1629,20 +1696,25 @@
 		if ((skb = skb_peek(&ch->io_queue))) {
 			int rc = 0;
 
+			set_normalized_cda(&ch->ccw[4],
+					   virt_to_phys(skb->data));
+			if (ch->ccw[4].cda == 0) {
+				printk(KERN_DEBUG "%s: IDAL alloc failed, "
+				       "restarting channel\n", dev->name);
+				fsm_event(((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);
 			if (event == CH_EVENT_TIMER)
 				s390irq_spin_lock_irqsave(ch->irq, saveflags);
-			if (ch->dccw)
-				rc = do_IO(ch->irq, ch->dccw, (intparm_t)ch,
-					   0xff, 0);
-			else {
-				clear_normalized_cda(&ch->ccw[1]);
-				ch->ccw[1].count = skb->len;
-				set_normalized_cda(&ch->ccw[1],
-						   virt_to_phys(skb->data));
-				rc = do_IO(ch->irq, &ch->ccw[0],
-					   (intparm_t)ch, 0xff, 0);
-			}
+			ch->ccw[4].count = skb->len;
+#ifdef DEBUG
+	printk(KERN_DEBUG "ccw[4].cda = %08x\n", ch->ccw[4].cda);
+#endif
+			rc = do_IO(ch->irq, &ch->ccw[3],
+				   (intparm_t)ch, 0xff, 0);
 			if (event == CH_EVENT_TIMER)
 				s390irq_spin_unlock_irqrestore(ch->irq,
 							       saveflags);
@@ -1687,6 +1759,13 @@
 	{ 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       },
@@ -1694,10 +1773,12 @@
 	{ 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_iofatal    },
+	{ 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       },
@@ -1707,6 +1788,7 @@
 	{ 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_iofatal    },
+	{ 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       },
@@ -1718,6 +1800,7 @@
 	{ CH_STATE_RXINIT,     CH_EVENT_IO_ENODEV,  ch_action_iofatal    },
 	{ CH_STATE_RXINIT,     CH_EVENT_IO_EIO,     ch_action_iofatal    },
 	{ 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       },
@@ -1726,6 +1809,8 @@
 //	{ 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_iofatal    },
+	{ 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       },
@@ -1735,6 +1820,7 @@
 	{ 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_iofatal    },
+	{ 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       },
@@ -1743,18 +1829,21 @@
 	{ 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_iofatal    },
+	{ 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       },
@@ -1764,9 +1853,12 @@
 	{ 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_iofatal    },
+	{ 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);
@@ -1796,7 +1888,7 @@
 		return -1;
 	}
 	memset(ch, 0, sizeof(channel));
-	if ((ch->ccw = (ccw1_t *)kmalloc(sizeof(ccw1_t) * 5,
+	if ((ch->ccw = (ccw1_t *)kmalloc(sizeof(ccw1_t) * 8,
 					 GFP_KERNEL|GFP_DMA)) == NULL) {
 		kfree(ch);
 		printk(KERN_WARNING "ctc: Out of memory in add_channel\n");
@@ -1808,24 +1900,29 @@
 	 *
 	 * ccw[0..2] (Channel program for generic I/O):
 	 *           0: prepare
-	 *           1: read or write (depending on direction)
+	 *           1: read or write (depending on direction) with fixed
+	 *              buffer (idal allocated once when buffer is allocated)
 	 *           2: nop
-	 * ccw[3..4] (Channel program for initial channel setup):
+	 * ccw[3..5] (Channel program for direct write of packets)
+	 *           3: prepare
+	 *           4: write (idal allocated on every write).
+	 *           5: nop
+	 * ccw[6..7] (Channel program for initial channel setup):
 	 *           3: set extended mode
 	 *           4: nop
 	 *
-	 * ch->ccw[0..2] are initialized in ch_action_start because
+	 * ch->ccw[0..5] are initialized in ch_action_start because
 	 * the channel's direction is yet unknown here.
 	 */
-	ch->ccw[3].cmd_code = CCW_CMD_SET_EXTENDED;
-	ch->ccw[3].flags    = CCW_FLAG_SLI;
-	ch->ccw[3].count    = 0;
-	ch->ccw[3].cda      = 0;
+	ch->ccw[6].cmd_code = CCW_CMD_SET_EXTENDED;
+	ch->ccw[6].flags    = CCW_FLAG_SLI;
+	ch->ccw[6].count    = 0;
+	ch->ccw[6].cda      = 0;
 	
-	ch->ccw[4].cmd_code = CCW_CMD_NOOP;
-	ch->ccw[4].flags    = CCW_FLAG_SLI;
-	ch->ccw[4].count    = 0;
-	ch->ccw[4].cda      = 0;
+	ch->ccw[7].cmd_code = CCW_CMD_NOOP;
+	ch->ccw[7].flags    = CCW_FLAG_SLI;
+	ch->ccw[7].count    = 0;
+	ch->ccw[7].cda      = 0;
 
 	ch->irq = irq;
 	ch->devno = devno;
@@ -1835,7 +1932,8 @@
 			ch_event_names, NR_CH_STATES, NR_CH_EVENTS,
 			ch_fsm, CH_FSM_LEN, GFP_KERNEL);
 	if (ch->fsm == NULL) {
-		printk(KERN_WARNING "ctc: Could not create FSM in add_channel\n");
+		printk(KERN_WARNING
+		       "ctc: Could not create FSM in add_channel\n");
 		kfree(ch);
 		return -1;
 	}
@@ -1852,8 +1950,8 @@
 		c = &(*c)->next;
 	if ((*c)->devno == devno) {
 		printk(KERN_DEBUG
-		       "ctc: add_channel: device %04x already in list\n",
-		       (*c)->devno);
+		       "ctc: add_channel: device %04x already in list, "
+		       "using old entry\n", (*c)->devno);
 		kfree(ch->devstat);
 		kfree_fsm(ch->fsm);
 		kfree(ch);
@@ -1867,14 +1965,14 @@
 	return 0;
 }
 
+#ifndef CTC_CHANDEV
 /**
  * scan for all channels and create an entry in the channels list
  * for every supported channel.
- *
- * @param print_result Flag: If !0, print a final result.
  */
-static void channel_scan(int print_result)
+static void channel_scan(void)
 {
+	static int      print_result = 1;
 	int	        irq;
 	int             nr_escon = 0;
 	int             nr_ctca  = 0;
@@ -1912,7 +2010,9 @@
 		else
 			printk(KERN_INFO "ctc: No channel devices found.\n");
 	}
+	print_result = 0;
 }
+#endif
 
 /**
  * Release a specific channel in the channel list.
@@ -1925,6 +2025,40 @@
 	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(channel *ch)
+{
+	channel **c = &channels;
+
+	if (ch == NULL)
+		return;
+
+#ifndef CTC_CHANDEV
+	if (ch->flags & CHANNEL_FLAGS_INUSE)
+		FREE_IRQ(ch->irq, ch->devstat);
+#endif
+	channel_free(ch);
+	while (*c) {
+		if (*c == ch) {
+			*c = ch->next;
+			fsm_deltimer(&ch->timer);
+			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);
+			return;
+		}
+		c = &((*c)->next);
+	}
+}
+
 
 /**
  * Get a specific channel from the channel list.
@@ -1977,7 +2111,7 @@
 	return ch;
 }
 
-
+#ifndef CTC_CHANDEV
 /**
  * Get the next free channel from the channel list
  *
@@ -2001,6 +2135,7 @@
 	}
 	return ch;
 }
+#endif
 
 /**
  * Return the channel type by name.
@@ -2065,7 +2200,6 @@
 		}
 	}
 
-	clear_normalized_cda(&ch->ccw[1]);
 	dev = (net_device *)(ch->netdev);
 	if (dev == NULL) {
 		printk(KERN_CRIT
@@ -2173,6 +2307,7 @@
 static void dev_action_chup(fsm_instance *fi, int event, void *arg)
 {
 	net_device *dev = (net_device *)arg;
+	ctc_priv   *privptr = dev->priv;
 
 	switch (fsm_getstate(fi)) {
 		case DEV_STATE_STARTWAIT_RXTX:
@@ -2187,6 +2322,8 @@
 				printk(KERN_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;
@@ -2196,6 +2333,8 @@
 				printk(KERN_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;
@@ -2220,8 +2359,13 @@
  */
 static void dev_action_chdown(fsm_instance *fi, int event, void *arg)
 {
+	net_device *dev = (net_device *)arg;
+	ctc_priv   *privptr = dev->priv;
+
 	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
@@ -2313,8 +2457,6 @@
 	if (fsm_getstate(ch->fsm) != CH_STATE_TXIDLE) {
 		int l = skb->len + LL_HEADER_LENGTH;
 
-		if (ch->type == channel_type_escon)
-			return -EBUSY;
 		spin_lock_irqsave(&ch->collect_lock, saveflags);
 		if (ch->collect_len + l > ch->max_bufsize - 2)
 			rc = -EBUSY;
@@ -2331,6 +2473,7 @@
 		spin_unlock_irqrestore(&ch->collect_lock, saveflags);
 	} else {
 		__u16 block_len;
+		int ccw_idx;
 
 		/**
 		 * Protect skb against beeing free'd by upper
@@ -2345,17 +2488,52 @@
 		       LL_HEADER_LENGTH);
 		block_len = skb->len + 2;
 		*((__u16 *)skb_push(skb, 2)) = block_len;
-		skb_queue_tail(&ch->io_queue, skb);
+		set_normalized_cda(&ch->ccw[4], virt_to_phys(skb->data));
+		if (ch->ccw[4].cda == 0) {
+			/**
+			 * 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.
+				 */
+				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 {
+			ch->ccw[4].count = block_len;
+			skb_queue_tail(&ch->io_queue, skb);
+			ccw_idx = 3;
+		}
 		ch->retry = 0;
+#ifdef DEBUG
+		ctc_dump_skb(skb, 0);
+#endif
 		fsm_newstate(ch->fsm, CH_STATE_TX);
-		fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
-		ch->ccw[1].count = block_len;
-		set_normalized_cda(&ch->ccw[1], virt_to_phys(skb->data));
+		fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC,
+			     CH_EVENT_TIMER, ch);
 		s390irq_spin_lock_irqsave(ch->irq, saveflags);
 		ch->prof.send_stamp = xtime;
-		rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
+#ifdef DEBUG
+	printk(KERN_DEBUG "ccw[%d].cda = %08x\n", ccw_idx+1,
+	       ch->ccw[ccw_idx+1].cda);
+#endif
+		rc = do_IO(ch->irq, &ch->ccw[ccw_idx], (intparm_t)ch, 0xff, 0);
 		s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
-		ch->prof.doios_single++;
+		if (ccw_idx == 3)
+			ch->prof.doios_single++;
 		if (rc != 0) {
 			fsm_deltimer(&ch->timer);
 			ccw_check_return_code(ch, rc);
@@ -2365,6 +2543,14 @@
 			 * again on retransmit.
 			 */
 			skb_pull(skb, LL_HEADER_LENGTH + 2);
+		} else {
+			if (ccw_idx == 0) {
+				net_device *dev = ch->netdev;
+				ctc_priv   *privptr = dev->priv;
+				privptr->stats.tx_packets++;
+				privptr->stats.tx_bytes +=
+					skb->len - LL_HEADER_LENGTH;
+			}
 		}
 	}
 
@@ -2429,8 +2615,9 @@
 		return 0;
 	}
 	if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) {
-		printk(KERN_WARNING "%s: Got sk_buff with head room < %ld bytes\n",
-				dev->name, LL_HEADER_LENGTH + 2);
+		printk(KERN_WARNING
+		       "%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;
@@ -2443,6 +2630,8 @@
 	 */
 	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;
 		dst_link_failure(skb);
 		dev_kfree_skb(skb);
 		privptr->stats.tx_dropped++;
@@ -2577,12 +2766,23 @@
 		return -EFAULT;
 	tmp[count+1] = '\0';
 	bs1 = simple_strtoul(tmp, &e, 0);
+
 	if ((bs1 > CTC_BUFSIZE_LIMIT) ||
-	    (bs1 < (dev->mtu - LL_HEADER_LENGTH - 2)) ||
 	    (e && (!isspace(*e))))
 		return -EINVAL;
+	if ((dev->flags & IFF_RUNNING) &&
+	    (bs1 < (dev->mtu + LL_HEADER_LENGTH + 2)))
+		return -EINVAL;
+	if (bs1 < (576 + LL_HEADER_LENGTH + 2))
+		return -EINVAL;
+
+
 	privptr->channel[READ]->max_bufsize =
 		privptr->channel[WRITE]->max_bufsize = bs1;
+	if (!(dev->flags & IFF_RUNNING))
+		dev->mtu = bs1 - LL_HEADER_LENGTH - 2;
+	privptr->channel[READ]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED;
+	privptr->channel[WRITE]->flags |= CHANNEL_FLAGS_BUFSIZE_CHANGED;
 
 	return count;
 }
@@ -2685,7 +2885,7 @@
 			     fsm_getstate_str(privptr->channel[WRITE]->fsm));
 		p += sprintf(p, "Max. TX buffer used: %ld\n",
 			     privptr->channel[WRITE]->prof.maxmulti);
-		p += sprintf(p, "Max. chained CCWs: %ld\n",
+		p += sprintf(p, "Max. chained SKBs: %ld\n",
 			     privptr->channel[WRITE]->prof.maxcqueue);
 		p += sprintf(p, "TX single write ops: %ld\n",
 			     privptr->channel[WRITE]->prof.doios_single);
@@ -2853,15 +3053,18 @@
 	proc_register(privptr->proc_dentry, privptr->proc_stat_entry);
 	proc_register(privptr->proc_dentry, privptr->proc_ctrl_entry);
 #endif
+	privptr->proc_registered = 1;
 }
 
-#ifdef MODULE
+
 /**
  * Destroy a device specific subdirectory.
  *
  * @param privptr Pointer to device private data.
  */
 static void ctc_proc_destroy_sub(ctc_priv *privptr) {
+	if (!privptr->proc_registered)
+		return;
 #if LINUX_VERSION_CODE > 0x020362
 	remove_proc_entry("statistics", privptr->proc_dentry);
 	remove_proc_entry("buffersize", privptr->proc_dentry);
@@ -2874,9 +3077,12 @@
 	proc_unregister(&ctc_dir,
 			privptr->proc_dentry->low_ino);
 #endif
+	privptr->proc_registered = 0;
 }
-#endif MODULE
+
 
+
+#ifndef CTC_CHANDEV
 /**
  * Setup related routines
  *****************************************************************************/
@@ -2991,7 +3197,8 @@
 #else
 static param parms_array[MAX_STATIC_DEVICES];
 static param *next_param = parms_array;
-#define alloc_param() ((next_param<parms_array+MAX_STATIC_DEVICES)?next_param++:NULL)
+#define alloc_param() \
+        ((next_param<parms_array+MAX_STATIC_DEVICES)?next_param++:NULL)
 #endif MODULE
 
 /**
@@ -3159,7 +3366,313 @@
 #if LINUX_VERSION_CODE >= 0x020300
 __setup("ctc=", ctc_setup);
 #endif
+#endif /* !CTC_CHANDEV */
 
+
+static void
+ctc_netdev_unregister(net_device *dev)
+{
+	ctc_priv *privptr;
+
+	if (!dev)
+		return;
+	privptr = (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(net_device *dev)
+{
+	ctc_priv *privptr = (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(net_device *dev, int free_dev)
+{
+	ctc_priv *privptr;
+	if (!dev)
+		return;
+	privptr = dev->priv;
+	if (privptr) {
+		if (privptr->fsm)
+			kfree_fsm(privptr->fsm);
+		ctc_proc_destroy_sub(privptr);
+		kfree(privptr);
+	}
+#ifdef MODULE
+	if (free_dev)
+		kfree(dev);
+#endif
+}
+
+#ifdef CTC_CHANDEV
+static int
+ctc_shutdown(net_device *dev)
+{
+	ctc_priv *privptr;
+
+	if (!dev)
+		return 0;
+	privptr = (ctc_priv *)dev->priv;
+	channel_remove(privptr->channel[READ]);
+	channel_remove(privptr->channel[WRITE]);
+	ctc_free_netdevice(dev, 0);
+	return 0;
+}
+#endif
+
+/**
+ * Initialize everything of the net device except the name and the
+ * channel structs.
+ */
+static net_device *
+ctc_init_netdevice(net_device *dev, int alloc_device)
+{
+	ctc_priv *privptr;
+	int      priv_size;
+	if (alloc_device) {
+		dev = kmalloc(sizeof(net_device)
+#if LINUX_VERSION_CODE < 0x020300
+			      + 11 /* name + zero */
+#endif
+			      , GFP_KERNEL);
+		if (!dev)
+			return NULL;
+		memset(dev, 0, sizeof(net_device));
+	}
+	priv_size = sizeof(ctc_priv) + sizeof(ctc_template) +
+		sizeof(stat_entry) + sizeof(ctrl_entry);
+	dev->priv = kmalloc(priv_size, GFP_KERNEL);
+	if (dev->priv == NULL) {
+		if (alloc_device)
+			kfree(dev);
+		return NULL;
+	}
+        memset(dev->priv, 0, priv_size);
+        privptr = (ctc_priv *)dev->priv;
+        privptr->proc_dentry = (struct proc_dir_entry *)
+		(((char *)privptr) + sizeof(ctc_priv));
+        privptr->proc_stat_entry = (struct proc_dir_entry *)
+		(((char *)privptr) + sizeof(ctc_priv) +
+		 sizeof(ctc_template));
+        privptr->proc_ctrl_entry = (struct proc_dir_entry *)
+		(((char *)privptr) + sizeof(ctc_priv) +
+		 sizeof(ctc_template) + sizeof(stat_entry));
+	memcpy(privptr->proc_dentry, &ctc_template, sizeof(ctc_template));
+	memcpy(privptr->proc_stat_entry, &stat_entry, sizeof(stat_entry));
+	memcpy(privptr->proc_ctrl_entry, &ctrl_entry, sizeof(ctrl_entry));
+	privptr->fsm = init_fsm("ctcdev", dev_state_names,
+			dev_event_names, NR_DEV_STATES, NR_DEV_EVENTS,
+			dev_fsm, DEV_FSM_LEN, GFP_KERNEL);
+	if (privptr->fsm == NULL) {
+		kfree(privptr);
+		if (alloc_device)
+			kfree(dev);
+		return NULL;
+	}
+	fsm_newstate(privptr->fsm, DEV_STATE_STOPPED);
+	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;
+	SET_DEVICE_START(dev, 1);
+	dev_init_buffers(dev);
+	dev->flags	         = IFF_POINTOPOINT | IFF_NOARP;
+	return dev;
+}
+
+#ifdef CTC_CHANDEV
+static void
+ctc_chandev_msck_notify(void *dev, int msck_irq,
+			chandev_msck_status prevstatus,
+			chandev_msck_status newstatus)
+{
+	net_device *device = (net_device *)dev;
+	ctc_priv *privptr;
+	int direction;
+
+	if (!dev)
+		return;
+
+	privptr = device->priv;
+	if (prevstatus == chandev_status_revalidate)
+		for (direction = READ; direction <= WRITE; direction++) {
+			channel *ch = privptr->channel[direction];
+			if(ch->irq == msck_irq) {
+				s390_dev_info_t devinfo;
+
+				if (get_dev_info_by_irq(ch->irq, &devinfo))
+					ch->devno = devinfo.devno;
+				else
+					printk(KERN_WARNING
+					       "ctc_chandev_msck_notify: "
+					       "get_dev_info_by_irq failed for "
+					       "irq %d\n", ch->irq);
+			}
+		}
+	switch (newstatus) {
+		case chandev_status_not_oper:
+		case chandev_status_no_path:
+		case chandev_status_gone:
+			for (direction = READ; direction <= WRITE; direction++) {
+				channel *ch = privptr->channel[direction];
+				fsm_event(ch->fsm, CH_EVENT_MC_FAIL, ch);
+			}
+			printk(KERN_WARNING
+			       "ctc: %s channel deactivated\n", device->name);
+			break;
+		case chandev_status_all_chans_good:
+			for (direction = READ; direction <= WRITE; direction++) {
+				channel *ch = privptr->channel[direction];
+				fsm_event(ch->fsm, CH_EVENT_MC_GOOD, ch);
+			}
+			printk(KERN_WARNING
+			       "ctc: %s channel activated\n", device->name);
+			break;
+		default:
+			break;
+	}
+}
+
+/**
+ *
+ * Setup an interface.
+ *
+ * Like ctc_setup(), ctc_probe() can be called from two different locations:
+ *  - If built as module, it is called from within init_module().
+ *  - If built in monolithic kernel, it is called from within generic network
+ *    layer during initialization for every corresponding device, declared in
+ *    drivers/net/Space.c
+ *
+ * @param dev Pointer to net_device to be initialized.
+ *
+ * @returns 0 on success, !0 on failure.
+ */
+static int ctc_chandev_probe(chandev_probeinfo *info)
+{
+	int               devno[2];
+	__u16             proto;
+	int               rc;
+	int               direction;
+	channel_type_t    type;
+	ctc_priv          *privptr;
+	net_device        *dev;
+
+	ctc_proc_create_main();
+
+
+	switch (info->chan_type) {
+		case chandev_type_ctc:
+			type = channel_type_ctca;
+			break;
+		case chandev_type_escon:
+			type = channel_type_escon;
+			break;
+		default:
+			printk(KERN_WARNING "ctc_chandev_probe called with "
+			       "unsupported channel type %d\n", info->chan_type);
+			return -ENODEV;
+	}
+	devno[READ]  = info->read.devno;
+	devno[WRITE] = info->write.devno;
+	proto        = info->port_protocol_no;
+
+	if (add_channel(info->read.irq, info->read.devno, type))
+		return -ENOMEM;
+	if (add_channel(info->write.irq, info->write.devno, type))
+		return -ENOMEM;
+
+	dev = ctc_init_netdevice(NULL, 1);
+
+	
+	if (!dev) {
+		printk(KERN_WARNING "ctc_init_netdevice failed\n");
+		return -ENODEV;
+	}
+	
+	if (proto == CTC_PROTO_LINUX_TTY)
+		chandev_build_device_name(info, dev->name, "ctctty", 1);
+	else
+		chandev_build_device_name(info, dev->name, "ctc", 1);
+
+	privptr = (ctc_priv *)dev->priv;
+	privptr->protocol = proto;
+	for (direction = READ; direction <= WRITE; direction++) {
+		privptr->channel[direction] =
+			channel_get(type, devno[direction], direction);
+		if (privptr->channel[direction] == NULL) {
+			if (direction == WRITE) {
+				FREE_IRQ(privptr->channel[READ]->irq,
+					 privptr->channel[READ]->devstat);
+				channel_free(privptr->channel[READ]);
+			}
+			ctc_free_netdevice(dev, 1);
+			return -ENODEV;
+		}
+		privptr->channel[direction]->netdev = dev;
+		privptr->channel[direction]->protocol = proto;
+		privptr->channel[direction]->max_bufsize = CTC_BUFSIZE_DEFAULT;
+		rc = REQUEST_IRQ(privptr->channel[direction]->irq,
+				 (void *)ctc_irq_handler, SA_INTERRUPT,
+				 dev->name,
+				 privptr->channel[direction]->devstat);
+		if (rc) {
+			printk(KERN_WARNING
+			       "%s: requested irq %d is busy rc=%02x\n",
+			       dev->name, privptr->channel[direction]->irq,
+			       rc);
+			if (direction == WRITE) {
+				FREE_IRQ(privptr->channel[READ]->irq,
+					 privptr->channel[READ]->devstat);
+				channel_free(privptr->channel[READ]);
+			}
+			channel_free(privptr->channel[direction]);
+			ctc_free_netdevice(dev, 1);
+			return -EBUSY;
+		}
+	}
+	if (ctc_netdev_register(dev) != 0) {
+		ctc_free_netdevice(dev, 1);
+		return -ENODEV;
+	}
+
+	/**
+	 * register subdir in /proc/net/ctc
+	 */
+	ctc_proc_create_sub(dev);
+	strncpy(privptr->fsm->name, dev->name, sizeof(privptr->fsm->name));
+	activated++;
+
+	print_banner();
+
+	printk(KERN_INFO
+	       "%s: read: ch %04x (irq %04x), "
+	       "write: ch %04x (irq %04x) proto: %d\n",
+	       dev->name, privptr->channel[READ]->devno,
+	       privptr->channel[READ]->irq, privptr->channel[WRITE]->devno,
+	       privptr->channel[WRITE]->irq, proto);
+
+	chandev_initdevice(info, dev, 0, dev->name,
+			   (proto == CTC_PROTO_LINUX_TTY)
+			   ? chandev_category_serial_device :
+			   chandev_category_network_device,
+			   (chandev_unregfunc)ctc_netdev_unregister);
+	return 0;
+}
+#else /* ! CHANDEV */
 /**
  *
  * Setup an interface.
@@ -3191,7 +3704,7 @@
 	 * ctc_probe gets control.
 	 */
 	if (channels == NULL)
-		channel_scan(1);
+		channel_scan();
 
 	type = extract_channel_media(dev->name);
 	if (type == channel_type_unknown)
@@ -3212,47 +3725,13 @@
 		}
 	}
 
-	dev->priv = kmalloc(sizeof(ctc_priv)
-			    + sizeof(ctc_template)
-			    + sizeof(stat_entry)
-			    + sizeof(ctrl_entry)
-			    , GFP_KERNEL);
-	if (dev->priv == NULL)
-		return -ENOMEM;
-	memset(dev->priv, 0, sizeof(ctc_priv));
+#ifndef MODULE
+	if (ctc_init_netdevice(dev, 0) == NULL)
+		return -ENODEV;
+#endif
 	privptr = (ctc_priv *)dev->priv;
 	privptr->protocol = proto;
-        privptr->proc_dentry = (struct proc_dir_entry *)
-		(((char *)privptr) + sizeof(ctc_priv));
-        privptr->proc_stat_entry = (struct proc_dir_entry *)
-		(((char *)privptr) + sizeof(ctc_priv) +
-		 sizeof(ctc_template));
-        privptr->proc_ctrl_entry = (struct proc_dir_entry *)
-		(((char *)privptr) + sizeof(ctc_priv) +
-		 sizeof(ctc_template) + sizeof(stat_entry));
-	memcpy(privptr->proc_dentry, &ctc_template, sizeof(ctc_template));
-	memcpy(privptr->proc_stat_entry, &stat_entry, sizeof(stat_entry));
-	memcpy(privptr->proc_ctrl_entry, &ctrl_entry, sizeof(ctrl_entry));
-	privptr->fsm = init_fsm(dev->name, dev_state_names,
-			dev_event_names, NR_DEV_STATES, NR_DEV_EVENTS,
-			dev_fsm, DEV_FSM_LEN, GFP_KERNEL);
-	fsm_newstate(privptr->fsm, DEV_STATE_STOPPED);
-	if (privptr->fsm == NULL) {
-		kfree(privptr);
-		return -ENOMEM;
-	}
-	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;
-	SET_DEVICE_START(dev, 1);
-	dev->flags	         = IFF_POINTOPOINT | IFF_NOARP;
+
 	for (direction = READ; direction <= WRITE; direction++) {
 		if ((ctc_no_auto == 0) || (devno[direction] == -1))
 			privptr->channel[direction] =
@@ -3262,19 +3741,17 @@
 				channel_get(type, devno[direction], direction);
 		if (privptr->channel[direction] == NULL) {
 			if (direction == WRITE) {
-				free_irq(privptr->channel[READ]->irq,
+				FREE_IRQ(privptr->channel[READ]->irq,
 					 privptr->channel[READ]->devstat);
 				channel_free(privptr->channel[READ]);
 			}
-			kfree_fsm(privptr->fsm);
-			kfree(dev->priv);
-			dev->priv = NULL;
+			ctc_free_netdevice(dev, 1);
 			return -ENODEV;
 		}
 		privptr->channel[direction]->netdev = dev;
 		privptr->channel[direction]->protocol = proto;
 		privptr->channel[direction]->max_bufsize = CTC_BUFSIZE_DEFAULT;
-		rc = request_irq(privptr->channel[direction]->irq,
+		rc = REQUEST_IRQ(privptr->channel[direction]->irq,
 				 (void *)ctc_irq_handler, SA_INTERRUPT,
 				 dev->name,
 				 privptr->channel[direction]->devstat);
@@ -3284,14 +3761,12 @@
 			       dev->name, privptr->channel[direction]->irq,
 			       rc);
 			if (direction == WRITE) {
-				free_irq(privptr->channel[READ]->irq,
+				FREE_IRQ(privptr->channel[READ]->irq,
 					 privptr->channel[READ]->devstat);
 				channel_free(privptr->channel[READ]);
 			}
 			channel_free(privptr->channel[direction]);
-			kfree_fsm(privptr->fsm);
-			kfree(dev->priv);
-			dev->priv = NULL;
+			ctc_free_netdevice(dev, 1);
 			return -EBUSY;
 		}
 	}
@@ -3304,13 +3779,15 @@
 	print_banner();
 
 	printk(KERN_INFO
-	       "%s: read: ch %04x (irq %04x), write: ch %04x (irq %04x) proto: %d\n",
+	       "%s: read: ch %04x (irq %04x), "
+	       "write: ch %04x (irq %04x) proto: %d\n",
 	       dev->name, privptr->channel[READ]->devno,
 	       privptr->channel[READ]->irq, privptr->channel[WRITE]->devno,
 	       privptr->channel[WRITE]->irq, proto);
 
 	return 0;
 }
+#endif
 
 /**
  * Module related routines
@@ -3324,46 +3801,35 @@
  * for that.
  */
 void cleanup_module(void) {
-	channel *c = channels;
 
-	ctc_tty_cleanup();
+	ctc_tty_cleanup(0);
 	/* we are called if all interfaces are down only, so no need
 	 * to bother around with locking stuff
 	 */
-	channels = NULL;
-	while (c) {
-		channel *oldc = c;
-		if (c->netdev && c->netdev->priv) {
-			net_device *nd = c->netdev;
-			ctc_priv *privptr = (ctc_priv *)nd->priv;
-
-			fsm_deltimer(&privptr->channel[READ]->timer);
-			fsm_deltimer(&privptr->channel[WRITE]->timer);
-			free_irq(privptr->channel[READ]->irq,
-				 privptr->channel[READ]->devstat);
-			free_irq(privptr->channel[WRITE]->irq,
-				 privptr->channel[WRITE]->devstat);
-			kfree_fsm(privptr->channel[READ]->fsm);
-			kfree_fsm(privptr->channel[WRITE]->fsm);
-			if (privptr->protocol != CTC_PROTO_LINUX_TTY)
-				unregister_netdev(nd);
-			else
-				ctc_tty_unregister_netdev(nd);
-			kfree_fsm(privptr->fsm);
-			privptr->channel[READ]->netdev = NULL;
-			privptr->channel[WRITE]->netdev = NULL;
-			ctc_proc_destroy_sub(privptr);
-			kfree(privptr);
-			kfree(nd);
-			if (c->netdev != NULL)
-				printk(KERN_EMERG
-				       "ctc: PANIC: channel list corrupted\n");
-		}
-		c = c->next;
-		kfree(oldc->ccw);
-		kfree(oldc);
+#ifndef CTC_CHANDEV
+	while (channels) {
+		if ((channels->flags & CHANNEL_FLAGS_INUSE) &&
+		    (channels->netdev != NULL)) {
+			net_device *dev = channels->netdev;
+			ctc_priv *privptr = dev->priv;
+
+			if (privptr) {
+				privptr->channel[READ]->netdev = NULL;
+				privptr->channel[WRITE]->netdev = NULL;
+			}
+			channels->netdev = NULL;
+			ctc_netdev_unregister(dev);
+			ctc_free_netdevice(dev, 1);
+		}
+		channel_remove(channels);
 	}
+	channels = NULL;
+#endif
+	ctc_tty_cleanup(1);
 	ctc_proc_destroy_main();
+#ifdef CTC_CHANDEV
+	chandev_unregister(ctc_chandev_probe, 1);
+#endif
 	printk(KERN_INFO "CTC driver unloaded\n");
 }
 
@@ -3377,41 +3843,48 @@
  * @return 0 on success, !0 on error.
  */
 int ctc_init(void) {
+#ifndef CTC_CHANDEV
 	int   cnt[2];
 	int   itype;
 	int   activated;
-	int   ret = 0;
 	param *par;
+#endif
+	int   ret = 0;
+	int   probed = 0;
 
 	print_banner();
 
-#ifdef DEBUG
+#if defined(DEBUG) && !defined(CTC_CHANDEV)
 	printk(KERN_DEBUG
-	       "ctc: init_module(): got string '%s'\n", setup);
+	       "ctc: init_module(): got string '%s'\n", ctc);
 #endif
 
+#ifndef CTC_CHANDEV
 #ifdef MODULE
 	ctc_setup(ctc);
 #endif
-	activated = 0;
 	par = params;
+#endif
+
+	activated = 0;
 	ctc_tty_init();
+#ifdef CTC_CHANDEV
+	chandev_register_and_probe(ctc_chandev_probe,
+				   (chandev_shutdownfunc)ctc_shutdown,
+				   ctc_chandev_msck_notify,
+				   chandev_type_ctc|chandev_type_escon);
+#else /* CTC_CHANDEV */
 	for (itype = 0; itype < 2; itype++) {
 		net_device *dev = NULL;
 		char       *bname = (itype) ? "escon" : "ctc";
 
 		cnt[itype] = 0;
 		do {
-			dev = kmalloc(sizeof(net_device)
-#if LINUX_VERSION_CODE < 0x020300
-				      + 11 /* name + zero */
-#endif
-				      , GFP_KERNEL);
+			dev = ctc_init_netdevice(NULL, 1);
 			if (!dev) {
 				ret = -ENOMEM;
 				break;
 			}
-			memset(dev, 0, sizeof(net_device));
 #if LINUX_VERSION_CODE < 0x020300
 			dev->name = (unsigned char *)dev + sizeof(net_device);
 #endif
@@ -3425,7 +3898,9 @@
 					if (isdigit(*p))
 						break;
 				if (p && *p) {
-					int it = (strncmp(dev->name, "escon", 5)) ? 1 : 0;
+					int it =
+						(strncmp(dev->name, "escon", 5))
+						? 1 : 0;
 					n = simple_strtoul(p, NULL, 0);
 					if (n >= cnt[it])
 						cnt[it] = n + 1;
@@ -3433,7 +3908,7 @@
 			} else {
 				if (ctc_no_auto) {
 					itype = 3;
-					kfree(dev);
+					ctc_free_netdevice(dev, 1);
 					dev = NULL;
 					break;
 				}
@@ -3444,6 +3919,7 @@
 			printk(KERN_DEBUG "ctc: %s(): probing for device %s\n",
 						 __FUNCTION__, dev->name);
 #endif			
+			probed = 1;
 			if (ctc_probe(dev) == 0) {
 				ctc_priv *privptr = (ctc_priv *)dev->priv;
 #ifdef DEBUG
@@ -3454,67 +3930,56 @@
 				       "ctc: %s(): registering device %s\n",
 				       __FUNCTION__, dev->name);
 #endif
-				if (privptr->protocol != CTC_PROTO_LINUX_TTY) {
-					if (register_netdev(dev) != 0) {
-						printk(KERN_WARNING
-						       "ctc: Couldn't register netdev %s\n",
-						       dev->name);
-						free_irq(privptr->channel[READ]->irq,
-							 privptr->channel[READ]->devstat);
-						free_irq(privptr->channel[WRITE]->irq,
-							 privptr->channel[WRITE]->devstat);
-						channel_free(privptr->channel[READ]);
-						channel_free(privptr->channel[WRITE]);
-						kfree(dev->priv);
-						kfree(dev);
-					} else {
-#ifdef DEBUG
-						printk(KERN_DEBUG
-						       "ctc: %s(): register succeed\n",
-						       __FUNCTION__);
-#endif			
-						activated++;
-					}
+				if (ctc_netdev_register(dev) != 0) {
+					printk(KERN_WARNING
+					       "ctc: Couldn't register %s\n",
+					       dev->name);
+					FREE_IRQ(
+						privptr->channel[READ]->irq,
+						privptr->channel[READ]->devstat);
+					FREE_IRQ(
+						privptr->channel[WRITE]->irq,
+						privptr->channel[WRITE]->devstat);
+					channel_free(privptr->channel[READ]);
+					channel_free(privptr->channel[WRITE]);
+					ctc_free_netdevice(dev, 1);
+					dev = NULL;
 				} else {
-					if (ctc_tty_register_netdev(dev) != 0) {
-						printk(KERN_WARNING
-						       "ctc: Couldn't register ttydev %s\n",
-						       dev->name);
-						free_irq(privptr->channel[READ]->irq,
-							 privptr->channel[READ]->devstat);
-						free_irq(privptr->channel[WRITE]->irq,
-							 privptr->channel[WRITE]->devstat);
-						channel_free(privptr->channel[READ]);
-						channel_free(privptr->channel[WRITE]);
-						kfree(dev->priv);
-						kfree(dev);
-					} else {
-#ifdef DEBUG
-						printk(KERN_DEBUG
-						       "ctc: %s(): register succeed\n",
-						       __FUNCTION__);
+#ifdef DEBUG
+					printk(KERN_DEBUG
+					       "ctc: %s(): register succeed\n",
+					       __FUNCTION__);
 #endif			
-						activated++;
-					}
+					activated++;
 				}
-				
 			} else {
 #ifdef DEBUG
 				printk(KERN_DEBUG
 				       "ctc: %s(): probing failed\n",
 				       __FUNCTION__);
 #endif			
-				kfree(dev);
 				dev = NULL;
 			}
 		} while (dev && (ret == 0));
 	}
+#endif /* CHANDEV */
+#if !defined(CTC_CHANDEV) && defined(MODULE)
 	if (!activated) {
 		printk(KERN_WARNING "ctc: No devices registered\n");
 		ret = -ENODEV;
 	}
-	if (ret)
-		ctc_tty_cleanup();
+#endif
+	if (ret) {
+		ctc_tty_cleanup(0);
+		ctc_tty_cleanup(1);
+#if defined(CTC_CHANDEV) && defined(MODULE)
+		chandev_unregister(ctc_chandev_probe, 0);
+#endif
+#ifdef MODULE
+		if (probed)
+			ctc_proc_destroy_main();
+#endif
+	}
 	return ret;
 }
 

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