/*
* Things to sort out:
*
* o tbusy handling
* o allow users to set the parameters
* o sync/async switching ?
*
* Note: This does _not_ implement CCITT X.25 asynchronous framing
* recommendations. Its primarily for testing purposes. If you wanted
* to do CCITT then in theory all you need is to nick the HDLC async
* checksum routines from ppp.c
* Changes:
*
* 2000-10-29 Henner Eisen lapb_data_indication() return status.
*/
#include <linux/module.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/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/x25.h>
#include <linux/lapb.h>
#include <linux/init.h>
#include "x25_asy.h"
#include <net/x25device.h>
static struct net_device **x25_asy_devs;
static int x25_asy_maxdev = SL_NRUNIT;
module_param(x25_asy_maxdev, int, 0);
MODULE_LICENSE("GPL");
static int x25_asy_esc(unsigned char *p, unsigned char *d, int len);
static void x25_asy_unesc(struct x25_asy *sl, unsigned char c);
static void x25_asy_setup(struct net_device *dev);
/* Find a free X.25 channel, and link in this `tty' line. */
static struct x25_asy *x25_asy_alloc(void)
{
struct net_device *dev = NULL;
struct x25_asy *sl;
int i;
if (x25_asy_devs == NULL)
return NULL; /* Master array missing ! */
for (i = 0; i < x25_asy_maxdev; i++) {
dev = x25_asy_devs[i];
/* Not allocated ? */
if (dev == NULL)
break;
sl = dev->priv;
/* Not in use ? */
if (!test_and_set_bit(SLF_INUSE, &sl->flags))
return sl;
}
/* Sorry, too many, all slots in use */
if (i >= x25_asy_maxdev)
return NULL;
/* If no channels are available, allocate one */
if (!dev) {
char name[IFNAMSIZ];
sprintf(name, "x25asy%d", i);
dev = alloc_netdev(sizeof(struct x25_asy),
name, x25_asy_setup);
if (!dev)
return NULL;
/* Initialize channel control data */
sl = dev->priv;
dev->base_addr = i;
/* register device so that it can be ifconfig'ed */
if (register_netdev(dev) == 0) {
/* (Re-)Set the INUSE bit. Very Important! */
set_bit(SLF_INUSE, &sl->flags);
x25_asy_devs[i] = dev;
return sl;
} else {
printk("x25_asy_alloc() - register_netdev() failure.\n");
free_netdev(dev);
}
}
return NULL;
}
/* Free an X.25 channel. */
static void x25_asy_free(struct x25_asy *sl)
{
/* Free all X.25 frame buffers. */
if (sl->rbuff) {
kfree(sl->rbuff);
}
sl->rbuff = NULL;
if (sl->xbuff) {
kfree(sl->xbuff);
}
sl->xbuff = NULL;
if (!test_and_clear_bit(SLF_INUSE, &sl->flags)) {
printk("%s: x25_asy_free for already free unit.\n", sl->dev->name);
}
}
static int x25_asy_change_mtu(struct net_device *dev, int newmtu)
{
struct x25_asy *sl = dev->priv;
unsigned char *xbuff, *rbuff;
int len = 2* newmtu;
xbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
rbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
if (xbuff == NULL || rbuff == NULL)
{
printk("%s: unable to grow X.25 buffers, MTU change cancelled.\n",
dev->name);
if (xbuff != NULL)
kfree(xbuff);
if (rbuff != NULL)
kfree(rbuff);
return -ENOMEM;
}
spin_lock_bh(&sl->lock);
xbuff = xchg(&sl->xbuff, xbuff);
if (sl->xleft) {
if (sl->xleft <= len) {
memcpy(sl->xbuff, sl->xhead, sl->xleft);
} else {
sl->xleft = 0;
sl->stats.tx_dropped++;
}
}
sl->xhead = sl->xbuff;
rbuff = xchg(&sl->rbuff, rbuff);
if (sl->rcount) {
if (sl->rcount <= len) {
memcpy(sl->rbuff, rbuff, sl->rcount);
} else {
sl->rcount = 0;
sl->stats.rx_over_errors++;
set_bit(SLF_ERROR, &sl->flags);
}
}
dev->mtu = newmtu;
sl->buffsize = len;
spin_unlock_bh(&sl->lock);
if (xbuff != NULL)
kfree(xbuff);
if (rbuff != NULL)
kfree(rbuff);
return 0;
}
/* Set the "sending" flag. This must be atomic, hence the ASM. */
static inline void x25_asy_lock(struct x25_asy *sl)
{
netif_stop_queue(sl->dev