patch-1.3.82 linux/drivers/net/strip.c

Next file: linux/drivers/pci/pci.c
Previous file: linux/drivers/net/slip.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.81/linux/drivers/net/strip.c linux/drivers/net/strip.c
@@ -0,0 +1,1494 @@
+/*
+ * Copyright 1996 The Board of Trustees of The Leland Stanford
+ * Junior University. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies.  Stanford University
+ * makes no representations about the suitability of this
+ * software for any purpose.  It is provided "as is" without
+ * express or implied warranty.
+ *
+ * strip.c	This module implements Starmode Radio IP (STRIP)
+ *		for kernel-based devices like TTY.  It interfaces between a
+ *		raw TTY, and the kernel's INET protocol layers (via DDI).
+ *
+ * Version:	@(#)strip.c	0.9.1	3/6/95
+ *
+ * Author:	Stuart Cheshire <cheshire@cs.stanford.edu>
+ *
+ * Fixes:
+ *		Stuart Cheshire:
+ *			Original version converted from SLIP driver
+ *		Jonathan Stone:
+ *			change to 1.3 calling conventions
+ *		Stuart Cheshire:
+ *			v0.9 12th Feb 1996.
+ *			New byte stuffing (2+6 run-length encoding)
+ *			New watchdog timer task
+ *			New Protocol key (SIP0)
+ *			v0.9.1 3rd March 1996
+ *			Changed to dynamic device allocation
+ */
+
+#include <linux/config.h>
+#ifdef MODULE
+#include <linux/module.h>
+#include <linux/version.h>
+#endif
+
+#include <asm/system.h>
+#include <asm/segment.h>
+#include <asm/bitops.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/if_strip.h>
+#include <net/arp.h>
+#ifdef CONFIG_INET
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#endif
+
+#ifdef MODULE
+#define STRIP_VERSION    "0.9.1-STUART.CHESHIRE-MODULAR"
+#else
+#define STRIP_VERSION    "0.9.1-STUART.CHESHIRE"
+#endif
+
+#define STRIP_MTU 1024
+#define STRIP_MAGIC 0x5303
+
+/*
+ *	Do we still needs all these flags? 
+ */
+
+enum 
+{
+	STR_INUSE = 0,		/* Channel in use	*/
+	STR_ESCAPE,			/* ESC received		*/
+	STR_ERROR			/* Parity, etc. error	*/
+}
+STRIP_FLAGS;
+
+struct strip 
+{
+	int magic;
+	/*
+	 *	Other useful structures. 
+	 */
+
+	/*
+	 *	These are pointers to the malloc()ed frame buffers.
+	 */
+
+	unsigned char     *rx_buff;		/* buffer for received IP packet*/
+	unsigned char     *sx_buff;		/* buffer for received serial data*/
+	int                sx_count;		/* received serial data counter */
+	unsigned char     *tx_buff;		/* transmitter buffer  */
+	unsigned char     *tx_head;		/* pointer to next byte to XMIT */
+	int                tx_left;		/* bytes left in XMIT queue     */
+
+	/*
+	 *	STRIP interface statistics.
+	 */
+	 
+	unsigned long      rx_packets;		/* inbound frames counter	*/
+	unsigned long      tx_packets;		/* outbound frames counter	*/
+	unsigned long      rx_errors;		/* Parity, etc. errors		*/
+	unsigned long      tx_errors;		/* Planned stuff		*/
+	unsigned long      rx_dropped;		/* No memory for skb		*/
+	unsigned long      tx_dropped;		/* When MTU change		*/
+	unsigned long      rx_over_errors;	/* Frame bigger then STRIP buf. */
+
+	/*
+	 *	Internal variables.
+	 */
+	 
+	struct strip      *next;		/* The next struct in the list 	*/
+	struct strip     **referrer;		/* The pointer that points to us */
+	unsigned char      flags;		/* Flag values/ mode etc	*/
+	int                mtu;			/* Our mtu (to spot changes!)	*/
+	int                buffsize;		/* Max buffers sizes		*/
+	long               watchdog_doprobe;	/* Next time to test the radio	*/
+	long               watchdog_doreset;	/* Time to do next reset	*/
+	struct timer_list  idle_timer;
+
+	struct tty_struct *tty;			/* ptr to TTY structure		*/
+	char               if_name[8];		/* Dynamically generated name	*/
+	struct device       dev;		/* Our device stucture		*/
+};
+/************************************************************************/
+/* Utility routines for disabling and restoring interrupts		*/
+
+typedef unsigned long InterruptStatus;
+
+extern __inline__ InterruptStatus DisableInterrupts(void)
+{
+    InterruptStatus x;
+    save_flags(x);
+    cli();
+    return(x);
+}
+
+extern __inline__ void RestoreInterrupts(InterruptStatus x)
+{
+    restore_flags(x);
+}
+
+/************************************************************************/
+/* Useful structures and definitions					*/
+
+typedef struct {
+    __u8 c[32];
+} RadioName;
+
+typedef struct {
+    __u8 c[ 4];
+} MetricomKey;
+
+typedef union {
+    __u8 b[ 4];
+    __u32 l;
+} IPaddr;
+
+static const MetricomKey ProtocolKey = 
+{
+    {
+        "SIP0"
+    }
+};
+
+enum 
+{
+    FALSE = 0,
+    TRUE = 1
+};
+
+#define LONG_TIME 0x7FFFFFFF
+
+typedef struct 
+{
+	RadioName   name;	/* The address, with delimiters eg. *0000-1164* */
+	MetricomKey key;	/* Protocol type */
+} STRIP_Header;
+
+typedef struct 
+{
+	STRIP_Header h;
+	__u8 data[4];		/* Placeholder for payload (The IP packet) */
+} STRIP_Packet;
+
+/*
+ *	STRIP_ENCAP_SIZE of an IP packet is the STRIP header at the front,
+ *	byte-stuffing overhead of the payload, plus the CR at the end
+ */
+ 
+#define STRIP_ENCAP_SIZE(X) (sizeof(STRIP_Header) + (X)*65L/64L + 2)
+
+/*
+ *	Note: A Metricom packet looks like this: *<address>*<key><payload><CR>
+ *	eg. *0000-1164*SIP0<payload><CR>
+ */
+
+static struct strip *struct_strip_list = NULL;
+
+/************************************************************************/
+/* Byte stuffing/unstuffing routines					*/
+
+/* Stuffing scheme:
+ * 00    Unused (reserved character)
+ * 01-3F Run of 2-64 different characters
+ * 40-7F Run of 1-64 different characters plus a single zero at the end
+ * 80-BF Run of 1-64 of the same character
+ * C0-FF Run of 1-64 zeroes (ASCII 0)
+ */
+
+typedef enum 
+{
+    Stuff_Diff      = 0x00,
+    Stuff_DiffZero  = 0x40,
+    Stuff_Same      = 0x80,
+    Stuff_Zero      = 0xC0,
+    Stuff_NoCode    = 0xFF,	/* Special code, meaning no code selected */
+    
+    Stuff_CodeMask  = 0xC0,
+    Stuff_CountMask = 0x3F,
+    Stuff_MaxCount  = 0x3F,
+    Stuff_Magic     = 0x0D	/* The value we are eliminating */
+} StuffingCode;
+
+/* StuffData encodes the data starting at "src" for "length" bytes.
+ * It writes it to the buffer pointed to by "dst" (which must be at least
+ * as long as 1 + 65/64 of the input length). The output may be up to 1.6%
+ * larger than the input for pathological input, but will usually be smaller.
+ * StuffData returns the new value of the dst pointer as its result.
+ * "code_ptr_ptr" points to a "__u8 *" which is used to hold encoding state
+ * between calls, allowing an encoded packet to be incrementally built up
+ * from small parts. On the first call, the "__u8 *" pointed to should be
+ * initialized to NULL; between subsequent calls the calling routine should
+ * leave the value alone and simply pass it back unchanged so that the
+ * encoder can recover its current state.
+ */
+
+#define StuffData_FinishBlock(X) \
+(*code_ptr = (X) ^ Stuff_Magic, code = Stuff_NoCode)
+
+static __u8 *StuffData(__u8 *src, __u32 length, __u8 *dst, __u8 **code_ptr_ptr)
+{
+	__u8 *end = src + length;
+	__u8 *code_ptr = *code_ptr_ptr;
+ 	__u8 code = Stuff_NoCode, count = 0;
+    
+	if (!length) 
+		return(dst);
+    
+	if (code_ptr) 
+	{
+		/*
+		 *	Recover state from last call, if applicable 
+		 */
+		code  = *code_ptr & Stuff_CodeMask;
+		count = *code_ptr & Stuff_CountMask;
+	}
+
+	while (src < end) 
+	{
+		switch (code) 
+		{
+			/* Stuff_NoCode: If no current code, select one */
+			case Stuff_NoCode:
+				/* Record where we're going to put this code */
+				code_ptr = dst++;
+				count = 0;	/* Reset the count (zero means one instance) */
+				/* Tentatively start a new block */
+				if (*src == 0) 
+				{
+					code = Stuff_Zero;
+        				src++;
+				}
+				else
+				{
+					code = Stuff_Same;
+					*dst++ = *src++ ^ Stuff_Magic;
+				}
+				/* Note: We optimistically assume run of same -- */
+				/* which will be fixed later in Stuff_Same */
+				/* if it turns out not to be true. */
+				break;
+
+			/* Stuff_Zero: We already have at least one zero encoded */
+			case Stuff_Zero:
+                		/* If another zero, count it, else finish this code block */
+                		if (*src == 0) 
+                		{
+					count++;
+					src++;
+				}
+				else 
+				{
+					StuffData_FinishBlock(Stuff_Zero + count);
+        			}
+				break;
+
+			/* Stuff_Same: We already have at least one byte encoded */
+			case Stuff_Same:
+				/* If another one the same, count it */
+				if ((*src ^ Stuff_Magic) == code_ptr[1]) 
+				{
+					count++;
+					src++;
+					break;
+				}
+				/* else, this byte does not match this block. */
+        			/* If we already have two or more bytes encoded, */
+				/* finish this code block */
+				if (count) 
+				{
+					StuffData_FinishBlock(Stuff_Same + count);
+					break;
+				}
+				/* else, we only have one so far, */
+				/* so switch to Stuff_Diff code */
+				code = Stuff_Diff;
+				/* and fall through to Stuff_Diff case below */
+			 /* Stuff_Diff: We have at least two *different* bytes encoded */
+        		case Stuff_Diff:
+				/* If this is a zero, must encode a Stuff_DiffZero, */
+				/* and begin a new block */
+				if (*src == 0) 
+				{
+					StuffData_FinishBlock(Stuff_DiffZero + count);
+				}
+				/* else, if we have three in a row, it is worth starting */
+				/* a Stuff_Same block */
+				else if ((*src ^ Stuff_Magic)==dst[-1] && dst[-1]==dst[-2]) 
+				{
+				/* Back off the last two characters we encoded */
+				code += count-2;
+				/* Note: "Stuff_Diff + 0" is an illegal code */
+				if (code == Stuff_Diff + 0) 
+				{
+					code = Stuff_Same + 0;
+				}
+				StuffData_FinishBlock(code);
+				code_ptr = dst-2;
+				/* dst[-1] already holds the correct value */
+				count = 2;		/* 2 means three bytes encoded */
+				code = Stuff_Same;
+			}
+			/* else, another different byte, so add it to the block */
+			else 
+                	{
+                		*dst++ = *src ^ Stuff_Magic;
+                		count++;
+                	}
+                	src++;	/* Consume the byte */
+                	break;
+		}
+		if (count == Stuff_MaxCount) 
+		{
+			StuffData_FinishBlock(code + count);
+		}
+	}
+	if (code == Stuff_NoCode) 
+	{
+		*code_ptr_ptr = NULL;
+	}
+	else 
+	{
+		*code_ptr_ptr = code_ptr;
+		StuffData_FinishBlock(code + count);
+	}	
+	return(dst);
+}
+
+/* UnStuffData decodes the data at "src", up to (but not including) "end".
+It writes the decoded data into the buffer pointed to by "dst", up to a
+maximum of "dst_length", and returns the new value of "src" so that a
+follow-on call can read more data, continuing from where the first left off.
+
+There are three types of results:
+1. The source data runs out before extracting "dst_length" bytes:
+   UnStuffData returns NULL to indicate failure.
+2. The source data produces exactly "dst_length" bytes:
+   UnStuffData returns new_src = end to indicate that all bytes were consumed.
+3. "dst_length" bytes are extracted, with more remaining.
+   UnStuffData returns new_src < end to indicate that there are more bytes
+   to be read.
+
+Note: The decoding may be dstructive, in that it may alter the source
+data in the process of decoding it (this is necessary to allow a follow-on
+call to resume correctly). */
+
+static __u8 *UnStuffData(__u8 *src, __u8 *end, __u8 *dst, __u32 dst_length)
+{
+	__u8 *dst_end = dst + dst_length;
+	/* Sanity check */
+	if (!src || !end || !dst || !dst_length) 
+		return(NULL);
+	while (src < end && dst < dst_end) 
+	{
+		int count = (*src ^ Stuff_Magic) & Stuff_CountMask;
+		switch ((*src ^ Stuff_Magic) & Stuff_CodeMask) 
+		{
+			case Stuff_Diff:
+                		if (src+1+count >= end) 
+					return(NULL);
+		                do 
+		                {
+					*dst++ = *++src ^ Stuff_Magic;
+				}
+				while(--count >= 0 && dst < dst_end);
+				if (count < 0) 
+					src += 1;
+				else 
+				{
+					if (count == 0)
+						*src = Stuff_Same ^ Stuff_Magic;
+			                else
+						*src = (Stuff_Diff + count) ^ Stuff_Magic;
+		                }
+                		break;
+			case Stuff_DiffZero:
+				if (src+1+count >= end) 
+					return(NULL);
+				do 
+				{
+					*dst++ = *++src ^ Stuff_Magic;
+				}
+				while(--count >= 0 && dst < dst_end);
+				if (count < 0)
+					*src = Stuff_Zero ^ Stuff_Magic;
+        			else
+					*src = (Stuff_DiffZero + count) ^ Stuff_Magic;
+				break;
+			case Stuff_Same:
+				if (src+1 >= end)
+     					return(NULL);
+				do 
+				{
+					*dst++ = src[1] ^ Stuff_Magic;
+				}
+				while(--count >= 0 && dst < dst_end);
+				if (count < 0)
+					src += 2;
+		                else
+					*src = (Stuff_Same + count) ^ Stuff_Magic;
+				break;
+			case Stuff_Zero:
+				do 
+				{
+					*dst++ = 0;
+				}
+				while(--count >= 0 && dst < dst_end);
+				if (count < 0)
+					src += 1;
+				else
+					*src = (Stuff_Zero + count) ^ Stuff_Magic;
+		                break;
+		}
+	}
+	if (dst < dst_end) 
+		return(NULL);
+	else
+		return(src);
+}
+
+/************************************************************************/
+/* General routines for STRIP						*/
+
+/* MTU has been changed by the IP layer. Unfortunately we are not told
+ * about this, but we spot it ourselves and fix things up. We could be in
+ * an upcall from the tty driver, or in an ip packet queue.
+ */
+
+static void strip_changedmtu(struct strip *strip_info)
+{
+	struct device *dev = &strip_info->dev;
+	unsigned char *tbuff, *rbuff, *sbuff, *otbuff, *orbuff, *osbuff;
+	int len;
+	InterruptStatus intstat;
+
+	len = STRIP_ENCAP_SIZE(dev->mtu);
+	if (len < STRIP_ENCAP_SIZE(576)) 
+		len = STRIP_ENCAP_SIZE(576);
+
+	tbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+	rbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+	sbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+	if (!tbuff || !rbuff || !sbuff) 
+	{
+        	printk("%s: unable to grow strip buffers, MTU change cancelled.\n",
+        		strip_info->dev.name);
+		dev->mtu = strip_info->mtu;
+		if (tbuff)
+			kfree(tbuff);
+		if (rbuff)
+			kfree(rbuff);
+		if (sbuff)
+			kfree(sbuff);
+		return;
+	}
+
+	intstat = DisableInterrupts();
+	otbuff = strip_info->tx_buff; strip_info->tx_buff = tbuff;
+	orbuff = strip_info->rx_buff; strip_info->rx_buff = rbuff;
+	osbuff = strip_info->sx_buff; strip_info->sx_buff = sbuff;
+	if (strip_info->tx_left) 
+	{
+		if (strip_info->tx_left <= len)
+			memcpy(strip_info->tx_buff, strip_info->tx_head, strip_info->tx_left);
+		else 
+		{
+			strip_info->tx_left = 0;
+			strip_info->tx_dropped++;
+		}
+	}
+	strip_info->tx_head = strip_info->tx_buff;
+
+	if (strip_info->sx_count) 
+	{
+		if (strip_info->sx_count <= len)
+			memcpy(strip_info->sx_buff, osbuff, strip_info->sx_count);
+		else
+		{
+			strip_info->sx_count = 0;
+			strip_info->rx_over_errors++;
+			set_bit(STR_ERROR, &strip_info->flags);
+		}
+	}
+
+	strip_info->mtu      = STRIP_ENCAP_SIZE(dev->mtu);
+	strip_info->buffsize = len;
+
+	RestoreInterrupts(intstat);
+
+	if (otbuff != NULL)
+		 kfree(otbuff);
+	if (orbuff != NULL)
+		kfree(orbuff);
+	if (osbuff != NULL)
+		kfree(osbuff);
+}
+
+static void strip_unlock(struct strip *strip_info)
+{
+	strip_info->idle_timer.expires  = jiffies + 2 * HZ;
+	add_timer(&strip_info->idle_timer);
+	if (!clear_bit(0, (void *)&strip_info->dev.tbusy))
+		printk("%s: trying to unlock already unlocked device!\n",
+			strip_info->dev.name);
+}
+
+/************************************************************************/
+/* Sending routines							*/
+
+static void ResetRadio(struct strip *strip_info)
+{	
+	static const char InitString[] = "ate0dt**starmode\r**";
+	strip_info->watchdog_doprobe = jiffies + 10 * HZ;
+	strip_info->watchdog_doreset = jiffies + 1 * HZ;
+	strip_info->tty->driver.write(strip_info->tty, 0,
+		(char *)InitString, sizeof(InitString)-1);
+}
+
+/*
+ * Called by the driver when there's room for more data.  If we have
+ * more packets to send, we send them here.
+ */
+
+static void strip_write_some_more(struct tty_struct *tty)
+{
+	InterruptStatus intstat;
+	int num_written;
+	struct strip *strip_info = (struct strip *) tty->disc_data;
+
+	/* First make sure we're connected. */
+	if (!strip_info || strip_info->magic != STRIP_MAGIC || !strip_info->dev.start)
+		return;
+
+	if (strip_info->tx_left > 0) 
+	{	/* If some data left, send it */
+		/* Must disable interrupts because othewise the write_wakeup might
+		 * happen before we've had a chance to update the tx_left and
+		 *  tx_head fields
+ 		 */
+		intstat = DisableInterrupts();
+		num_written = tty->driver.write(tty, 0, strip_info->tx_head, strip_info->tx_left);
+		strip_info->tx_left -= num_written;
+		strip_info->tx_head += num_written;
+		RestoreInterrupts(intstat);
+	}
+	else			/* Else start transmission of another packet */
+	{
+		tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+		strip_unlock(strip_info);
+		mark_bh(NET_BH);
+	}
+}
+
+
+/* Encapsulate one IP datagram. */
+
+static unsigned char *strip_stuff(unsigned char *ptr, struct strip *strip_info, struct sk_buff *skb)
+{
+    __u8         *start;
+    __u8         *stuffstate = NULL;
+    unsigned char  *icp        = skb->data;
+    int             len        = skb->len;
+    MetricomAddress haddr;
+
+    if (len > strip_info->mtu) {		/* Sigh, shouldn't occur BUT ... */
+        printk("%s: Dropping oversized transmit packet!\n", strip_info->dev.name);
+        strip_info->tx_dropped++;
+        return(NULL);
+    }
+
+    if (!arp_query(haddr.c, skb->raddr, &strip_info->dev)) {
+        IPaddr a,b,c;
+        a.l = skb->raddr;
+        b.l = skb->saddr;
+        c.l = skb->daddr;
+        printk("%s: Unknown dest %d.%d.%d.%d s=%d.%d.%d.%d d=%d.%d.%d.%d\n",
+            strip_info->dev.name,
+            a.b[0], a.b[1], a.b[2], a.b[3],
+            b.b[0], b.b[1], b.b[2], b.b[3],
+            c.b[0], c.b[1], c.b[2], c.b[3]);
+        strip_info->tx_dropped++;
+        return(NULL);
+    }
+
+    *ptr++ = '*';
+    ptr[3] = '0' + haddr.s[0] % 10; haddr.s[0] /= 10;
+    ptr[2] = '0' + haddr.s[0] % 10; haddr.s[0] /= 10;
+    ptr[1] = '0' + haddr.s[0] % 10; haddr.s[0] /= 10;
+    ptr[0] = '0' + haddr.s[0] % 10;
+    ptr+=4;
+    *ptr++ = '-';
+    ptr[3] = '0' + haddr.s[1] % 10; haddr.s[1] /= 10;
+    ptr[2] = '0' + haddr.s[1] % 10; haddr.s[1] /= 10;
+    ptr[1] = '0' + haddr.s[1] % 10; haddr.s[1] /= 10;
+    ptr[0] = '0' + haddr.s[1] % 10;
+    ptr+=4;
+    *ptr++ = '*';
+    *ptr++ = ProtocolKey.c[0];				/* Protocol key */
+    *ptr++ = ProtocolKey.c[1];
+    *ptr++ = ProtocolKey.c[2];
+    *ptr++ = ProtocolKey.c[3];
+
+    start = ptr;
+    ptr = StuffData(icp, len, ptr, &stuffstate);	/* Make payload */
+
+    *ptr++ = 0x0D;					/* Put on final delimiter */
+    return(ptr);
+}
+
+/* Encapsulate one IP datagram and stuff into a TTY queue. */
+static void strip_send(struct strip *strip_info, struct sk_buff *skb)
+{
+    unsigned char *ptr;
+
+    /* See if someone has been ifconfigging */
+    if (strip_info->mtu != STRIP_ENCAP_SIZE(strip_info->dev.mtu))
+        strip_changedmtu(strip_info);
+
+    ptr = strip_info->tx_buff;
+
+    /* If we have a packet, encapsulate it and put it in the buffer */
+    if (skb) {
+        ptr = strip_stuff(ptr, strip_info, skb);
+        /* If error, unlock and return */
+        if (!ptr) { strip_unlock(strip_info); return; }
+        strip_info->tx_packets++;	/* Count another successful packet */
+    }
+
+    /* Set up the strip_info ready to send the data */
+    strip_info->tx_head =       strip_info->tx_buff;
+    strip_info->tx_left = ptr - strip_info->tx_buff;
+    strip_info->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
+
+    /* If watchdog has expired, reset the radio */
+    if ((long)jiffies - strip_info->watchdog_doreset >= 0) {
+        printk("%s: No response: Resetting radio.\n", strip_info->dev.name);
+        ResetRadio(strip_info);
+        /* Note: if there's a packet to send, strip_write_some_more
+                 will do it after the reset has finished */
+        return;
+    }
+    
+    /* No reset.
+     * If it is time for another tickle, tack it on the end of the packet
+     */
+    if ((long)jiffies - strip_info->watchdog_doprobe >= 0) {
+        /* printk("%s: Routine radio test.\n", strip_info->dev.name); */
+        *ptr++ = '*';			/* Tickle to make radio protest */
+        *ptr++ = '*';
+        strip_info->tx_left += 2;
+        strip_info->watchdog_doprobe = jiffies + 10 * HZ;
+        strip_info->watchdog_doreset = jiffies + 1 * HZ;
+    }
+    
+    /* All ready. Start the transmission */
+    strip_write_some_more(strip_info->tty);
+}
+
+/* Encapsulate an IP datagram and kick it into a TTY queue. */
+static int strip_xmit(struct sk_buff *skb, struct device *dev)
+{
+    struct strip *strip_info = (struct strip *)(dev->priv);
+
+    if (!dev->start) {
+	printk("%s: xmit call when iface is down\n", dev->name);
+	return(1);
+    }
+    if (set_bit(0, (void *) &strip_info->dev.tbusy)) return(1);
+    del_timer(&strip_info->idle_timer);
+    strip_send(strip_info, skb);
+    if (skb) dev_kfree_skb(skb, FREE_WRITE);
+    return(0);
+}
+
+/* IdleTask periodically calls strip_xmit, so even when we have no IP packets
+   to send for an extended period of time, the watchdog processing still gets
+   done to ensure that the radio stays in Starmode */
+
+static void strip_IdleTask(unsigned long parameter)
+{
+	strip_xmit(NULL, (struct device *)parameter);
+}
+
+/************************************************************************/
+/* Receiving routines							*/
+
+static int strip_receive_room(struct tty_struct *tty)
+{
+	return 65536;  /* We can handle an infinite amount of data. :-) */
+}
+
+/* Send one completely decapsulated IP datagram to the IP layer. */
+
+static void strip_bump(struct strip *strip_info, __u16 packetlen)
+{
+	int count = sizeof(STRIP_Header) + packetlen;
+	struct sk_buff *skb = dev_alloc_skb(count);
+	if (skb == NULL) 
+	{
+		printk("%s: memory squeeze, dropping packet.\n", 
+			strip_info->dev.name);
+		strip_info->rx_dropped++;
+		return;
+	}
+	skb->dev = &strip_info->dev;
+	memcpy(skb_put(skb, count), strip_info->rx_buff, count);
+	skb->mac.raw=skb->data;
+	skb->protocol = htons(ETH_P_IP);
+	netif_rx(skb);
+	strip_info->rx_packets++;
+}
+
+static void RecvErr(char *msg, struct strip *strip_info)
+{
+	static const int MAX_RecvErr = 80;
+	__u8 *ptr = strip_info->sx_buff;
+	__u8 *end = strip_info->sx_buff + strip_info->sx_count;
+	__u8 pkt_text[MAX_RecvErr], *p = pkt_text;
+	*p++ = '\"';
+	while (ptr<end && p < &pkt_text[MAX_RecvErr-4]) 
+	{
+		if (*ptr == '\\') 
+		{
+			*p++ = '\\';
+			*p++ = '\\';
+		}
+		else 
+		{
+			if (*ptr >= 32 && *ptr <= 126) 
+				*p++ = *ptr;
+			else
+			{
+				sprintf(p, "\\%02X", *ptr);
+				p+= 3;
+			}
+		}
+	        ptr++;
+	}
+	if (ptr == end)
+	        *p++ = '\"';
+	*p++ = 0;
+	printk("%-13s%s\n", msg, pkt_text);
+	set_bit(STR_ERROR, &strip_info->flags);
+	strip_info->rx_errors++;
+}
+
+static void RecvErr_Message(struct strip *strip_info, __u8 *sendername, __u8 *msg)
+{
+	static const char ERR_001[] = "ERR_001 Not in StarMode!";
+	static const char ERR_002[] = "ERR_002 Remap handle";
+	static const char ERR_003[] = "ERR_003 Can't resolve name";
+	static const char ERR_004[] = "ERR_004 Name too small or missing";
+	static const char ERR_007[] = "ERR_007 Body too big";
+	static const char ERR_008[] = "ERR_008 Bad character in name";
+
+	if (!strncmp(msg, ERR_001, sizeof(ERR_001)-1))
+		printk("Radio %s is not in StarMode\n", sendername);
+	else if (!strncmp(msg, ERR_002, sizeof(ERR_002)-1)) 
+	{
+#ifdef notyet		/*Kernel doesn't have scanf!*/
+		int handle;
+		__u8 newname[64];
+		sscanf(msg, "ERR_002 Remap handle &%d to name %s", &handle, newname);
+		printk("Radio name %s is handle %d\n", newname, handle);
+#endif
+	}
+	else if (!strncmp(msg, ERR_003, sizeof(ERR_003)-1)) 
+		printk("Radio name <unspecified> is unknown (\"Can't resolve name\" error)\n");
+	else if (!strncmp(msg, ERR_004, sizeof(ERR_004)-1)) 
+        	strip_info->watchdog_doreset = jiffies + LONG_TIME;
+	 else if (!strncmp(msg, ERR_007, sizeof(ERR_007)-1)) 
+	 {
+		/*
+		 *	Note: This error knocks the radio back into 
+		 *	command mode. 
+		 */
+		printk("Error! Packet size <unspecified> is too big for radio.");
+		strip_info->watchdog_doreset = jiffies;		/* Do reset ASAP */
+	}
+	else if (!strncmp(msg, ERR_008, sizeof(ERR_008)-1)) 
+		printk("Name <unspecified> contains illegal character\n");
+	else 
+		RecvErr("Error Msg:", strip_info);
+}
+
+static void process_packet(struct strip *strip_info)
+{
+    __u8 *ptr = strip_info->sx_buff;
+    __u8 *end = strip_info->sx_buff + strip_info->sx_count;
+    __u8 *name, *name_end;
+    __u16 packetlen;
+
+    /* Ignore empty lines */
+    if (strip_info->sx_count == 0) return;
+
+    /* Catch 'OK' responses which show radio has fallen out of starmode */
+    if (strip_info->sx_count == 2 && ptr[0] == 'O' && ptr[1] == 'K') {
+        printk("%s: Radio is back in AT command mode: Will Reset\n",
+            strip_info->dev.name);
+        strip_info->watchdog_doreset = jiffies;		/* Do reset ASAP */
+        return;
+    }
+
+    /* Check for start of address marker, and then skip over it */
+    if (*ptr != '*') {
+        /* Catch other error messages */
+        if (ptr[0] == 'E' && ptr[1] == 'R' && ptr[2] == 'R' && ptr[3] == '_')
+            RecvErr_Message(strip_info, NULL, strip_info->sx_buff);
+        else RecvErr("No initial *", strip_info);
+        return;
+    }
+    ptr++;
+
+    /* Skip the return address */
+    name = ptr;
+    while (ptr < end && *ptr != '*') ptr++;
+
+    /* Check for end of address marker, and skip over it */
+    if (ptr == end) {
+        RecvErr("No second *", strip_info);
+        return;
+    }
+    name_end = ptr++;
+
+    /* Check for SRIP key, and skip over it */
+    if (ptr[0] != ProtocolKey.c[0] ||
+        ptr[1] != ProtocolKey.c[1] ||
+        ptr[2] != ProtocolKey.c[2] ||
+        ptr[3] != ProtocolKey.c[3]) {
+        if (ptr[0] == 'E' && ptr[1] == 'R' && ptr[2] == 'R' && ptr[3] == '_') { *name_end = 0; RecvErr_Message(strip_info, name, ptr); }
+        else RecvErr("Unrecognized protocol key", strip_info);
+        return;
+    }
+    ptr += 4;
+
+    /* Decode start of the IP packet header */
+    ptr = UnStuffData(ptr, end, strip_info->rx_buff, 4);
+    if (!ptr) {
+        RecvErr("Runt packet", strip_info);
+        return;
+    }
+
+    packetlen = ((__u16)strip_info->rx_buff[2] << 8) | strip_info->rx_buff[3];
+/*	printk("Packet %02X.%02X.%02X.%02X\n",
+        strip_info->rx_buff[0], strip_info->rx_buff[1],
+        strip_info->rx_buff[2], strip_info->rx_buff[3]);
+    printk("Got %d byte packet\n", packetlen);*/
+
+    /* Decode remainder of the IP packer */
+    ptr = UnStuffData(ptr, end, strip_info->rx_buff+4, packetlen-4);
+    if (!ptr) {
+        RecvErr("Runt packet", strip_info);
+        return;
+    }
+    strip_bump(strip_info, packetlen);
+
+    /* This turns out to be a mistake. Taking receipt of a valid packet as
+     * evidence that the radio is correctly in Starmode (and resetting the
+     * watchdog_doreset timer) is wrong.  It turns out that if the radio is
+     * in command mode, with character echo on, then the echo of the packet
+     * you sent coming back looks like a valid packet and fools this test.
+     * We should only accept the "ERR_004 Name too small or missing" message
+     * as evidence that the radio is correctly in Starmode.
+    strip_info->watchdog_doprobe = jiffies + 10 * HZ;
+    strip_info->watchdog_doreset = jiffies + LONG_TIME;
+     */
+}
+
+/*
+ * Handle the 'receiver data ready' interrupt.
+ * This function is called by the 'tty_io' module in the kernel when
+ * a block of STRIP data has been received, which can now be decapsulated
+ * and sent on to some IP layer for further processing.
+ */
+static void
+strip_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
+{
+/*	struct timeval tv;*/
+    struct strip *strip_info = (struct strip *) tty->disc_data;
+    const unsigned char *end = cp + count;
+
+    if (!strip_info || strip_info->magic != STRIP_MAGIC || !strip_info->dev.start)
+        return;
+
+    /* Argh! mtu change time! - costs us the packet part received at the change */
+    if (strip_info->mtu != STRIP_ENCAP_SIZE(strip_info->dev.mtu))
+        strip_changedmtu(strip_info);
+
+/*	do_gettimeofday(&tv);
+    printk("**** strip_receive_buf: %3d bytes at %d.%06d\n",
+        count, tv.tv_sec % 100, tv.tv_usec);*/
+    /* Read the characters out of the buffer */
+    while (cp < end) {
+        if (fp && *fp++) {
+            if (!set_bit(STR_ERROR, &strip_info->flags)) strip_info->rx_errors++;
+        }
+        else if (*cp == 0x0D) {
+            /*printk("Cut a %d byte packet (%d bytes remaining)\n",
+                strip_info->sx_count, end-cp-1);*/
+            if (!clear_bit(STR_ERROR, &strip_info->flags))
+                process_packet(strip_info);
+            strip_info->sx_count = 0;
+        }
+        else if (!test_bit(STR_ERROR, &strip_info->flags) &&
+            (strip_info->sx_count > 0 || *cp != 0x0A))
+        {
+            /* (leading newline characters are ignored) */
+            if (strip_info->sx_count < strip_info->buffsize)
+                strip_info->sx_buff[strip_info->sx_count++] = *cp;
+            else
+            {
+                set_bit(STR_ERROR, &strip_info->flags);
+                strip_info->rx_over_errors++;
+            }
+        }
+        cp++;
+    }
+}
+
+/************************************************************************/
+/* General control routines						*/
+
+/*
+ *	 Create the Ethernet MAC header for an arbitrary protocol layer 
+ *
+ *	saddr=NULL	means use device source address
+ *	daddr=NULL	means leave destination address (eg unresolved arp)
+ */
+
+static int strip_header(struct sk_buff *skb, struct device *dev, 
+	unsigned short type, void *daddr, void *saddr, unsigned len)
+{
+	return(-dev->hard_header_len);
+}
+
+/*
+ *	Rebuild the Ethernet MAC header. This is called after an ARP
+ *	(or in future other address resolution) has completed on this
+ *	sk_buff. We now let ARP fill in the other fields.
+ */
+
+/* I think this should return zero if packet is ready to send, */
+/* or non-zero if it needs more time to do an address lookup   */
+
+static int strip_rebuild_header(void *buff, struct device *dev, 
+	unsigned long dst, struct sk_buff *skb)
+{
+/*	STRIP_Header *h = (STRIP_Header *)buff;*/
+
+#ifdef CONFIG_INET
+	/* I'll use arp_find when I understand it */
+	/* Arp find returns zero if if knows the address, or if it doesn't */
+	/* know the address it sends an ARP packet and returns non-zero */
+	/*return arp_find(eth->h_dest, dst, dev, dev->pa_addr, skb)? 1 : 0;*/
+	return(0);
+#else
+	return(0);
+#endif	
+}
+
+static int strip_set_dev_mac_address(struct device *dev, void *addr)
+{
+	memcpy(dev->dev_addr, addr, 7);
+	return 0;
+}
+
+static struct enet_statistics *strip_get_stats(struct device *dev)
+{
+	static struct enet_statistics stats;
+	struct strip *strip_info = (struct strip *)(dev->priv);
+
+	memset(&stats, 0, sizeof(struct enet_statistics));
+
+	stats.rx_packets     = strip_info->rx_packets;
+	stats.tx_packets     = strip_info->tx_packets;
+	stats.rx_dropped     = strip_info->rx_dropped;
+	stats.tx_dropped     = strip_info->tx_dropped;
+	stats.tx_errors      = strip_info->tx_errors;
+	stats.rx_errors      = strip_info->rx_errors;
+	stats.rx_over_errors = strip_info->rx_over_errors;
+	return(&stats);
+}
+
+/************************************************************************/
+/* Opening and closing							*/
+
+/*
+ * Here's the order things happen:
+ * When the user runs "slattach -p strip ..."
+ *  1. The TTY module calls strip_open
+ *  2. strip_open calls strip_alloc
+ *  3.                  strip_alloc calls register_netdev
+ *  4.                  register_netdev calls strip_dev_init
+ *  5. then strip_open finishes setting up the strip_info
+ *
+ * When the user runs "ifconfig st<x> up address netmask ..."
+ *  6. strip_open_low gets called
+ *
+ * When the user runs "ifconfig st<x> down"
+ *  7. strip_close_low gets called
+ *
+ * When the user kills the slattach process
+ *  8. strip_close gets called
+ *  9. strip_close calls dev_close
+ * 10. if the device is still up, then dev_close calls strip_close_low
+ * 11. strip_close calls strip_free
+ */
+
+/* Open the low-level part of the STRIP channel. Easy! */
+
+static int strip_open_low(struct device *dev)
+{
+	struct strip *strip_info = (struct strip *)(dev->priv);
+	unsigned long len;
+
+	if (strip_info->tty == NULL) 
+		return(-ENODEV);
+
+	/*
+	 * Allocate the STRIP frame buffers:
+	 *
+	 * rbuff	Receive buffer.
+	 * tbuff	Transmit buffer.
+	 * cbuff        Temporary compression buffer.
+	 */
+
+	len = STRIP_ENCAP_SIZE(dev->mtu);
+	if (len < STRIP_ENCAP_SIZE(576)) 
+		len = STRIP_ENCAP_SIZE(576);
+	strip_info->rx_buff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL);
+	if (strip_info->rx_buff == NULL) 
+		goto norbuff;
+	strip_info->sx_buff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL);
+	if (strip_info->sx_buff == NULL) 
+		goto nosbuff;
+	strip_info->tx_buff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL);
+	if (strip_info->tx_buff == NULL) 
+		goto notbuff;
+
+	strip_info->flags   &= (1 << STR_INUSE); /* Clear ESCAPE & ERROR flags */
+	strip_info->mtu	 = STRIP_ENCAP_SIZE(dev->mtu);
+	strip_info->buffsize = len;
+	strip_info->sx_count = 0;
+	strip_info->tx_left  = 0;
+
+	/*
+	 *	Needed because address '0' is special 
+	 */
+	 
+	if (dev->pa_addr == 0) 
+		dev->pa_addr=ntohl(0xC0A80001);
+	dev->tbusy  = 0;
+	dev->start  = 1;
+
+	printk("%s: Initializing Radio.\n", strip_info->dev.name);
+	ResetRadio(strip_info);
+	strip_info->idle_timer.expires  = jiffies + 2 * HZ;
+	add_timer(&strip_info->idle_timer);
+	return(0);
+
+notbuff:
+	kfree(strip_info->sx_buff);
+nosbuff:
+	kfree(strip_info->rx_buff);
+norbuff:
+	return(-ENOMEM);
+}
+
+
+/*
+ *	Close the low-level part of the STRIP channel. Easy! 
+ */
+ 
+static int strip_close_low(struct device *dev)
+{
+	struct strip *strip_info = (struct strip *)(dev->priv);
+
+	if (strip_info->tty == NULL) 
+		return -EBUSY;
+	strip_info->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+	dev->tbusy = 1;
+	dev->start = 0;
+    
+	/*
+	 *	Free all STRIP frame buffers.
+	 */
+	if (strip_info->rx_buff) 
+	{
+		kfree(strip_info->rx_buff);
+		strip_info->rx_buff = NULL;
+	}
+	if (strip_info->sx_buff) 
+	{
+		kfree(strip_info->sx_buff); 
+		strip_info->sx_buff = NULL;
+	}
+	if (strip_info->tx_buff) 
+	{
+		kfree(strip_info->tx_buff); 
+		strip_info->tx_buff = NULL; 
+	}
+	del_timer(&strip_info->idle_timer);
+	return 0;
+}
+
+/* 
+ *	This routine is called by DDI when the
+ *	(dyamically assigned) device is registered
+ */
+ 
+static int strip_dev_init(struct device *dev)
+{
+	int i;
+	
+	/*
+	 *	Finish setting up the DEVICE info. 
+	 */
+
+	dev->trans_start        = 0;
+	dev->last_rx            = 0;
+	dev->tx_queue_len       = 30; 	/* Drop after 30 frames queued */
+
+	dev->flags              = 0;
+	dev->family             = AF_INET;
+	dev->metric             = 0;
+	dev->mtu                = STRIP_MTU;
+	dev->type               = ARPHRD_METRICOM;        /* dtang */
+	dev->hard_header_len    = 8; /*sizeof(STRIP_Header);*/
+	/*
+	 *  dev->priv                 Already holds a pointer to our struct strip 
+	 */
+
+	dev->broadcast[0]       = 0;
+	dev->dev_addr[0]        = 0;
+	dev->addr_len           = sizeof(MetricomAddress);
+	dev->pa_addr            = 0;
+	dev->pa_brdaddr         = 0;
+	dev->pa_mask            = 0;
+	dev->pa_alen            = sizeof(unsigned long);
+
+	/*
+	 *	Pointer to the interface buffers. 
+	 */
+	 
+	for (i = 0; i < DEV_NUMBUFFS; i++) 
+		skb_queue_head_init(&dev->buffs[i]);
+
+	/*
+	 *	Pointers to interface service routines. 
+	 */
+
+	dev->open               = strip_open_low;
+	dev->stop               = strip_close_low;
+	dev->hard_start_xmit    = strip_xmit;
+	dev->hard_header        = strip_header;
+	dev->rebuild_header     = strip_rebuild_header;
+	/*  dev->type_trans            unused */
+	/*  dev->set_multicast_list   unused */
+	dev->set_mac_address    = strip_set_dev_mac_address;
+	/*  dev->do_ioctl             unused */
+	/*  dev->set_config           unused */
+	dev->get_stats          = strip_get_stats;
+	return 0;
+}
+
+/*
+ *	Free a STRIP channel. 
+ */
+ 
+static void strip_free(struct strip *strip_info)
+{
+	*(strip_info->referrer) = strip_info->next;
+	if (strip_info->next) 
+		strip_info->next->referrer = strip_info->referrer;
+	strip_info->magic = 0;
+	kfree(strip_info);
+}
+
+/* 
+ *	Allocate a new free STRIP channel 
+ */
+ 
+static struct strip *strip_alloc(void)
+{
+	int channel_id = 0;
+	struct strip **s = &struct_strip_list;
+	struct strip *strip_info = (struct strip *)
+		kmalloc(sizeof(struct strip), GFP_KERNEL);
+
+	if (!strip_info) 
+		return(NULL);	/* If no more memory, return */
+
+	/*
+	 *	Clear the allocated memory 
+	 */
+	 
+	memset(strip_info, 0, sizeof(struct strip));
+
+	/*
+	 *	Search the list to find where to put our new entry
+	 *	(and in the process decide what channel number it is
+	 *	going to be) 
+	 */
+	 
+	while (*s && (*s)->dev.base_addr == channel_id) 
+	{
+		channel_id++;
+		s = &(*s)->next;
+	}
+
+	/*
+	 *	Fill in the link pointers 
+	 */
+	 
+	strip_info->next = *s;
+	if (*s) 
+		(*s)->referrer = &strip_info->next;
+	strip_info->referrer = s;
+	*s = strip_info;
+
+	set_bit(STR_INUSE, &strip_info->flags);
+	strip_info->magic = STRIP_MAGIC;
+	strip_info->tty   = NULL;
+
+	init_timer(&strip_info->idle_timer);
+	strip_info->idle_timer.data     = (long)&strip_info->dev;
+	strip_info->idle_timer.function = strip_IdleTask;
+
+	sprintf(strip_info->if_name, "st%d", channel_id);
+	strip_info->dev.name         = strip_info->if_name;
+	strip_info->dev.base_addr    = channel_id;
+	strip_info->dev.priv         = (void*)strip_info;
+	strip_info->dev.next         = NULL;
+	strip_info->dev.init         = strip_dev_init;
+
+	return(strip_info);
+}
+
+/*
+ *	Open the high-level part of the STRIP channel.
+ *	This function is called by the TTY module when the
+ *	STRIP line discipline is called for.  Because we are
+ *	sure the tty line exists, we only have to link it to
+ *	a free STRIP channel...
+ */
+
+static int strip_open(struct tty_struct *tty)
+{
+	struct strip *strip_info = (struct strip *) tty->disc_data;
+
+	/*
+	 *	First make sure we're not already connected.
+	 */
+	
+	if (strip_info && strip_info->magic == STRIP_MAGIC) 
+		return -EEXIST;
+
+	/*
+	 *	OK.  Find a free STRIP channel to use. 
+	 */
+	 
+	if ((strip_info = strip_alloc()) == NULL) 
+		return -ENFILE;
+
+	/*
+	 *	Register our newly created device so it can be ifconfig'd
+	 * strip_dev_init() will be called as a side-effect
+	 */
+     
+	if (register_netdev(&strip_info->dev) != 0) 
+	{
+		printk("strip: register_netdev() failed.\n");
+		strip_free(strip_info);
+		return -ENFILE;
+	}
+
+	strip_info->tty = tty;
+	tty->disc_data = strip_info;
+	if (tty->driver.flush_buffer) 
+		tty->driver.flush_buffer(tty);
+	if (tty->ldisc.flush_buffer) 
+		tty->ldisc.flush_buffer(tty);
+
+	/*
+	 *	Restore default settings 
+	 */
+	 
+	strip_info->dev.type = ARPHRD_METRICOM;	/* dtang */
+
+	/*
+	 *	Set tty options 
+	 */
+
+	tty->termios->c_iflag |= IGNBRK |IGNPAR;/* Ignore breaks and parity errors. */
+	tty->termios->c_cflag |= CLOCAL;	/* Ignore modem control signals. */
+	tty->termios->c_cflag &= ~HUPCL;	/* Don't close on hup */
+
+#ifdef MODULE
+	MOD_INC_USE_COUNT;
+#endif
+	/*
+	 *	Done.  We have linked the TTY line to a channel. 
+	 */
+	return(strip_info->dev.base_addr);
+}
+
+/*
+ * Close down a STRIP channel.
+ * This means flushing out any pending queues, and then restoring the
+ * TTY line discipline to what it was before it got hooked to STRIP
+ * (which usually is TTY again).
+ */
+static void strip_close(struct tty_struct *tty)
+{
+	struct strip *strip_info = (struct strip *) tty->disc_data;
+
+	/*
+	 *	First make sure we're connected. 
+	 */
+	 
+	if (!strip_info || strip_info->magic != STRIP_MAGIC) 
+		return;
+
+	dev_close(&strip_info->dev);
+	unregister_netdev(&strip_info->dev);
+    
+	tty->disc_data = 0;
+	strip_info->tty = NULL;
+	strip_free(strip_info);
+	tty->disc_data = NULL;
+#ifdef MODULE
+	MOD_DEC_USE_COUNT;
+#endif
+}
+
+
+/************************************************************************/
+/* Perform I/O control calls on an active STRIP channel.       		*/
+
+static int strip_ioctl(struct tty_struct *tty, struct file *file, 
+	unsigned int cmd, unsigned long arg)
+{
+	struct strip *strip_info = (struct strip *) tty->disc_data;
+	int err;
+
+	/*
+	 *	First make sure we're connected. 
+	 */
+	 
+	if (!strip_info || strip_info->magic != STRIP_MAGIC) 
+		return -EINVAL;
+
+	switch(cmd) 
+	{
+		case SIOCGIFNAME:
+			err = verify_area(VERIFY_WRITE, (void*)arg, 16);
+			if (err)
+				return -err;
+			memcpy_tofs((void*)arg, strip_info->dev.name, 
+				strlen(strip_info->dev.name) + 1);
+			return 0;
+
+		case SIOCSIFHWADDR:
+        		return -EINVAL;
+
+		/*
+		 *	Allow stty to read, but not set, the serial port 
+		 */
+	 
+		case TCGETS:
+		case TCGETA:
+			return n_tty_ioctl(tty, (struct file *) file, cmd, 
+				(unsigned long) arg);
+
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+/************************************************************************/
+/* Initialization							*/
+
+/*
+ *	Initialize the STRIP driver.
+ *	This routine is called at boot time, to bootstrap the multi-channel
+ *	STRIP driver
+ */
+
+#ifdef MODULE
+static
+#endif
+int strip_init_ctrl_dev(struct device *dummy)
+{
+	static struct tty_ldisc strip_ldisc;
+	int status;
+	printk("STRIP: version %s (unlimited channels)\n", STRIP_VERSION);
+
+	/*
+	 *	Fill in our line protocol discipline, and register it
+	 */
+	 
+	memset(&strip_ldisc, 0, sizeof(strip_ldisc));
+	strip_ldisc.magic	= TTY_LDISC_MAGIC;
+	strip_ldisc.flags	= 0;
+	strip_ldisc.open	= strip_open;
+	strip_ldisc.close	= strip_close;
+	strip_ldisc.read	= NULL;
+	strip_ldisc.write	= NULL;
+	strip_ldisc.ioctl	= strip_ioctl;
+	strip_ldisc.select       = NULL;
+	strip_ldisc.receive_buf  = strip_receive_buf;
+	strip_ldisc.receive_room = strip_receive_room;
+	strip_ldisc.write_wakeup = strip_write_some_more;
+	status = tty_register_ldisc(N_STRIP, &strip_ldisc);
+	if (status != 0) 
+	{
+		printk("STRIP: can't register line discipline (err = %d)\n", status);
+	}
+
+#ifdef MODULE
+	 return status;
+#else
+	/* Return "not found", so that dev_init() will unlink
+	 * the placeholder device entry for us.
+	 */
+	return ENODEV;
+#endif
+}
+
+/************************************************************************/
+/* From here down is only used when compiled as an external module        */
+
+#ifdef MODULE
+
+int init_module(void)
+{
+    return strip_init_ctrl_dev(0);
+}
+
+void cleanup_module(void)
+{
+	int i;
+	while (struct_strip_list) 
+		strip_free(struct_strip_list);
+
+	if ((i = tty_register_ldisc(N_STRIP, NULL)))  
+		printk("STRIP: can't unregister line discipline (err = %d)\n", i);
+}
+#endif /* MODULE */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this