summaryrefslogtreecommitdiffstats
path: root/drivers/net/hamradio/kiss.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/hamradio/kiss.c')
-rw-r--r--drivers/net/hamradio/kiss.c1276
1 files changed, 1276 insertions, 0 deletions
diff --git a/drivers/net/hamradio/kiss.c b/drivers/net/hamradio/kiss.c
new file mode 100644
index 000000000..f3cce6429
--- /dev/null
+++ b/drivers/net/hamradio/kiss.c
@@ -0,0 +1,1276 @@
+/*
+ * kiss.c This module implements the KISS protocol for kernel-based
+ * devices like TTY. It interfaces between a raw TTY, and the
+ * kernel's AX.25 protocol layers.
+ *
+ * Shamelessly copied from slip.c on 99/01/04:
+ * Matthias Welwarsky <dg2fef@afthd.tu-darmstadt.de>
+ *
+ * Original Authors:
+ * Laurence Culhane, <loz@holmes.demon.co.uk>
+ * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ *
+ * See slip.c for additional credits and fixes.
+ *
+ * 2000-09-21 adapted for kernel 2.4.0, it however still crashes
+ * badly when killing kissattach and trying to re-
+ * start the interface. Does the original SLIP driver
+ * do the same?
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/system.h>
+#include <asm/uaccess.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/rtnetlink.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <net/ax25.h>
+#include <net/ax25dev.h>
+#ifdef CONFIG_INET
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#endif
+#include "kiss.h"
+
+
+#define KISS_VERSION "KISS-NET3.019-NEWTTY"
+
+static int maxdev = KISS_NRUNIT;
+static kiss_ctrl_t **kiss_ctrls = NULL;
+static struct tty_ldisc kiss_ldisc;
+
+static int kiss_esc(unsigned char *p, unsigned char *d, int len);
+static void kiss_unesc(struct kiss *ks, unsigned char c);
+static int kiss_esc_crc(unsigned char *s, unsigned char *d, unsigned short crc, unsigned int len);
+static int kiss_init(struct net_device *dev);
+static int kiss_ioctl(struct tty_struct *tty, void *file, int cmd, void *arg);
+static int kiss_dev_ioctl(struct net_device *dev,struct ifreq *rq,int cmd);
+static int kiss_dev_set_mac_address(struct net_device *dev, void *addr);
+static void kiss_parameter_change_notify(struct net_device *dev, int valueno, int old, int new);
+static void kiss_update_parameters(struct net_device *dev);
+static void kiss_send_parameter(struct net_device *dev, unsigned char param, unsigned char value);
+
+static spinlock_t ks_ctrl_lock = SPIN_LOCK_UNLOCKED;
+
+/*---------------------------------------------------------------------------*/
+
+static const unsigned short Crc_16_table[] = {
+ 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
+ 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
+ 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
+ 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
+ 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
+ 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
+ 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
+ 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
+ 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
+ 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
+ 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
+ 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
+ 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
+ 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
+ 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
+ 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
+ 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
+ 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
+ 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
+ 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
+ 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
+ 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
+ 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
+ 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
+ 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
+ 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
+ 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
+ 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
+ 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
+ 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
+ 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
+ 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
+};
+
+/*---------------------------------------------------------------------------*/
+
+static unsigned short
+calc_crc_16(unsigned char *cp, int size)
+{
+ unsigned short crc = 0;
+
+ while (size--)
+ crc = (crc >> 8) ^ Crc_16_table[(crc ^ *cp++) & 0xff];
+
+ return crc;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static int
+check_crc_16(unsigned char *cp, int size)
+{
+ unsigned short crc = 0xffff;
+
+ if (size < 3)
+ return -1;
+
+ while (size--)
+ crc = (crc >> 8) ^ Crc_16_table[(crc ^ *cp++) & 0xff];
+
+ if (crc != 0)
+ return -1;
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static const unsigned short Crc_flex_table[] = {
+ 0x0f87, 0x1e0e, 0x2c95, 0x3d1c, 0x49a3, 0x582a, 0x6ab1, 0x7b38,
+ 0x83cf, 0x9246, 0xa0dd, 0xb154, 0xc5eb, 0xd462, 0xe6f9, 0xf770,
+ 0x1f06, 0x0e8f, 0x3c14, 0x2d9d, 0x5922, 0x48ab, 0x7a30, 0x6bb9,
+ 0x934e, 0x82c7, 0xb05c, 0xa1d5, 0xd56a, 0xc4e3, 0xf678, 0xe7f1,
+ 0x2e85, 0x3f0c, 0x0d97, 0x1c1e, 0x68a1, 0x7928, 0x4bb3, 0x5a3a,
+ 0xa2cd, 0xb344, 0x81df, 0x9056, 0xe4e9, 0xf560, 0xc7fb, 0xd672,
+ 0x3e04, 0x2f8d, 0x1d16, 0x0c9f, 0x7820, 0x69a9, 0x5b32, 0x4abb,
+ 0xb24c, 0xa3c5, 0x915e, 0x80d7, 0xf468, 0xe5e1, 0xd77a, 0xc6f3,
+ 0x4d83, 0x5c0a, 0x6e91, 0x7f18, 0x0ba7, 0x1a2e, 0x28b5, 0x393c,
+ 0xc1cb, 0xd042, 0xe2d9, 0xf350, 0x87ef, 0x9666, 0xa4fd, 0xb574,
+ 0x5d02, 0x4c8b, 0x7e10, 0x6f99, 0x1b26, 0x0aaf, 0x3834, 0x29bd,
+ 0xd14a, 0xc0c3, 0xf258, 0xe3d1, 0x976e, 0x86e7, 0xb47c, 0xa5f5,
+ 0x6c81, 0x7d08, 0x4f93, 0x5e1a, 0x2aa5, 0x3b2c, 0x09b7, 0x183e,
+ 0xe0c9, 0xf140, 0xc3db, 0xd252, 0xa6ed, 0xb764, 0x85ff, 0x9476,
+ 0x7c00, 0x6d89, 0x5f12, 0x4e9b, 0x3a24, 0x2bad, 0x1936, 0x08bf,
+ 0xf048, 0xe1c1, 0xd35a, 0xc2d3, 0xb66c, 0xa7e5, 0x957e, 0x84f7,
+ 0x8b8f, 0x9a06, 0xa89d, 0xb914, 0xcdab, 0xdc22, 0xeeb9, 0xff30,
+ 0x07c7, 0x164e, 0x24d5, 0x355c, 0x41e3, 0x506a, 0x62f1, 0x7378,
+ 0x9b0e, 0x8a87, 0xb81c, 0xa995, 0xdd2a, 0xcca3, 0xfe38, 0xefb1,
+ 0x1746, 0x06cf, 0x3454, 0x25dd, 0x5162, 0x40eb, 0x7270, 0x63f9,
+ 0xaa8d, 0xbb04, 0x899f, 0x9816, 0xeca9, 0xfd20, 0xcfbb, 0xde32,
+ 0x26c5, 0x374c, 0x05d7, 0x145e, 0x60e1, 0x7168, 0x43f3, 0x527a,
+ 0xba0c, 0xab85, 0x991e, 0x8897, 0xfc28, 0xeda1, 0xdf3a, 0xceb3,
+ 0x3644, 0x27cd, 0x1556, 0x04df, 0x7060, 0x61e9, 0x5372, 0x42fb,
+ 0xc98b, 0xd802, 0xea99, 0xfb10, 0x8faf, 0x9e26, 0xacbd, 0xbd34,
+ 0x45c3, 0x544a, 0x66d1, 0x7758, 0x03e7, 0x126e, 0x20f5, 0x317c,
+ 0xd90a, 0xc883, 0xfa18, 0xeb91, 0x9f2e, 0x8ea7, 0xbc3c, 0xadb5,
+ 0x5542, 0x44cb, 0x7650, 0x67d9, 0x1366, 0x02ef, 0x3074, 0x21fd,
+ 0xe889, 0xf900, 0xcb9b, 0xda12, 0xaead, 0xbf24, 0x8dbf, 0x9c36,
+ 0x64c1, 0x7548, 0x47d3, 0x565a, 0x22e5, 0x336c, 0x01f7, 0x107e,
+ 0xf808, 0xe981, 0xdb1a, 0xca93, 0xbe2c, 0xafa5, 0x9d3e, 0x8cb7,
+ 0x7440, 0x65c9, 0x5752, 0x46db, 0x3264, 0x23ed, 0x1176, 0x00ff
+};
+
+/*---------------------------------------------------------------------------*/
+
+static unsigned short
+calc_crc_flex(unsigned char *cp, int size)
+{
+ unsigned short crc = 0xffff;
+
+ while (size--)
+ crc = (crc << 8) ^ Crc_flex_table[((crc >> 8) ^ *cp++) & 0xff];
+
+ return crc;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static int
+check_crc_flex(unsigned char *cp, int size)
+{
+ unsigned short crc = 0xffff;
+
+ if (size < 3)
+ return -1;
+
+ while (size--)
+ crc = (crc << 8) ^ Crc_flex_table[((crc >> 8) ^ *cp++) & 0xff];
+
+ if ((crc & 0xffff) != 0x7070)
+ return -1;
+
+ return 0;
+}
+
+/********************************
+* Buffer administration routines:
+* kiss_alloc_bufs()
+* kiss_free_bufs()
+* kiss_realloc_bufs()
+*
+* NOTE: kiss_realloc_bufs != kiss_free_bufs + kiss_alloc_bufs, because
+* kiss_realloc_bufs provides strong atomicity and reallocation
+* on actively running device.
+*********************************/
+
+/*
+ Allocate channel buffers.
+ */
+
+static int
+kiss_alloc_bufs(struct kiss *ks, int mtu)
+{
+ int err = -ENOBUFS;
+ unsigned long len;
+ char * rbuff = NULL;
+ char * xbuff = NULL;
+
+ /*
+ * Allocate the KISS frame buffers:
+ *
+ * rbuff Receive buffer.
+ * xbuff Transmit buffer.
+ */
+ len = mtu * 2;
+
+ /*
+ * allow for arrival of larger UDP packets, even if we say not to
+ * also fixes a bug in which SunOS sends 512-byte packets even with
+ * an MSS of 128
+ */
+ if (len < 576 * 2)
+ len = 576 * 2;
+ rbuff = kmalloc(len + 4, GFP_KERNEL);
+ if (rbuff == NULL)
+ goto err_exit;
+ xbuff = kmalloc(len + 4, GFP_KERNEL);
+ if (xbuff == NULL)
+ goto err_exit;
+
+ spin_lock_bh(&ks->lock);
+ if (ks->tty == NULL) {
+ err = -ENODEV;
+ goto err_exit;
+ }
+ ks->mtu = mtu;
+ ks->buffsize = len;
+ ks->rcount = 0;
+ ks->xleft = 0;
+ rbuff = xchg(&ks->rbuff, rbuff);
+ xbuff = xchg(&ks->xbuff, xbuff);
+ err = 0;
+
+ /* Cleanup */
+err_exit:
+ spin_unlock_bh(&ks->lock);
+ if (xbuff)
+ kfree(xbuff);
+ if (rbuff)
+ kfree(rbuff);
+ return err;
+}
+
+/* Free a KISS channel buffers. */
+static void
+kiss_free_bufs(struct kiss *ks)
+{
+ void * tmp;
+
+ /* Free all KISS frame buffers. */
+ if ((tmp = xchg(&ks->rbuff, NULL)) != NULL)
+ kfree(tmp);
+ if ((tmp = xchg(&ks->xbuff, NULL)) != NULL)
+ kfree(tmp);
+}
+
+/*
+ Reallocate kiss channel buffers.
+ */
+
+static int kiss_realloc_bufs(struct kiss *ks, int mtu)
+{
+ int err = 0;
+ struct net_device *dev = ks->dev;
+ unsigned char *xbuff, *rbuff;
+ int len = (mtu + MAX_HEADER) * 2;
+
+/*
+ * allow for arrival of larger UDP packets, even if we say not to
+ * also fixes a bug in which SunOS sends 512-byte packets even with
+ * an MSS of 128
+ */
+ if (len < 576 * 2)
+ len = 576 * 2;
+
+ xbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+ rbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+
+ if (xbuff == NULL || rbuff == NULL) {
+ if (mtu >= dev->mtu) {
+ printk("%s: unable to grow kiss buffers, MTU change cancelled.\n",
+ dev->name);
+ err = -ENOBUFS;
+ }
+ goto done;
+ }
+
+ spin_lock_bh(&ks->lock);
+
+ err = -ENODEV;
+ if (ks->tty == NULL)
+ goto done_on_bh;
+
+ xbuff = xchg(&ks->xbuff, xbuff);
+ rbuff = xchg(&ks->rbuff, rbuff);
+ if (ks->xleft) {
+ if (ks->xleft <= len) {
+ memcpy(ks->xbuff, ks->xhead, ks->xleft);
+ } else {
+ ks->xleft = 0;
+ ks->stats.tx_dropped++;
+ }
+ }
+ ks->xhead = ks->xbuff;
+
+ if (ks->rcount) {
+ if (ks->rcount <= len) {
+ memcpy(ks->rbuff, rbuff, ks->rcount);
+ } else {
+ ks->rcount = 0;
+ ks->stats.rx_over_errors++;
+ ks->flags.error=1;
+ }
+ }
+ ks->mtu = mtu + MAX_HEADER;;
+ dev->mtu = mtu;
+ ks->buffsize = len;
+ err = 0;
+
+done_on_bh:
+ spin_unlock_bh(&ks->lock);
+
+done:
+ if (xbuff)
+ kfree(xbuff);
+ if (rbuff)
+ kfree(rbuff);
+ return err;
+}
+
+/* Send one completely decapsulated IP datagram to the IP layer. */
+static void
+kiss_bump(struct kiss *ks)
+{
+ struct sk_buff *skb;
+ int count;
+
+ if (ks->mode == KISS_MODE_ADAPTIVE) {
+ if (ks->rbuff[0] & 0x20) {
+ ks->mode = KISS_MODE_FLEX;
+ } else
+ if (ks->rbuff[0] & 0x80) {
+ ks->mode = KISS_MODE_SMACK;
+ }
+ }
+
+ if (ks->mode == KISS_MODE_FLEX) {
+ if (check_crc_flex(ks->rbuff, ks->rcount) < 0) {
+ ks->stats.rx_errors++;
+ return;
+ }
+ ks->rcount -= 2;
+ } else if (ks->mode == KISS_MODE_SMACK) {
+ if (check_crc_16(ks->rbuff, ks->rcount) < 0) {
+ ks->stats.rx_errors++;
+ return;
+ }
+ ks->rcount -= 2;
+ }
+
+ count = ks->rcount-1;
+ ks->stats.rx_bytes += count;
+
+ skb = dev_alloc_skb(count+MAX_HEADER);
+ if (skb == NULL) {
+ printk("%s: memory squeeze, dropping packet.\n", ks->dev->name);
+ ks->stats.rx_dropped++;
+ return;
+ }
+ skb_reserve(skb, MAX_HEADER);
+ skb->dev = ks->dev;
+ memcpy(skb_put(skb,count), ks->rbuff+1, count);
+ skb->mac.raw = skb->data;
+ skb->protocol = __constant_htons(ETH_P_AX25);
+ netif_rx(skb);
+ ks->stats.rx_packets++;
+}
+
+/* Encapsulate one IP datagram and stuff into a TTY queue. */
+static void
+kiss_encaps(struct kiss *ks, unsigned char *icp, int len)
+{
+ unsigned char *p;
+ int actual, count;
+
+ if (len > ks->mtu) { /* Sigh, shouldn't occur BUT ... */
+ printk ("%s: dropping oversized transmit packet!\n", ks->dev->name);
+ ks->stats.tx_dropped++;
+ netif_wake_queue(ks->dev);
+ return;
+ }
+
+ p = icp;
+
+ if (*p == 0) {
+ /* KISS data */
+ switch (ks->mode) {
+ unsigned short crc;
+
+ case KISS_MODE_FLEX:
+ *p |= 0x20;
+ crc = calc_crc_flex(p, len);
+ count = kiss_esc_crc(p, ks->xbuff, crc, len+2);
+ break;
+ case KISS_MODE_SMACK:
+ *p |= 0x80;
+ crc = calc_crc_16(p, len);
+ count = kiss_esc_crc(p, ks->xbuff, crc, len+2);
+ break;
+ default:
+ count = kiss_esc(p, ks->xbuff, len);
+ break;
+ }
+ } else {
+ /* KISS parameter */
+ ks->xbuff[0] = END;
+ memcpy(&ks->xbuff[1], p, len);
+ ks->xbuff[len+1] = END;
+ count = len+2;
+ }
+
+ /* Order of next two lines is *very* important.
+ * When we are sending a little amount of data,
+ * the transfer may be completed inside driver.write()
+ * routine, because it's running with interrupts enabled.
+ * In this case we *never* got WRITE_WAKEUP event,
+ * if we did not request it before write operation.
+ * 14 Oct 1994 Dmitry Gorodchanin.
+ */
+ ks->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
+ actual = ks->tty->driver.write(ks->tty, 0, ks->xbuff, count);
+ ks->dev->trans_start = jiffies;
+ ks->xleft = count - actual;
+ ks->xhead = ks->xbuff + actual;
+}
+
+/*
+ * Called by the driver when there's room for more data. If we have
+ * more packets to send, we send them here.
+ */
+static void kiss_write_wakeup(struct tty_struct *tty)
+{
+ int actual;
+ struct kiss *ks = (struct kiss *) tty->disc_data;
+
+ /* First make sure we're connected. */
+ if (!ks || !netif_running(ks->dev)) {
+ return;
+ }
+ if (ks->xleft <= 0) {
+ /* Now serial buffer is almost free & we can start
+ * transmission of another packet */
+ ks->stats.tx_packets++;
+ tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+ netif_wake_queue(ks->dev);
+ return;
+ }
+
+ actual = tty->driver.write(tty, 0, ks->xhead, ks->xleft);
+ ks->xleft -= actual;
+ ks->xhead += actual;
+}
+
+/* Encapsulate an IP datagram and kick it into a TTY queue. */
+static int
+kiss_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct kiss *ks = (struct kiss*)(dev->priv);
+
+ spin_lock(&ks->lock);
+
+ /* ldisc disappeared under our feet, ouch! */
+ if (ks->tty == NULL)
+ goto done;
+
+ /* rant by dl1bke:
+ * There used to be a completely useless copy of
+ * the skb here. If there really _is_ a race condition
+ * fix the upper protocols, dammit! Masquearading these
+ * problems doesn't help at all, they'll rear their ugly
+ * head somewhere else again anyway!
+ */
+
+ /* We were not busy, so we are now... :-) */
+ if (skb_headroom(skb) > 0)
+ {
+ *skb_push(skb, 1) = 0; /* prepend KISS header */
+ netif_stop_queue(dev);
+ ks->stats.tx_bytes+=skb->len;
+ kiss_encaps(ks, skb->data, skb->len);
+ } else {
+ ks->stats.tx_dropped++;
+ }
+
+done:
+ dev_kfree_skb(skb);
+ spin_unlock(&ks->lock);
+ return 0;
+}
+
+
+/******************************************
+ * Routines looking at netdevice side.
+ ******************************************/
+
+/* Netdevice UP -> DOWN routine */
+
+static int
+kiss_dev_close(struct net_device *dev)
+{
+ struct kiss *ks = (struct kiss*)(dev->priv);
+
+ spin_lock_bh(&ks->lock);
+ if (ks->tty) {
+ /* TTY discipline is running. */
+ ks->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+ }
+
+ ks->rcount = 0;
+ ks->xleft = 0;
+ netif_stop_queue(dev);
+ spin_unlock_bh(&ks->lock);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/* Netdevice DOWN -> UP routine */
+
+static int kiss_dev_open(struct net_device *dev)
+{
+ struct kiss *ks = (struct kiss*)(dev->priv);
+
+ if (ks->tty==NULL)
+ return -ENODEV;
+
+ ks->flags.in_use = 1;
+ ks->flags.error = 0;
+ ks->flags.escape = 0;
+
+ MOD_INC_USE_COUNT;
+
+ netif_start_queue(dev);
+ return 0;
+}
+
+/* Netdevice change MTU request */
+
+static int kiss_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct kiss *ks = (struct kiss*)(dev->priv);
+
+ if (new_mtu < 68 || new_mtu > 65534)
+ return -EINVAL;
+
+ if (new_mtu != dev->mtu)
+ return kiss_realloc_bufs(ks, new_mtu);
+ return 0;
+}
+
+/* Netdevice get statistics request */
+
+static struct net_device_stats *
+kiss_get_stats(struct net_device *dev)
+{
+ struct kiss *ks = (struct kiss*)(dev->priv);
+
+ return (&ks->stats);
+}
+
+/* Netdevice register callback */
+
+static int kiss_init(struct net_device *dev)
+{
+ struct kiss *ks = (struct kiss*)(dev->priv);
+
+ /*
+ * Finish setting up the DEVICE info.
+ */
+
+ dev->mtu = ks->mtu - MAX_HEADER;
+ dev->hard_start_xmit = kiss_xmit;
+ dev->open = kiss_dev_open;
+ dev->stop = kiss_dev_close;
+ dev->get_stats = kiss_get_stats;
+ dev->change_mtu = kiss_change_mtu;
+ dev->do_ioctl = kiss_dev_ioctl;
+ dev->set_mac_address = kiss_dev_set_mac_address;
+ dev->hard_header_len = AX25_MAX_HEADER_LEN;
+ dev->addr_len = AX25_ADDR_LEN;
+ dev->type = ARPHRD_AX25;
+ dev->tx_queue_len = 10;
+
+ dev_init_buffers(dev);
+
+ /* New-style flags. */
+ dev->flags = IFF_BROADCAST | IFF_MULTICAST;
+
+ /* initialise DDI interface values */
+ AX25_PTR(dev) = &ks->ax25dev;
+ memset(AX25_PTR(dev), 0, sizeof(struct ax25_dev));
+ AX25_PTR(dev)->hw.fast = 1;
+ AX25_PTR(dev)->hw.dcd = NULL;
+ AX25_PTR(dev)->hw.ptt = NULL;
+ AX25_PTR(dev)->hw.cts = NULL;
+ AX25_PTR(dev)->hw.rts = NULL;
+ AX25_PTR(dev)->hw.parameter_change_notify = kiss_parameter_change_notify;
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_DUPLEX, 0);
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_TXDELAY, 200);
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_TXTAIL, 20);
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_SLOTTIME, 10);
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_PPERSISTENCE, 64);
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_AUTO_ADJUST, 0);
+ return 0;
+}
+
+static void kiss_send_parameter(struct net_device *dev, unsigned char param, unsigned char value)
+{
+ struct kiss *ks = (struct kiss *) dev->priv;
+ unsigned char buf[2] = {param, value};
+
+ kiss_encaps(ks, buf, 2);
+ return;
+}
+
+static void kiss_update_parameters(struct net_device *dev)
+{
+ kiss_send_parameter(dev, KISS_PARAM_TXDELAY,
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXDELAY)/10);
+ kiss_send_parameter(dev, KISS_PARAM_PPERS,
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_PPERSISTENCE));
+ kiss_send_parameter(dev, KISS_PARAM_SLOT,
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_SLOTTIME)/10);
+ kiss_send_parameter(dev, KISS_PARAM_DUPLEX,
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_DUPLEX));
+ kiss_send_parameter(dev, KISS_PARAM_TXTAIL,
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXTAIL)/10);
+ return;
+}
+
+static void kiss_parameter_change_notify(struct net_device *dev, int valueno, int old, int new)
+{
+ struct kiss *ks = (struct kiss *) dev->priv;
+ int br;
+
+ switch (valueno) {
+ case AX25_VALUES_MEDIA_RXBITRATE:
+ case AX25_VALUES_MEDIA_TXBITRATE:
+ /*
+ * If anybody knows how to set TTY's baud rate
+ * from kernel mode please add it here. We currently
+ * reject baud rate change requests.
+ */
+ br = tty_get_baud_rate(ks->tty);
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_TXBITRATE, br);
+ ax25_dev_set_value(dev, AX25_VALUES_MEDIA_RXBITRATE, br);
+ break;
+ case AX25_VALUES_MEDIA_DUPLEX:
+ case AX25_VALUES_MEDIA_TXDELAY:
+ case AX25_VALUES_MEDIA_TXTAIL:
+ case AX25_VALUES_MEDIA_SLOTTIME:
+ case AX25_VALUES_MEDIA_PPERSISTENCE:
+ kiss_update_parameters(dev);
+ case AX25_VALUES_MEDIA_AUTO_ADJUST:
+ default:
+ break;
+ }
+ return;
+}
+
+/******************************************
+ Routines looking at TTY side.
+ ******************************************/
+
+
+static int kiss_receive_room(struct tty_struct *tty)
+{
+ return 65536; /* We can handle an infinite amount of data. :-) */
+}
+
+/*
+ * Handle the 'receiver data ready' interrupt.
+ * This function is called by the 'tty_io' module in the kernel when
+ * a block of KISS data has been received, which can now be decapsulated
+ * and sent on to some IP layer for further processing.
+ */
+
+static void kiss_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
+{
+ struct kiss *ks = (struct kiss *) tty->disc_data;
+
+ if (!ks || !netif_running(ks->dev))
+ return;
+
+ /* Read the characters out of the buffer */
+ while (count--) {
+ if (fp && *fp++) {
+ if (!ks->flags.error)
+ {
+ ks->flags.error = 1;
+ ks->stats.rx_errors++;
+ }
+ cp++;
+ continue;
+ }
+ kiss_unesc(ks, *cp++);
+ }
+}
+
+/************************************
+ * kiss_open helper routines.
+ ************************************/
+
+/* Collect hanged up channels */
+
+static void kiss_sync(void)
+{
+ int i;
+
+ spin_lock_bh(&ks_ctrl_lock);
+ for (i = 0; i < maxdev; i++) {
+ kiss_ctrl_t *ksp = kiss_ctrls[i];
+ if (ksp == NULL)
+ break;
+ if (ksp->ctrl.tty)
+ continue;
+ if (ksp->dev.flags & IFF_UP)
+ dev_close(&ksp->dev);
+ }
+ spin_unlock_bh(&ks_ctrl_lock);
+}
+
+/* Find a free KISS channel, and link in this `tty' line. */
+static struct kiss *
+kiss_alloc(kdev_t line)
+{
+ struct kiss *ks;
+ kiss_ctrl_t *ksp = NULL;
+ int i;
+ int sel = -1;
+ int score = -1;
+
+ if (kiss_ctrls == NULL)
+ return NULL; /* Master array missing ! */
+
+ spin_lock_bh(&ks_ctrl_lock);
+
+ for (i = 0; i < maxdev; i++) {
+ ksp = kiss_ctrls[i];
+ if (ksp == NULL)
+ break;
+
+ if (ksp->ctrl.tty)
+ continue;
+
+ if (current->pid == ksp->ctrl.pid) {
+ if (ksp->ctrl.line == line && score < 3) {
+ sel = i;
+ score = 3;
+ continue;
+ }
+ if (score < 2) {
+ sel = i;
+ score = 2;
+ }
+ continue;
+ }
+ if (ksp->ctrl.line == line && score < 1) {
+ sel = i;
+ score = 1;
+ continue;
+ }
+ if (score < 0) {
+ sel = i;
+ score = 0;
+ }
+ }
+
+ if (sel >= 0) {
+ i = sel;
+ ksp = kiss_ctrls[i];
+ if (score > 1) {
+ ksp->ctrl.flags.in_use = 1;
+ ksp->ctrl.flags.error = 0;
+ ksp->ctrl.flags.escape = 0;
+ goto done;
+ }
+ }
+
+ /* Sorry, too many, all slots in use */
+ if (i >= maxdev)
+ goto outahere;
+
+ if (ksp) {
+ if (ksp->ctrl.flags.in_use) {
+ unregister_netdevice(&ksp->dev);
+ kiss_free_bufs(&ksp->ctrl);
+ }
+ } else if ((ksp = (kiss_ctrl_t *)kmalloc(sizeof(kiss_ctrl_t),GFP_KERNEL)) == NULL)
+ goto outahere;
+
+ memset(ksp, 0, sizeof(kiss_ctrl_t));
+
+ ks = &ksp->ctrl;
+ /* Initialize channel control data */
+ ks->dev = &ksp->dev;
+ ks->mode = KISS_MODE_ADAPTIVE;
+ sprintf(ksp->dev.name, "ax%d", i);
+ ksp->dev.base_addr = i;
+ ksp->dev.priv = (void*)ks;
+ ksp->dev.init = kiss_init;
+ kiss_ctrls[i] = ksp;
+
+done:
+ spin_lock_init(&ksp->ctrl.lock);
+ spin_unlock_bh(&ks_ctrl_lock);
+ return &ksp->ctrl;
+
+outahere:
+ spin_unlock_bh(&ks_ctrl_lock);
+ return NULL;
+}
+
+/*
+ * Open the high-level part of the KISS channel.
+ * This function is called by the TTY module when the
+ * KISS line discipline is called for. Because we are
+ * sure the tty line exists, we only have to link it to
+ * a free KISS channel...
+ */
+static int
+kiss_open(struct tty_struct *tty)
+{
+ struct kiss *ks;
+ int err;
+
+ MOD_INC_USE_COUNT;
+
+ /* Collect hanged up channels. */
+ kiss_sync();
+
+ ks = (struct kiss *) tty->disc_data;
+ err = -EEXIST;
+ /* First make sure we're not already connected. */
+ if (ks) goto err_exit;
+
+ /* OK. Find a free KISS channel to use. */
+ err = -ENFILE;
+ if ((ks = kiss_alloc(tty->device)) == NULL)
+ goto err_exit;
+
+ ks->tty = tty;
+ tty->disc_data = ks;
+ ks->line = tty->device;
+ ks->pid = current->pid;
+ if (tty->driver.flush_buffer)
+ tty->driver.flush_buffer(tty);
+ if (tty->ldisc.flush_buffer)
+ tty->ldisc.flush_buffer(tty);
+
+ if (! ks->flags.in_use) {
+ /* Perform the low-level KISS initialization. */
+ if ((err = kiss_alloc_bufs(ks, KISS_MTU)) != 0)
+ goto err_free_chan;
+ if (register_netdevice(ks->dev)) {
+ kiss_free_bufs(ks);
+ goto err_free_chan;
+ }
+
+ ks->flags.in_use = 1;
+ }
+
+ return ks->dev->base_addr;
+
+err_free_chan:
+
+ ks->tty = NULL;
+ tty->disc_data = NULL;
+ ks->flags.in_use = 0;
+
+err_exit:
+ printk(KERN_DEBUG "kiss_open: err:%d\n", err);
+
+ /* Count references from TTY module */
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+/*
+ Let me to blame a bit.
+ 1. TTY module calls this funstion on soft interrupt.
+ 2. TTY module calls this function WITH MASKED INTERRUPTS!
+ 3. TTY module does not notify us about line discipline
+ shutdown,
+
+ Seems, now it is clean. The solution is to consider netdevice and
+ line discipline sides as two independent threads.
+
+ By-product (not desired): sl? does not feel hangups and remains open.
+ It is supposed, that user level program (dip, diald, slattach...)
+ will catch SIGHUP and make the rest of work.
+
+ I see no way to make more with current tty code. --ANK
+ */
+
+/*
+ * Close down a KISS channel.
+ * This means flushing out any pending queues, and then restoring the
+ * TTY line discipline to what it was before it got hooked to KISS
+ * (which usually is TTY again).
+ */
+static void
+kiss_close(struct tty_struct *tty)
+{
+ struct kiss *ks = (struct kiss *) tty->disc_data;
+
+ /* First make sure we're connected. */
+ if (!ks || ks->tty != tty)
+ return;
+
+ tty->disc_data = 0;
+ ks->tty = NULL;
+ ks->line = 0;
+
+ /* Count references from TTY module */
+ MOD_DEC_USE_COUNT;
+}
+
+ /************************************************************************
+ * STANDARD KISS ENCAPSULATION *
+ ************************************************************************/
+
+int
+kiss_esc(unsigned char *s, unsigned char *d, int len)
+{
+ unsigned char *ptr = d;
+ unsigned char c;
+
+ /*
+ * Send an initial END character to flush out any
+ * data that may have accumulated in the receiver
+ * due to line noise.
+ */
+
+ *ptr++ = END;
+
+ /*
+ * For each byte in the packet, send the appropriate
+ * character sequence, according to the KISS protocol.
+ */
+
+ while (len-- > 0) {
+ switch(c = *s++) {
+ case END:
+ *ptr++ = ESC;
+ *ptr++ = ESC_END;
+ break;
+ case ESC:
+ *ptr++ = ESC;
+ *ptr++ = ESC_ESC;
+ break;
+ default:
+ *ptr++ = c;
+ break;
+ }
+ }
+ *ptr++ = END;
+ return (ptr - d);
+}
+
+/*
+ * MW:
+ * OK its ugly, but tell me a better solution without copying the
+ * packet to a temporary buffer :-)
+ */
+static int kiss_esc_crc(unsigned char *s, unsigned char *d, unsigned short crc, unsigned int len)
+{
+ unsigned char *ptr = d;
+ unsigned char c = 0;
+
+ *ptr++ = END;
+ while (len > 0) {
+ if (len > 2)
+ c = *s++;
+ else if (len > 1)
+ c = crc >> 8;
+ else if (len > 0)
+ c = crc & 0xff;
+
+ len--;
+
+ switch (c) {
+ case END:
+ *ptr++ = ESC;
+ *ptr++ = ESC_END;
+ break;
+ case ESC:
+ *ptr++ = ESC;
+ *ptr++ = ESC_ESC;
+ break;
+ default:
+ *ptr++ = c;
+ break;
+ }
+ }
+ *ptr++ = END;
+ return ptr - d;
+}
+
+static void kiss_unesc(struct kiss *ks, unsigned char s)
+{
+
+ switch(s) {
+ case END:
+ if (! ks->flags.error && (ks->rcount > 2))
+ kiss_bump(ks);
+
+ ks->flags.error = 0;
+ ks->flags.escape = 0;
+ ks->rcount = 0;
+ return;
+
+ case ESC:
+ ks->flags.escape = 1;
+ return;
+ case ESC_ESC:
+ if (ks->flags.escape)
+ {
+ s = ESC;
+ ks->flags.escape = 0;
+ }
+ break;
+ case ESC_END:
+ if (ks->flags.escape) {
+ s = END;
+ ks->flags.escape = 0;
+ }
+ break;
+ }
+ if (!ks->flags.error) {
+ if (ks->rcount < ks->buffsize) {
+ ks->rbuff[ks->rcount++] = s;
+ } else {
+ ks->stats.rx_over_errors++;
+ ks->flags.error = 1;
+ }
+ }
+}
+
+
+static int kiss_set_mac_address(struct net_device *dev, void *addr)
+{
+ if (copy_from_user(dev->dev_addr, addr, AX25_ADDR_LEN))
+ return -EFAULT;
+ return 0;
+}
+
+static int kiss_dev_set_mac_address(struct net_device *dev, void *addr)
+{
+ struct sockaddr *sa = addr;
+
+ memcpy(dev->dev_addr, sa->sa_data, AX25_ADDR_LEN);
+ return 0;
+}
+
+/* Perform I/O control on an active KISS channel. */
+static int
+kiss_ioctl(struct tty_struct *tty, void *file, int cmd, void *arg)
+{
+ struct kiss *ks = (struct kiss *) tty->disc_data;
+ unsigned int tmp;
+
+ /* First make sure we're connected. */
+ if (!ks) return -EINVAL;
+
+ switch(cmd) {
+ case SIOCGIFNAME:
+ tmp = strlen(ks->dev->name) + 1;
+ if (copy_to_user(arg, ks->dev->name, tmp))
+ return -EFAULT;
+ return 0;
+
+ case SIOCSIFHWADDR:
+ return kiss_set_mac_address(ks->dev, arg);
+
+ case SIOCGIFENCAP:
+ if (put_user(ks->mode, (int *) arg))
+ return -EFAULT;
+ return 0;
+ case SIOCSIFENCAP:
+ if (get_user(tmp, (int *) arg))
+ return -EFAULT;
+ if (tmp < 0 || tmp > KISS_MODE_TNN_TOKENRING)
+ return -EINVAL;
+ if (tmp == KISS_MODE_COMPAT) tmp = KISS_MODE_ADAPTIVE;
+ ks->mode = tmp;
+ return 0;
+
+ /* 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;
+ }
+}
+
+static int kiss_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ int retval = -ENOIOCTLCMD;
+ struct kiss *ks = (struct kiss *)dev->priv;
+
+ if (ks == NULL)
+ return -ENODEV;
+
+ spin_lock_bh(&ks->lock);
+
+ if (!ks->tty) {
+ retval = -ENODEV;
+ goto done;
+ }
+
+ printk(KERN_DEBUG "ioctl %d called for dev %s\n", cmd, dev->name);
+
+ switch(cmd) {
+ default:
+ retval = 0;
+ break;
+ }
+
+done:
+ spin_unlock_bh(&ks->lock);
+ return retval;
+}
+
+/* Initialize KISS control device -- register KISS line discipline */
+
+int __init kiss_init_driver(void)
+{
+ int status;
+
+ if (maxdev < KISS_NRUNIT) maxdev = KISS_NRUNIT; /* Sanity */
+
+ printk(KERN_INFO "KISS: version %s (dynamic channels, max=%d)\n",
+ KISS_VERSION, maxdev);
+
+ kiss_ctrls = (kiss_ctrl_t **) kmalloc(sizeof(void*)*maxdev, GFP_KERNEL);
+ if (kiss_ctrls == NULL)
+ {
+ printk("KISS: Can't allocate kiss_ctrls[] array! Uaargh! (-> No KISS available)\n");
+ return -ENOMEM;
+ }
+
+ /* Clear the pointer array, we allocate devices when we need them */
+ memset(kiss_ctrls, 0, sizeof(void*)*maxdev); /* Pointers */
+
+ /* Fill in our line protocol discipline, and register it */
+ memset(&kiss_ldisc, 0, sizeof(kiss_ldisc));
+ kiss_ldisc.magic = TTY_LDISC_MAGIC;
+ kiss_ldisc.name = "kiss";
+ kiss_ldisc.flags = 0;
+ kiss_ldisc.open = kiss_open;
+ kiss_ldisc.close = kiss_close;
+ kiss_ldisc.read = NULL;
+ kiss_ldisc.write = NULL;
+ kiss_ldisc.ioctl = (int (*)(struct tty_struct *, struct file *,
+ unsigned int, unsigned long)) kiss_ioctl;
+ kiss_ldisc.poll = NULL;
+ kiss_ldisc.receive_buf = kiss_receive_buf;
+ kiss_ldisc.receive_room = kiss_receive_room;
+ kiss_ldisc.write_wakeup = kiss_write_wakeup;
+ if ((status = tty_register_ldisc(N_KISS, &kiss_ldisc)) != 0) {
+ printk("KISS: can't register line discipline (err = %d)\n", status);
+ }
+
+
+ return status;
+
+ /* Return "not found", so that dev_init() will unlink
+ * the placeholder device entry for us.
+ */
+ return -ENODEV;
+ }
+
+
+void __exit kiss_exit_driver(void)
+{
+ int i;
+
+ if (kiss_ctrls != NULL) {
+ unsigned long start = jiffies;
+ int busy = 0;
+
+ /* First of all: check for active disciplines and hangup them.
+ */
+ do {
+ if (busy) {
+ current->counter = 0;
+ schedule();
+ }
+
+ busy = 0;
+ spin_lock_bh(&ks_ctrl_lock);
+ for (i = 0; i < maxdev; i++) {
+ struct kiss_ctrl *ksc = kiss_ctrls[i];
+ if (ksc && ksc->ctrl.tty) {
+ busy++;
+ tty_hangup(ksc->ctrl.tty);
+ }
+ }
+ spin_unlock_bh(&ks_ctrl_lock);
+ } while (busy && jiffies - start < 1*HZ);
+
+ busy = 0;
+ for (i = 0; i < maxdev; i++) {
+ struct kiss_ctrl *ksc = kiss_ctrls[i];
+ if (ksc) {
+ unregister_netdev(&ksc->dev);
+ if (ksc->ctrl.tty) {
+ printk("%s: tty discipline is still running\n", ksc->dev.name);
+ /* Pin module forever */
+ MOD_INC_USE_COUNT;
+ busy++;
+ continue;
+ }
+ kiss_free_bufs(&ksc->ctrl);
+ kfree(ksc);
+ kiss_ctrls[i] = NULL;
+ }
+ }
+ if (!busy) {
+ kfree(kiss_ctrls);
+ kiss_ctrls = NULL;
+ }
+ }
+ if ((i = tty_register_ldisc(N_KISS, NULL)))
+ {
+ printk("KISS: can't unregister line discipline (err = %d)\n", i);
+ }
+}
+
+MODULE_PARM(maxdev, "i");
+MODULE_AUTHOR("Matthias Welwarsky <dg2fef@afthd.tu-darmstadt.de>");
+MODULE_DESCRIPTION("KISS driver for AX.25");
+module_init(kiss_init_driver);
+module_exit(kiss_exit_driver);