linux/drivers/atm/atmtcp.c

491 lines
11 KiB
C
Raw Normal View History

/* 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 cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
#include <linux/slab.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_sleep(sk_atm(vcc)), &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_sleep(sk_atm(vcc)), &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_sleep(sk_atm(vcc)));
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);
skb_copy_from_linear_data(skb, skb_put(new_skb, skb->len), 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;
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);
atm_dev_deregister(atmtcp_dev);
vcc->dev_data = NULL;
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;
}
__net_timestamp(new_skb);
skb_copy_from_linear_data(skb, skb_put(new_skb, skb->len), 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(atmtcp_control_dev.lock)
};
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);
atm_dev_deregister(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);