diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2015-06-24 04:23:46 +0200 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2015-06-24 10:03:18 +0200 |
commit | e5067d7cd967cb17067de24a162306b79f432b20 (patch) | |
tree | 541f101762df32a5742bec354009986a96d8e564 /drivers/net/hamradio/kiss.c | |
parent | 86a981e836404006efc35881ebf3d5ae36925e82 (diff) |
Import newax25-2.4.3.patch.1.bz2HEADnewax25-2.4.3-1
And cleanup the *.orig and *.rej files and whitespace errors that are part
of the original patch.
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'drivers/net/hamradio/kiss.c')
-rw-r--r-- | drivers/net/hamradio/kiss.c | 1276 |
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); |