/* * 6pack.c This module implements the 6pack protocol for kernel-based * devices like TTY. It interfaces between a raw TTY and the * kernel's new AX.25 protocol layer. * * Version: @(#)6pack.c 0.4.0 03/05/2000 * * Authors: Andreas Könsgen * Jens David (rework) * * Quite a lot of stuff "stolen" by Jörg Reuter from slip.c, written by * * Laurence Culhane, * Fred N. van Kempen, * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "6pack.h" #define min(a, b) (((a) < (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b)) /* ----------------------------------------------------------------------- */ static struct sixpack_ctrl_t **sixpack_ctrls = NULL; int sixpack_maxdev = SIXP_NRUNIT; /* override with insmod */ /* ----------------------------------------------------------------------- */ /* prototypes */ static void resync_tnc(unsigned long); static void sixpack_decode(struct sixpack *, unsigned char[], int); static int encode_sixpack(unsigned char *, unsigned char *, int, unsigned int); static void decode_prio_command(unsigned char, struct sixpack *); static void decode_std_command(unsigned char, struct sixpack *); static void decode_data(unsigned char, struct sixpack *); static int tnc_init(struct sixpack *); static unsigned int sixpack_ddi_report_dcd(struct net_device *dev); static unsigned int sixpack_ddi_report_ptt(struct net_device *dev); static void sixpack_parameter_change_notify(struct net_device *dev, int valueno, int old, int new); static int sixpack_init(struct net_device *dev); /* ----------------------------------------------------------------------- */ /* Find a free 6pack channel, and link in this `tty' line. */ static inline struct sixpack *sp_alloc(struct tty_struct *tty) { struct sixpack_ctrl_t *spp = NULL; int i; if (sixpack_ctrls == NULL) return NULL; /* Master array missing ! */ for (i = 0; i < sixpack_maxdev; i++) { spp = sixpack_ctrls[i]; if (spp == NULL) break; if (!spp->ctrl.flags.in_use) { spp->ctrl.flags.in_use = 1; break; } } /* Too many devices... */ if (i >= sixpack_maxdev) return NULL; /* If no channels are available, allocate one */ if (!spp && (sixpack_ctrls[i] = (struct sixpack_ctrl_t *) kmalloc(sizeof(struct sixpack_ctrl_t), GFP_KERNEL)) != NULL) { spp = sixpack_ctrls[i]; memset(spp, 0, sizeof(struct sixpack_ctrl_t)); /* Initialize channel control data */ spp->ctrl.flags.in_use= 1; sprintf(spp->dev.name, "sp%d", i); spp->dev.base_addr = i; spp->dev.priv = (void*)&(spp->ctrl); spp->dev.next = NULL; spp->dev.init = sixpack_init; } if (spp != NULL) { spp->ctrl.tty = tty; /* * register device so that it can be ifconfig'ed * sixpack_init() will be called as a side-effect * SIDE-EFFECT WARNING: sixpack_init() CLEARS spp->ctrl ! */ if (register_netdev(&(spp->dev)) == 0) { spp->ctrl.flags.in_use = 1; spp->ctrl.dev = &(spp->dev); spp->dev.priv = (void*)&(spp->ctrl); return (&(spp->ctrl)); } else { spp->ctrl.flags.in_use = 0; printk(KERN_WARNING "sp_alloc() - register_netdev() failure.\n"); } } return NULL; } /* ----------------------------------------------------------------------- */ /* Free a 6pack channel. */ static inline void sp_free(struct sixpack *sp) { /* Free all 6pack frame buffers. */ if (sp->rbuff) kfree(sp->rbuff); sp->rbuff = NULL; if (sp->xbuff) { kfree(sp->xbuff); } sp->xbuff = NULL; } /* ----------------------------------------------------------------------- */ /* Send one completely decapsulated IP datagram to the IP layer. */ static void sp_bump(struct sixpack *sp, char cmd) { struct sk_buff *skb; int count; unsigned char *ptr; count = sp->rcount; sp->stats.rx_bytes += count; skb = dev_alloc_skb(count); if (skb == NULL) { printk(KERN_DEBUG "%s: memory squeeze, dropping packet.\n", sp->dev->name); sp->stats.rx_dropped++; return; } skb->dev = sp->dev; ptr = skb_put(skb, count); memcpy(ptr, (sp->cooked_buf)+1, count); skb->mac.raw=skb->data; skb->protocol=htons(ETH_P_AX25); netif_rx(skb); sp->stats.rx_packets++; } /* ----------------------------------------------------------------------- */ /* Encapsulate one AX.25 frame and stuff into a TTY queue. */ static void sp_encaps(struct sixpack *sp, unsigned char *icp, int len) { unsigned char *p; int actual, count; int tx_delay; if (len > sp->mtu) { len = sp->mtu; printk(KERN_DEBUG "%s: dropping oversized transmit packet!\n", sp->dev->name); sp->stats.tx_dropped++; netif_wake_queue(sp->dev); return; } p = icp; tx_delay = ax25_dev_get_value(sp->dev, AX25_VALUES_MEDIA_TXDELAY); if (tx_delay > 2550) tx_delay = 2550; count = encode_sixpack(p, (unsigned char *) sp->xbuff, len, tx_delay); sp->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); sp->led_state = 0x70; sp->tty->driver.write(sp->tty, 0, &(sp->led_state), 1); sp->tx_counter++; /* TX counter +1 */ sp->tx_enable = 1; actual = sp->tty->driver.write(sp->tty, 0, sp->xbuff, count); sp->xleft = count - actual; sp->xhead = sp->xbuff + actual; sp->led_state = 0x60; sp->tty->driver.write(sp->tty, 0, &(sp->led_state), 1); } /* ----------------------------------------------------------------------- */ /* * Called by the TTY driver when there's room for more data. If we have * more packets to send, we send them here. */ static void sixpack_write_wakeup(struct tty_struct *tty) { int actual; struct sixpack *sp = (struct sixpack *) tty->disc_data; /* First make sure we're connected. */ if (!sp || !netif_running(sp->dev)) { return; } if (sp->xleft <= 0) { /* Now serial buffer is almost free & we can start * transmission of another packet */ sp->stats.tx_packets++; tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); sp->tx_enable = 0; netif_wake_queue(sp->dev); return; } if (sp->tx_enable == 1) { actual = tty->driver.write(tty, 0, sp->xhead, sp->xleft); sp->xleft -= actual; sp->xhead += actual; } } /* ----------------------------------------------------------------------- */ /* Encapsulate an IP datagram and kick it into a TTY queue. */ static int sp_xmit(struct sk_buff *skb, struct net_device *dev) { struct sixpack *sp = (struct sixpack *) dev->priv; /* We were not busy, so we are now... :-) */ if (skb != NULL) { netif_stop_queue(dev); sp->stats.tx_bytes += skb->len; sp_encaps(sp, skb->data, skb->len); dev_kfree_skb(skb); } return 0; } /* ----------------------------------------------------------------------- */ /* Open the low-level part of the 6pack channel. */ static int sp_open(struct net_device *dev) { struct sixpack *sp = (struct sixpack*)(dev->priv); unsigned long len; if (sp->tty == NULL) return -ENODEV; /* * Allocate the 6pack frame buffers: * * rbuff Receive buffer. * xbuff Transmit buffer. * cbuff Temporary compression buffer. */ /* !!! length of the buffers. MTU is IP MTU, not PACLEN! */ len = dev->mtu * 2; sp->rbuff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL); if (sp->rbuff == NULL) return -ENOMEM; sp->xbuff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL); if (sp->xbuff == NULL) { kfree(sp->rbuff); return -ENOMEM; } sp->mtu = SIXP_MTU; sp->buffsize = len; sp->rcount = 0; sp->rx_count = 0; sp->rx_count_cooked = 0; sp->xleft = 0; sp->flags.error = 0; sp->led_state = 0x60; sp->status = 1; sp->status1 = 1; sp->status2 = 0; sp->tnc_ok = 0; sp->tx_enable = 0; sp->tx_counter = 0; netif_start_queue(dev); init_timer(&sp->resync_t); return 0; } /* ----------------------------------------------------------------------- */ /* Close the low-level part of the 6pack channel. */ static int sp_close(struct net_device *dev) { struct sixpack *sp = (struct sixpack*)(dev->priv); if (sp->tty == NULL) { return -EBUSY; } sp->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); netif_stop_queue(dev); return 0; } /* ----------------------------------------------------------------------- */ static int sixpack_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 6pack data has been received, which can now be decapsulated * and sent on to some IP layer for further processing. */ static void sixpack_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { unsigned char buf[512]; unsigned long flags; int count1; struct sixpack *sp = (struct sixpack *) tty->disc_data; if (!sp || !netif_running(sp->dev) || !count) return; save_flags(flags); cli(); memcpy(buf, cp, min(count, sizeof(buf))); restore_flags(flags); /* Read the characters out of the buffer */ count1 = count; while(count) { count--; if (fp && *fp++) { if (!sp->flags.error) { sp->flags.error = 1; sp->stats.rx_errors++; } continue; } } sixpack_decode(sp, buf, count1); } /* ----------------------------------------------------------------------- */ /* * Open the high-level part of the 6pack channel. * This function is called by the TTY module when the * 6pack line discipline is called for. Because we are * sure the tty line exists, we only have to link it to * a free 6pack channel... */ static int sixpack_open(struct tty_struct *tty) { struct sixpack *sp = (struct sixpack *) tty->disc_data; int err; /* First make sure we're not already connected. */ if (sp) return -EEXIST; /* OK. Find a free 6pack channel to use. */ if ((sp = sp_alloc(tty)) == NULL) return -ENFILE; sp->tty = tty; tty->disc_data = sp; if (tty->driver.flush_buffer) tty->driver.flush_buffer(tty); if (tty->ldisc.flush_buffer) tty->ldisc.flush_buffer(tty); /* Restore default settings */ sp->dev->type = ARPHRD_AX25; /* Perform the low-level 6pack initialization. */ if ((err = sp_open(sp->dev))) return err; MOD_INC_USE_COUNT; /* Done. We have linked the TTY line to a channel. */ tnc_init(sp); return sp->dev->base_addr; } /* ----------------------------------------------------------------------- */ /* * Close down a 6pack channel. * This means flushing out any pending queues, and then restoring the * TTY line discipline to what it was before it got hooked to 6pack * (which usually is TTY again). */ static void sixpack_close(struct tty_struct *tty) { struct sixpack *sp = (struct sixpack *) tty->disc_data; /* First make sure we're connected. */ if (!sp) return; rtnl_lock(); if (sp->dev->flags & IFF_UP) dev_close(sp->dev); del_timer(&(sp->resync_t)); tty->disc_data = 0; sp->tty = NULL; sp_free(sp); unregister_netdevice(sp->dev); rtnl_unlock(); MOD_DEC_USE_COUNT; } /* ----------------------------------------------------------------------- */ static struct net_device_stats *sp_get_stats(struct net_device *dev) { struct sixpack *sp = (struct sixpack*) (dev->priv); return (&sp->stats); } /* ----------------------------------------------------------------------- */ static int sixpack_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 sixpack_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 6pack channel. */ static int sixpack_ioctl(struct tty_struct *tty, void *file, int cmd, void *arg) { struct sixpack *sp = (struct sixpack *) tty->disc_data; unsigned int tmp; /* First make sure we're connected. */ if (!sp) return -EINVAL; switch(cmd) { case SIOCGIFNAME: if (copy_to_user(arg, sp->dev->name, IFNAMSIZ)) return -EFAULT; return 0; case SIOCGIFENCAP: if (put_user(sp->mode, (int *)arg)) return -EFAULT; return 0; case SIOCSIFENCAP: if (get_user(tmp,(int *)arg)) return -EFAULT; sp->mode = tmp; return 0; case SIOCSIFHWADDR: return sixpack_set_mac_address(sp->dev, arg); /* 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; } } /* ----------------------------------------------------------------------- */ /* report DCD state to DDI layer */ static unsigned int sixpack_ddi_report_dcd(struct net_device *dev) { struct sixpack *sp = (struct sixpack *) dev->priv; return (sp->status1 & SIXP_DCD_MASK); } /* ----------------------------------------------------------------------- */ /* report PTT state to DDI layer */ static unsigned int sixpack_ddi_report_ptt(struct net_device *dev) { struct sixpack *sp = (struct sixpack *) dev->priv; return !!sp->tx_counter; } /* ----------------------------------------------------------------------- */ /* this function if called by DDI layer whenever a parameter changes. */ static void sixpack_parameter_change_notify(struct net_device *dev, int valueno, int old, int new) { struct sixpack *sp = (struct sixpack *) 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(sp->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_TXDELAY: case AX25_VALUES_MEDIA_TXTAIL: if (new >= 2500) ax25_dev_set_value(dev, valueno, 2490); break; case AX25_VALUES_MEDIA_DUPLEX: default: /* just let them do it. */ break; } return; } /* ----------------------------------------------------------------------- */ static int sp_open_dev(struct net_device *dev) { struct sixpack *sp = (struct sixpack*) (dev->priv); if (sp->tty == NULL) return -ENODEV; return 0; } /* ----------------------------------------------------------------------- */ /* Initialize the 6pack driver. Called by core/dev.c on registration */ static int sixpack_init(struct net_device *dev) { struct sixpack *sp = (struct sixpack *) dev->priv; if (sp == NULL) return -ENODEV; /* Set up the "6pack Control Block". (And clear statistics) */ sp->dev = dev; /* Finish setting up the DEVICE info. */ dev->mtu = SIXP_MTU; dev->hard_start_xmit = sp_xmit; dev->open = sp_open_dev; dev->stop = sp_close; dev->hard_header = NULL; dev->get_stats = sp_get_stats; dev->set_mac_address = sixpack_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->rebuild_header = NULL; dev->tx_timeout = NULL; dev->flags = IFF_BROADCAST | IFF_MULTICAST; AX25_PTR(dev) = &sp->ax25dev; memset(AX25_PTR(dev), 0, sizeof(struct ax25_dev)); AX25_PTR(dev)->hw.fast = 0; AX25_PTR(dev)->hw.dcd = sixpack_ddi_report_dcd; AX25_PTR(dev)->hw.ptt = sixpack_ddi_report_ptt; AX25_PTR(dev)->hw.cts = NULL; AX25_PTR(dev)->hw.rts = NULL; AX25_PTR(dev)->hw.parameter_change_notify = sixpack_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_AUTO_ADJUST, 1); dev_init_buffers(dev); return 0; } /* ----------------------------------------------------------------------- */ /* encode an AX.25 packet into 6pack */ int encode_sixpack(unsigned char *tx_buf, unsigned char *tx_buf_raw, int length, unsigned int tx_delay) { int count = 0; unsigned char checksum = 0, buf[400]; int raw_count = 0; tx_buf_raw[raw_count++] = SIXP_PRIO_CMD_MASK | SIXP_TX_MASK; tx_buf_raw[raw_count++] = SIXP_SEOF; buf[0] = tx_delay/10; /* 10 ms units */ memcpy(&buf[1], tx_buf, length); length++; for(count = 0; count < length; count++) checksum += buf[count]; buf[length] = (unsigned char) 0xff - checksum; for(count = 0; count <= length; count++) { if((count % 3) == 0) { tx_buf_raw[raw_count++] = (buf[count] & 0x3f); tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x30); } else if((count % 3) == 1) { tx_buf_raw[raw_count++] |= (buf[count] & 0x0f); tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x3c); } else { tx_buf_raw[raw_count++] |= (buf[count] & 0x03); tx_buf_raw[raw_count++] = (buf[count] >> 2); } } if ((length % 3) != 2) raw_count++; tx_buf_raw[raw_count++] = SIXP_SEOF; return(raw_count); } /* ----------------------------------------------------------------------- */ /* decode a 6pack packet */ void sixpack_decode(struct sixpack *sp, unsigned char pre_rbuff[], int count) { unsigned char inbyte; int count1; for (count1 = 0; count1 < count; count1++) { inbyte = pre_rbuff[count1]; if (inbyte == SIXP_FOUND_TNC) { printk(KERN_INFO "6pack: TNC found.\n"); sp->tnc_ok = 1; sp->tx_counter = 0; del_timer(&(sp->resync_t)); } if ((inbyte & SIXP_PRIO_CMD_MASK) != 0) { decode_prio_command(inbyte, sp); } else if((inbyte & SIXP_STD_CMD_MASK) != 0) { decode_std_command(inbyte, sp); } else { decode_data(inbyte, sp); } } } /* ----------------------------------------------------------------------- */ static int tnc_init(struct sixpack *sp) { static unsigned char inbyte = 0xe8; sp->tty->driver.write(sp->tty, 0, &inbyte, 1); del_timer(&(sp->resync_t)); sp->resync_t.data = (unsigned long) sp; sp->resync_t.function = resync_tnc; sp->resync_t.expires = jiffies + SIXP_RESYNC_TIMEOUT; add_timer(&(sp->resync_t)); return 0; } /* ----------------------------------------------------------------------- */ /* identify and execute a 6pack priority command byte */ void decode_prio_command(unsigned char cmd, struct sixpack *sp) { unsigned char channel; int actual; int duplex; struct net_device *dev = sp->dev; duplex = ax25_dev_get_value(dev, AX25_VALUES_MEDIA_DUPLEX); channel = cmd & SIXP_CHN_MASK; if ((cmd & SIXP_PRIO_DATA_MASK) != 0) { /* * not idle. * RX and DCD flags can only be set in the same prio command, * if the DCD flag has been set without the RX flag in the previous * prio command. If DCD has not been set before, something in the * transmission has gone wrong. In this case, RX and DCD are * cleared in order to prevent the decode_data routine from * reading further data that might be corrupt. */ if (((sp->status & SIXP_DCD_MASK) == 0) && ((cmd & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)) { if (sp->status != 1) { printk(KERN_DEBUG "6pack: protocol violation\n"); sp->tnc_ok = 0; } else { sp->status = 0; } cmd &= !SIXP_RX_DCD_MASK; } sp->status = cmd & SIXP_PRIO_DATA_MASK; } else { /* output watchdog char if idle */ if ((sp->status2 != 0) && (duplex == 1)) { sp->led_state = 0x70; sp->tty->driver.write(sp->tty, 0, &(sp->led_state), 1); sp->tx_enable = 1; actual = sp->tty->driver.write(sp->tty, 0, sp->xbuff, sp->status2); sp->xleft -= actual; sp->xhead += actual; sp->led_state = 0x60; sp->status2 = 0; } } if (((cmd & 0xc0) == 0x80) && (cmd & 0x20)) { /* dirty hack for now */ if (--sp->tx_counter < 0) sp->tnc_ok = 0; } /* needed to trigger the TNC watchdog */ sp->tty->driver.write(sp->tty, 0, &(sp->led_state), 1); /* * if the state byte has been received, the TNC is present, * so the resync timer can be reset. */ if (sp->tnc_ok == 1) { del_timer(&(sp->resync_t)); sp->resync_t.data = (unsigned long) sp; sp->resync_t.function = resync_tnc; sp->resync_t.expires = jiffies + SIXP_INIT_RESYNC_TIMEOUT; add_timer(&(sp->resync_t)); } sp->status1 = cmd & SIXP_PRIO_DATA_MASK; } /* ----------------------------------------------------------------------- */ /* * try to resync the TNC. Called by the resync timer defined in * decode_prio_command */ static void resync_tnc(unsigned long channel) { static char resync_cmd = 0xe8; struct sixpack *sp = (struct sixpack *) channel; printk(KERN_INFO "6pack: resyncing TNC...\n"); /* clear any data that might have been received */ sp->rx_count = 0; sp->rx_count_cooked = 0; /* reset state machine */ sp->status = 1; sp->status1 = 1; sp->status2 = 0; sp->tnc_ok = 0; /* resync the TNC */ sp->led_state = 0x60; sp->tty->driver.write(sp->tty, 0, &(sp->led_state), 1); sp->tty->driver.write(sp->tty, 0, &resync_cmd, 1); /* Start resync timer again -- the TNC might be still absent */ del_timer(&(sp->resync_t)); sp->resync_t.data = (unsigned long) sp; sp->resync_t.function = resync_tnc; sp->resync_t.expires = jiffies + SIXP_RESYNC_TIMEOUT; add_timer(&(sp->resync_t)); } /* ----------------------------------------------------------------------- */ /* identify and execute a standard 6pack command byte */ void decode_std_command(unsigned char cmd, struct sixpack *sp) { unsigned char checksum = 0, rest = 0, channel; short i; channel = cmd & SIXP_CHN_MASK; switch (cmd & SIXP_CMD_MASK) { /* normal command */ case SIXP_SEOF: if ((sp->rx_count == 0) && (sp->rx_count_cooked == 0)) { if ((sp->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK) { sp->led_state = 0x68; sp->tty->driver.write(sp->tty, 0, &(sp->led_state), 1); } } else { sp->led_state = 0x60; /* fill trailing bytes with zeroes */ sp->tty->driver.write(sp->tty, 0, &(sp->led_state), 1); rest = sp->rx_count; if (rest != 0) for(i=rest; i<=3; i++) decode_data(0, sp); if (rest == 2) { sp->rx_count_cooked -= 2; } else if (rest == 3) { sp->rx_count_cooked -= 1; } for (i=0; irx_count_cooked; i++) checksum+=sp->cooked_buf[i]; if (checksum != SIXP_CHKSUM) { sp->stats.rx_crc_errors++; } else { sp->rcount = sp->rx_count_cooked-2; sp_bump(sp, 0); } sp->rx_count_cooked = 0; } break; case SIXP_TX_URUN: sp->stats.tx_fifo_errors++; break; case SIXP_RX_ORUN: sp->stats.rx_over_errors++; break; case SIXP_RX_BUF_OVL: sp->stats.rx_length_errors++; } } /* ----------------------------------------------------------------------- */ /* decode 4 sixpack-encoded bytes into 3 data bytes */ void decode_data(unsigned char inbyte, struct sixpack *sp) { unsigned char *buf; if (sp->rx_count != 3) { sp->raw_buf[sp->rx_count++] = inbyte; } else { buf = sp->raw_buf; sp->cooked_buf[sp->rx_count_cooked++] = buf[0] | ((buf[1] << 2) & 0xc0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[2] & 0x03) | (inbyte << 2); sp->rx_count = 0; } } /* ----------------------------------------------------------------------- */ /* Fill in our line protocol discipline */ static struct tty_ldisc sp_ldisc = { magic: TTY_LDISC_MAGIC, name: "6pack", open: sixpack_open, close: sixpack_close, ioctl: (int (*)(struct tty_struct *, struct file *, unsigned int, unsigned long)) sixpack_ioctl, receive_buf: sixpack_receive_buf, receive_room: sixpack_receive_room, write_wakeup: sixpack_write_wakeup, }; /* Initialize 6pack control device -- register 6pack line discipline */ int __init sixpack_init_ctrl_dev(void) { int status; /* Do sanity checks on maximum device parameter. */ if (sixpack_maxdev < 4) sixpack_maxdev = 4; printk(KERN_INFO "AX.25: 6pack driver, %s (dynamic channels, max=%d)\n", SIXPACK_VERSION, sixpack_maxdev); sixpack_ctrls = (struct sixpack_ctrl_t **) kmalloc(sizeof(void *) *sixpack_maxdev, GFP_KERNEL); if (sixpack_ctrls == NULL) { printk(KERN_WARNING "6pack: Can't allocate sixpack_ctrls[] array! Uaargh! (-> No 6pack available)\n"); return -ENOMEM; } /* Clear the pointer array, we allocate devices when we need them */ memset(sixpack_ctrls, 0, sizeof(void*)*sixpack_maxdev); if ((status = tty_register_ldisc(N_6PACK, &sp_ldisc)) != 0) { printk(KERN_WARNING "6pack: can't register line discipline (err = %d)\n", status); kfree(sixpack_ctrls); } return 0; } void __exit sixpack_exit_driver(void) { int i; if (sixpack_ctrls != NULL) { if ((i = tty_register_ldisc(N_6PACK, NULL))) printk(KERN_WARNING "6pack: can't unregister line discipline (err = %d)\n", i); for (i = 0; i < sixpack_maxdev; i++) { if (sixpack_ctrls[i]) { /* * VSV = if netif_running(), then device * unregistered while close proc. */ if (netif_running(&(sixpack_ctrls[i]->dev))) unregister_netdev(&(sixpack_ctrls[i]->dev)); kfree(sixpack_ctrls[i]); sixpack_ctrls[i] = NULL; } } kfree(sixpack_ctrls); sixpack_ctrls = NULL; } } MODULE_AUTHOR("Andreas Könsgen "); MODULE_DESCRIPTION("6pack driver for AX.25"); MODULE_PARM(sixpack_maxdev, "i"); MODULE_PARM_DESC(sixpack_maxdev, "number of 6PACK devices"); module_init(sixpack_init_ctrl_dev); module_exit(sixpack_exit_driver);