summary refs log tree commit diff
path: root/drivers/atm/atmtcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/atm/atmtcp.c')
-rw-r--r--drivers/atm/atmtcp.c505
1 files changed, 505 insertions, 0 deletions
diff --git a/drivers/atm/atmtcp.c b/drivers/atm/atmtcp.c
new file mode 100644
index 000000000000..f2f01cb82cb4
--- /dev/null
+++ b/drivers/atm/atmtcp.c
@@ -0,0 +1,505 @@
+/* drivers/atm/atmtcp.c - ATM over TCP "device" driver */
+
+/* Written 1997-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/atmdev.h>
+#include <linux/atm_tcp.h>
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+
+extern int atm_init_aal5(struct atm_vcc *vcc); /* "raw" AAL5 transport */
+
+
+#define PRIV(dev) ((struct atmtcp_dev_data *) ((dev)->dev_data))
+
+
+struct atmtcp_dev_data {
+	struct atm_vcc *vcc;	/* control VCC; NULL if detached */
+	int persist;		/* non-zero if persistent */
+};
+
+
+#define DEV_LABEL    "atmtcp"
+
+#define MAX_VPI_BITS  8	/* simplifies life */
+#define MAX_VCI_BITS 16
+
+
+/*
+ * Hairy code ahead: the control VCC may be closed while we're still
+ * waiting for an answer, so we need to re-validate out_vcc every once
+ * in a while.
+ */
+
+
+static int atmtcp_send_control(struct atm_vcc *vcc,int type,
+    const struct atmtcp_control *msg,int flag)
+{
+	DECLARE_WAITQUEUE(wait,current);
+	struct atm_vcc *out_vcc;
+	struct sk_buff *skb;
+	struct atmtcp_control *new_msg;
+	int old_test;
+	int error = 0;
+
+	out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL;
+	if (!out_vcc) return -EUNATCH;
+	skb = alloc_skb(sizeof(*msg),GFP_KERNEL);
+	if (!skb) return -ENOMEM;
+	mb();
+	out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL;
+	if (!out_vcc) {
+		dev_kfree_skb(skb);
+		return -EUNATCH;
+	}
+	atm_force_charge(out_vcc,skb->truesize);
+	new_msg = (struct atmtcp_control *) skb_put(skb,sizeof(*new_msg));
+	*new_msg = *msg;
+	new_msg->hdr.length = ATMTCP_HDR_MAGIC;
+	new_msg->type = type;
+	memset(&new_msg->vcc,0,sizeof(atm_kptr_t));
+	*(struct atm_vcc **) &new_msg->vcc = vcc;
+	old_test = test_bit(flag,&vcc->flags);
+	out_vcc->push(out_vcc,skb);
+	add_wait_queue(sk_atm(vcc)->sk_sleep, &wait);
+	while (test_bit(flag,&vcc->flags) == old_test) {
+		mb();
+		out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL;
+		if (!out_vcc) {
+			error = -EUNATCH;
+			break;
+		}
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule();
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(sk_atm(vcc)->sk_sleep, &wait);
+	return error;
+}
+
+
+static int atmtcp_recv_control(const struct atmtcp_control *msg)
+{
+	struct atm_vcc *vcc = *(struct atm_vcc **) &msg->vcc;
+
+	vcc->vpi = msg->addr.sap_addr.vpi;
+	vcc->vci = msg->addr.sap_addr.vci;
+	vcc->qos = msg->qos;
+	sk_atm(vcc)->sk_err = -msg->result;
+	switch (msg->type) {
+	    case ATMTCP_CTRL_OPEN:
+		change_bit(ATM_VF_READY,&vcc->flags);
+		break;
+	    case ATMTCP_CTRL_CLOSE:
+		change_bit(ATM_VF_ADDR,&vcc->flags);
+		break;
+	    default:
+		printk(KERN_ERR "atmtcp_recv_control: unknown type %d\n",
+		    msg->type);
+		return -EINVAL;
+	}
+	wake_up(sk_atm(vcc)->sk_sleep);
+	return 0;
+}
+
+
+static void atmtcp_v_dev_close(struct atm_dev *dev)
+{
+	/* Nothing.... Isn't this simple :-)  -- REW */
+}
+
+
+static int atmtcp_v_open(struct atm_vcc *vcc)
+{
+	struct atmtcp_control msg;
+	int error;
+	short vpi = vcc->vpi;
+	int vci = vcc->vci;
+
+	memset(&msg,0,sizeof(msg));
+	msg.addr.sap_family = AF_ATMPVC;
+	msg.hdr.vpi = htons(vpi);
+	msg.addr.sap_addr.vpi = vpi;
+	msg.hdr.vci = htons(vci);
+	msg.addr.sap_addr.vci = vci;
+	if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) return 0;
+	msg.type = ATMTCP_CTRL_OPEN;
+	msg.qos = vcc->qos;
+	set_bit(ATM_VF_ADDR,&vcc->flags);
+	clear_bit(ATM_VF_READY,&vcc->flags); /* just in case ... */
+	error = atmtcp_send_control(vcc,ATMTCP_CTRL_OPEN,&msg,ATM_VF_READY);
+	if (error) return error;
+	return -sk_atm(vcc)->sk_err;
+}
+
+
+static void atmtcp_v_close(struct atm_vcc *vcc)
+{
+	struct atmtcp_control msg;
+
+	memset(&msg,0,sizeof(msg));
+	msg.addr.sap_family = AF_ATMPVC;
+	msg.addr.sap_addr.vpi = vcc->vpi;
+	msg.addr.sap_addr.vci = vcc->vci;
+	clear_bit(ATM_VF_READY,&vcc->flags);
+	(void) atmtcp_send_control(vcc,ATMTCP_CTRL_CLOSE,&msg,ATM_VF_ADDR);
+}
+
+
+static int atmtcp_v_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
+{
+	struct atm_cirange ci;
+	struct atm_vcc *vcc;
+	struct hlist_node *node;
+	struct sock *s;
+	int i;
+
+	if (cmd != ATM_SETCIRANGE) return -ENOIOCTLCMD;
+	if (copy_from_user(&ci, arg,sizeof(ci))) return -EFAULT;
+	if (ci.vpi_bits == ATM_CI_MAX) ci.vpi_bits = MAX_VPI_BITS;
+	if (ci.vci_bits == ATM_CI_MAX) ci.vci_bits = MAX_VCI_BITS;
+	if (ci.vpi_bits > MAX_VPI_BITS || ci.vpi_bits < 0 ||
+	    ci.vci_bits > MAX_VCI_BITS || ci.vci_bits < 0) return -EINVAL;
+	read_lock(&vcc_sklist_lock);
+	for(i = 0; i < VCC_HTABLE_SIZE; ++i) {
+		struct hlist_head *head = &vcc_hash[i];
+
+		sk_for_each(s, node, head) {
+			vcc = atm_sk(s);
+			if (vcc->dev != dev)
+				continue;
+			if ((vcc->vpi >> ci.vpi_bits) ||
+			    (vcc->vci >> ci.vci_bits)) {
+				read_unlock(&vcc_sklist_lock);
+				return -EBUSY;
+			}
+		}
+	}
+	read_unlock(&vcc_sklist_lock);
+	dev->ci_range = ci;
+	return 0;
+}
+
+
+static int atmtcp_v_send(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+	struct atmtcp_dev_data *dev_data;
+	struct atm_vcc *out_vcc=NULL; /* Initializer quietens GCC warning */
+	struct sk_buff *new_skb;
+	struct atmtcp_hdr *hdr;
+	int size;
+
+	if (vcc->qos.txtp.traffic_class == ATM_NONE) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		return -EINVAL;
+	}
+	dev_data = PRIV(vcc->dev);
+	if (dev_data) out_vcc = dev_data->vcc;
+	if (!dev_data || !out_vcc) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		if (dev_data) return 0;
+		atomic_inc(&vcc->stats->tx_err);
+		return -ENOLINK;
+	}
+	size = skb->len+sizeof(struct atmtcp_hdr);
+	new_skb = atm_alloc_charge(out_vcc,size,GFP_ATOMIC);
+	if (!new_skb) {
+		if (vcc->pop) vcc->pop(vcc,skb);
+		else dev_kfree_skb(skb);
+		atomic_inc(&vcc->stats->tx_err);
+		return -ENOBUFS;
+	}
+	hdr = (void *) skb_put(new_skb,sizeof(struct atmtcp_hdr));
+	hdr->vpi = htons(vcc->vpi);
+	hdr->vci = htons(vcc->vci);
+	hdr->length = htonl(skb->len);
+	memcpy(skb_put(new_skb,skb->len),skb->data,skb->len);
+	if (vcc->pop) vcc->pop(vcc,skb);
+	else dev_kfree_skb(skb);
+	out_vcc->push(out_vcc,new_skb);
+	atomic_inc(&vcc->stats->tx);
+	atomic_inc(&out_vcc->stats->rx);
+	return 0;
+}
+
+
+static int atmtcp_v_proc(struct atm_dev *dev,loff_t *pos,char *page)
+{
+	struct atmtcp_dev_data *dev_data = PRIV(dev);
+
+	if (*pos) return 0;
+	if (!dev_data->persist) return sprintf(page,"ephemeral\n");
+	return sprintf(page,"persistent, %sconnected\n",
+	    dev_data->vcc ? "" : "dis");
+}
+
+
+static void atmtcp_c_close(struct atm_vcc *vcc)
+{
+	struct atm_dev *atmtcp_dev;
+	struct atmtcp_dev_data *dev_data;
+	struct sock *s;
+	struct hlist_node *node;
+	struct atm_vcc *walk;
+	int i;
+
+	atmtcp_dev = (struct atm_dev *) vcc->dev_data;
+	dev_data = PRIV(atmtcp_dev);
+	dev_data->vcc = NULL;
+	if (dev_data->persist) return;
+	atmtcp_dev->dev_data = NULL;
+	kfree(dev_data);
+	shutdown_atm_dev(atmtcp_dev);
+	vcc->dev_data = NULL;
+	read_lock(&vcc_sklist_lock);
+	for(i = 0; i < VCC_HTABLE_SIZE; ++i) {
+		struct hlist_head *head = &vcc_hash[i];
+
+		sk_for_each(s, node, head) {
+			walk = atm_sk(s);
+			if (walk->dev != atmtcp_dev)
+				continue;
+			wake_up(s->sk_sleep);
+		}
+	}
+	read_unlock(&vcc_sklist_lock);
+	module_put(THIS_MODULE);
+}
+
+
+static struct atm_vcc *find_vcc(struct atm_dev *dev, short vpi, int vci)
+{
+        struct hlist_head *head;
+        struct atm_vcc *vcc;
+        struct hlist_node *node;
+        struct sock *s;
+
+        head = &vcc_hash[vci & (VCC_HTABLE_SIZE -1)];
+
+        sk_for_each(s, node, head) {
+                vcc = atm_sk(s);
+                if (vcc->dev == dev &&
+                    vcc->vci == vci && vcc->vpi == vpi &&
+                    vcc->qos.rxtp.traffic_class != ATM_NONE) {
+                                return vcc;
+                }
+        }
+        return NULL;
+}
+
+
+static int atmtcp_c_send(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+	struct atm_dev *dev;
+	struct atmtcp_hdr *hdr;
+	struct atm_vcc *out_vcc;
+	struct sk_buff *new_skb;
+	int result = 0;
+
+	if (!skb->len) return 0;
+	dev = vcc->dev_data;
+	hdr = (struct atmtcp_hdr *) skb->data;
+	if (hdr->length == ATMTCP_HDR_MAGIC) {
+		result = atmtcp_recv_control(
+		    (struct atmtcp_control *) skb->data);
+		goto done;
+	}
+	read_lock(&vcc_sklist_lock);
+	out_vcc = find_vcc(dev, ntohs(hdr->vpi), ntohs(hdr->vci));
+	read_unlock(&vcc_sklist_lock);
+	if (!out_vcc) {
+		atomic_inc(&vcc->stats->tx_err);
+		goto done;
+	}
+	skb_pull(skb,sizeof(struct atmtcp_hdr));
+	new_skb = atm_alloc_charge(out_vcc,skb->len,GFP_KERNEL);
+	if (!new_skb) {
+		result = -ENOBUFS;
+		goto done;
+	}
+	do_gettimeofday(&new_skb->stamp);
+	memcpy(skb_put(new_skb,skb->len),skb->data,skb->len);
+	out_vcc->push(out_vcc,new_skb);
+	atomic_inc(&vcc->stats->tx);
+	atomic_inc(&out_vcc->stats->rx);
+done:
+	if (vcc->pop) vcc->pop(vcc,skb);
+	else dev_kfree_skb(skb);
+	return result;
+}
+
+
+/*
+ * Device operations for the virtual ATM devices created by ATMTCP.
+ */
+
+
+static struct atmdev_ops atmtcp_v_dev_ops = {
+	.dev_close	= atmtcp_v_dev_close,
+	.open		= atmtcp_v_open,
+	.close		= atmtcp_v_close,
+	.ioctl		= atmtcp_v_ioctl,
+	.send		= atmtcp_v_send,
+	.proc_read	= atmtcp_v_proc,
+	.owner		= THIS_MODULE
+};
+
+
+/*
+ * Device operations for the ATMTCP control device.
+ */
+
+
+static struct atmdev_ops atmtcp_c_dev_ops = {
+	.close		= atmtcp_c_close,
+	.send		= atmtcp_c_send
+};
+
+
+static struct atm_dev atmtcp_control_dev = {
+	.ops		= &atmtcp_c_dev_ops,
+	.type		= "atmtcp",
+	.number		= 999,
+	.lock		= SPIN_LOCK_UNLOCKED
+};
+
+
+static int atmtcp_create(int itf,int persist,struct atm_dev **result)
+{
+	struct atmtcp_dev_data *dev_data;
+	struct atm_dev *dev;
+
+	dev_data = kmalloc(sizeof(*dev_data),GFP_KERNEL);
+	if (!dev_data)
+		return -ENOMEM;
+
+	dev = atm_dev_register(DEV_LABEL,&atmtcp_v_dev_ops,itf,NULL);
+	if (!dev) {
+		kfree(dev_data);
+		return itf == -1 ? -ENOMEM : -EBUSY;
+	}
+	dev->ci_range.vpi_bits = MAX_VPI_BITS;
+	dev->ci_range.vci_bits = MAX_VCI_BITS;
+	dev->dev_data = dev_data;
+	PRIV(dev)->vcc = NULL;
+	PRIV(dev)->persist = persist;
+	if (result) *result = dev;
+	return 0;
+}
+
+
+static int atmtcp_attach(struct atm_vcc *vcc,int itf)
+{
+	struct atm_dev *dev;
+
+	dev = NULL;
+	if (itf != -1) dev = atm_dev_lookup(itf);
+	if (dev) {
+		if (dev->ops != &atmtcp_v_dev_ops) {
+			atm_dev_put(dev);
+			return -EMEDIUMTYPE;
+		}
+		if (PRIV(dev)->vcc) return -EBUSY;
+	}
+	else {
+		int error;
+
+		error = atmtcp_create(itf,0,&dev);
+		if (error) return error;
+	}
+	PRIV(dev)->vcc = vcc;
+	vcc->dev = &atmtcp_control_dev;
+	vcc_insert_socket(sk_atm(vcc));
+	set_bit(ATM_VF_META,&vcc->flags);
+	set_bit(ATM_VF_READY,&vcc->flags);
+	vcc->dev_data = dev;
+	(void) atm_init_aal5(vcc); /* @@@ losing AAL in transit ... */
+	vcc->stats = &atmtcp_control_dev.stats.aal5;
+	return dev->number;
+}
+
+
+static int atmtcp_create_persistent(int itf)
+{
+	return atmtcp_create(itf,1,NULL);
+}
+
+
+static int atmtcp_remove_persistent(int itf)
+{
+	struct atm_dev *dev;
+	struct atmtcp_dev_data *dev_data;
+
+	dev = atm_dev_lookup(itf);
+	if (!dev) return -ENODEV;
+	if (dev->ops != &atmtcp_v_dev_ops) {
+		atm_dev_put(dev);
+		return -EMEDIUMTYPE;
+	}
+	dev_data = PRIV(dev);
+	if (!dev_data->persist) return 0;
+	dev_data->persist = 0;
+	if (PRIV(dev)->vcc) return 0;
+	kfree(dev_data);
+	atm_dev_put(dev);
+	shutdown_atm_dev(dev);
+	return 0;
+}
+
+static int atmtcp_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+	int err = 0;
+	struct atm_vcc *vcc = ATM_SD(sock);
+
+	if (cmd != SIOCSIFATMTCP && cmd != ATMTCP_CREATE && cmd != ATMTCP_REMOVE)
+		return -ENOIOCTLCMD;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	switch (cmd) {
+		case SIOCSIFATMTCP:
+			err = atmtcp_attach(vcc, (int) arg);
+			if (err >= 0) {
+				sock->state = SS_CONNECTED;
+				__module_get(THIS_MODULE);
+			}
+			break;
+		case ATMTCP_CREATE:
+			err = atmtcp_create_persistent((int) arg);
+			break;
+		case ATMTCP_REMOVE:
+			err = atmtcp_remove_persistent((int) arg);
+			break;
+	}
+	return err;
+}
+
+static struct atm_ioctl atmtcp_ioctl_ops = {
+	.owner 	= THIS_MODULE,
+	.ioctl	= atmtcp_ioctl,
+};
+
+static __init int atmtcp_init(void)
+{
+	register_atm_ioctl(&atmtcp_ioctl_ops);
+	return 0;
+}
+
+
+static void __exit atmtcp_exit(void)
+{
+	deregister_atm_ioctl(&atmtcp_ioctl_ops);
+}
+
+MODULE_LICENSE("GPL");
+module_init(atmtcp_init);
+module_exit(atmtcp_exit);