Initial commit
This commit is contained in:
commit
169c65d57e
51358 changed files with 23120455 additions and 0 deletions
73
net/atm/Kconfig
Normal file
73
net/atm/Kconfig
Normal file
|
@ -0,0 +1,73 @@
|
|||
#
|
||||
# Asynchronous Transfer Mode (ATM)
|
||||
#
|
||||
|
||||
config ATM
|
||||
tristate "Asynchronous Transfer Mode (ATM)"
|
||||
---help---
|
||||
ATM is a high-speed networking technology for Local Area Networks
|
||||
and Wide Area Networks. It uses a fixed packet size and is
|
||||
connection oriented, allowing for the negotiation of minimum
|
||||
bandwidth requirements.
|
||||
|
||||
In order to participate in an ATM network, your Linux box needs an
|
||||
ATM networking card. If you have that, say Y here and to the driver
|
||||
of your ATM card below.
|
||||
|
||||
Note that you need a set of user-space programs to actually make use
|
||||
of ATM. See the file <file:Documentation/networking/atm.txt> for
|
||||
further details.
|
||||
|
||||
config ATM_CLIP
|
||||
tristate "Classical IP over ATM"
|
||||
depends on ATM && INET
|
||||
help
|
||||
Classical IP over ATM for PVCs and SVCs, supporting InARP and
|
||||
ATMARP. If you want to communication with other IP hosts on your ATM
|
||||
network, you will typically either say Y here or to "LAN Emulation
|
||||
(LANE)" below.
|
||||
|
||||
config ATM_CLIP_NO_ICMP
|
||||
bool "Do NOT send ICMP if no neighbour"
|
||||
depends on ATM_CLIP
|
||||
help
|
||||
Normally, an "ICMP host unreachable" message is sent if a neighbour
|
||||
cannot be reached because there is no VC to it in the kernel's
|
||||
ATMARP table. This may cause problems when ATMARP table entries are
|
||||
briefly removed during revalidation. If you say Y here, packets to
|
||||
such neighbours are silently discarded instead.
|
||||
|
||||
config ATM_LANE
|
||||
tristate "LAN Emulation (LANE) support"
|
||||
depends on ATM
|
||||
help
|
||||
LAN Emulation emulates services of existing LANs across an ATM
|
||||
network. Besides operating as a normal ATM end station client, Linux
|
||||
LANE client can also act as an proxy client bridging packets between
|
||||
ELAN and Ethernet segments. You need LANE if you want to try MPOA.
|
||||
|
||||
config ATM_MPOA
|
||||
tristate "Multi-Protocol Over ATM (MPOA) support"
|
||||
depends on ATM && INET && ATM_LANE!=n
|
||||
help
|
||||
Multi-Protocol Over ATM allows ATM edge devices such as routers,
|
||||
bridges and ATM attached hosts establish direct ATM VCs across
|
||||
subnetwork boundaries. These shortcut connections bypass routers
|
||||
enhancing overall network performance.
|
||||
|
||||
config ATM_BR2684
|
||||
tristate "RFC1483/2684 Bridged protocols"
|
||||
depends on ATM && INET
|
||||
help
|
||||
ATM PVCs can carry ethernet PDUs according to RFC2684 (formerly 1483)
|
||||
This device will act like an ethernet from the kernels point of view,
|
||||
with the traffic being carried by ATM PVCs (currently 1 PVC/device).
|
||||
This is sometimes used over DSL lines. If in doubt, say N.
|
||||
|
||||
config ATM_BR2684_IPFILTER
|
||||
bool "Per-VC IP filter kludge"
|
||||
depends on ATM_BR2684
|
||||
help
|
||||
This is an experimental mechanism for users who need to terminate a
|
||||
large number of IP-only vcc's. Do not enable this unless you are sure
|
||||
you know what you are doing.
|
15
net/atm/Makefile
Normal file
15
net/atm/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# Makefile for the ATM Protocol Families.
|
||||
#
|
||||
|
||||
atm-y := addr.o pvc.o signaling.o svc.o ioctl.o common.o atm_misc.o raw.o resources.o atm_sysfs.o
|
||||
mpoa-objs := mpc.o mpoa_caches.o mpoa_proc.o
|
||||
|
||||
obj-$(CONFIG_ATM) += atm.o
|
||||
obj-$(CONFIG_ATM_CLIP) += clip.o
|
||||
obj-$(CONFIG_ATM_BR2684) += br2684.o
|
||||
atm-$(CONFIG_PROC_FS) += proc.o
|
||||
|
||||
obj-$(CONFIG_ATM_LANE) += lec.o
|
||||
obj-$(CONFIG_ATM_MPOA) += mpoa.o
|
||||
obj-$(CONFIG_PPPOATM) += pppoatm.o
|
161
net/atm/addr.c
Normal file
161
net/atm/addr.c
Normal file
|
@ -0,0 +1,161 @@
|
|||
/* net/atm/addr.c - Local ATM address registry */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "signaling.h"
|
||||
#include "addr.h"
|
||||
|
||||
static int check_addr(const struct sockaddr_atmsvc *addr)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (addr->sas_family != AF_ATMSVC)
|
||||
return -EAFNOSUPPORT;
|
||||
if (!*addr->sas_addr.pub)
|
||||
return *addr->sas_addr.prv ? 0 : -EINVAL;
|
||||
for (i = 1; i < ATM_E164_LEN + 1; i++) /* make sure it's \0-terminated */
|
||||
if (!addr->sas_addr.pub[i])
|
||||
return 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int identical(const struct sockaddr_atmsvc *a, const struct sockaddr_atmsvc *b)
|
||||
{
|
||||
if (*a->sas_addr.prv)
|
||||
if (memcmp(a->sas_addr.prv, b->sas_addr.prv, ATM_ESA_LEN))
|
||||
return 0;
|
||||
if (!*a->sas_addr.pub)
|
||||
return !*b->sas_addr.pub;
|
||||
if (!*b->sas_addr.pub)
|
||||
return 0;
|
||||
return !strcmp(a->sas_addr.pub, b->sas_addr.pub);
|
||||
}
|
||||
|
||||
static void notify_sigd(const struct atm_dev *dev)
|
||||
{
|
||||
struct sockaddr_atmpvc pvc;
|
||||
|
||||
pvc.sap_addr.itf = dev->number;
|
||||
sigd_enq(NULL, as_itf_notify, NULL, &pvc, NULL);
|
||||
}
|
||||
|
||||
void atm_reset_addr(struct atm_dev *dev, enum atm_addr_type_t atype)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct atm_dev_addr *this, *p;
|
||||
struct list_head *head;
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (atype == ATM_ADDR_LECS)
|
||||
head = &dev->lecs;
|
||||
else
|
||||
head = &dev->local;
|
||||
list_for_each_entry_safe(this, p, head, entry) {
|
||||
list_del(&this->entry);
|
||||
kfree(this);
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
if (head == &dev->local)
|
||||
notify_sigd(dev);
|
||||
}
|
||||
|
||||
int atm_add_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr,
|
||||
enum atm_addr_type_t atype)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct atm_dev_addr *this;
|
||||
struct list_head *head;
|
||||
int error;
|
||||
|
||||
error = check_addr(addr);
|
||||
if (error)
|
||||
return error;
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (atype == ATM_ADDR_LECS)
|
||||
head = &dev->lecs;
|
||||
else
|
||||
head = &dev->local;
|
||||
list_for_each_entry(this, head, entry) {
|
||||
if (identical(&this->addr, addr)) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
return -EEXIST;
|
||||
}
|
||||
}
|
||||
this = kmalloc(sizeof(struct atm_dev_addr), GFP_ATOMIC);
|
||||
if (!this) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
return -ENOMEM;
|
||||
}
|
||||
this->addr = *addr;
|
||||
list_add(&this->entry, head);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
if (head == &dev->local)
|
||||
notify_sigd(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atm_del_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr,
|
||||
enum atm_addr_type_t atype)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct atm_dev_addr *this;
|
||||
struct list_head *head;
|
||||
int error;
|
||||
|
||||
error = check_addr(addr);
|
||||
if (error)
|
||||
return error;
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (atype == ATM_ADDR_LECS)
|
||||
head = &dev->lecs;
|
||||
else
|
||||
head = &dev->local;
|
||||
list_for_each_entry(this, head, entry) {
|
||||
if (identical(&this->addr, addr)) {
|
||||
list_del(&this->entry);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
kfree(this);
|
||||
if (head == &dev->local)
|
||||
notify_sigd(dev);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int atm_get_addr(struct atm_dev *dev, struct sockaddr_atmsvc __user * buf,
|
||||
size_t size, enum atm_addr_type_t atype)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct atm_dev_addr *this;
|
||||
struct list_head *head;
|
||||
int total = 0, error;
|
||||
struct sockaddr_atmsvc *tmp_buf, *tmp_bufp;
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
if (atype == ATM_ADDR_LECS)
|
||||
head = &dev->lecs;
|
||||
else
|
||||
head = &dev->local;
|
||||
list_for_each_entry(this, head, entry)
|
||||
total += sizeof(struct sockaddr_atmsvc);
|
||||
tmp_buf = tmp_bufp = kmalloc(total, GFP_ATOMIC);
|
||||
if (!tmp_buf) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
return -ENOMEM;
|
||||
}
|
||||
list_for_each_entry(this, head, entry)
|
||||
memcpy(tmp_bufp++, &this->addr, sizeof(struct sockaddr_atmsvc));
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
error = total > size ? -E2BIG : total;
|
||||
if (copy_to_user(buf, tmp_buf, total < size ? total : size))
|
||||
error = -EFAULT;
|
||||
kfree(tmp_buf);
|
||||
return error;
|
||||
}
|
20
net/atm/addr.h
Normal file
20
net/atm/addr.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* net/atm/addr.h - Local ATM address registry */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#ifndef NET_ATM_ADDR_H
|
||||
#define NET_ATM_ADDR_H
|
||||
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
|
||||
void atm_reset_addr(struct atm_dev *dev, enum atm_addr_type_t type);
|
||||
int atm_add_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr,
|
||||
enum atm_addr_type_t type);
|
||||
int atm_del_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr,
|
||||
enum atm_addr_type_t type);
|
||||
int atm_get_addr(struct atm_dev *dev, struct sockaddr_atmsvc __user *buf,
|
||||
size_t size, enum atm_addr_type_t type);
|
||||
|
||||
#endif
|
101
net/atm/atm_misc.c
Normal file
101
net/atm/atm_misc.c
Normal file
|
@ -0,0 +1,101 @@
|
|||
/* net/atm/atm_misc.c - Various functions for use by ATM drivers */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL ICA */
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/sonet.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/atomic.h>
|
||||
|
||||
int atm_charge(struct atm_vcc *vcc, int truesize)
|
||||
{
|
||||
atm_force_charge(vcc, truesize);
|
||||
if (atomic_read(&sk_atm(vcc)->sk_rmem_alloc) <= sk_atm(vcc)->sk_rcvbuf)
|
||||
return 1;
|
||||
atm_return(vcc, truesize);
|
||||
atomic_inc(&vcc->stats->rx_drop);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(atm_charge);
|
||||
|
||||
struct sk_buff *atm_alloc_charge(struct atm_vcc *vcc, int pdu_size,
|
||||
gfp_t gfp_flags)
|
||||
{
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
int guess = SKB_TRUESIZE(pdu_size);
|
||||
|
||||
atm_force_charge(vcc, guess);
|
||||
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) {
|
||||
struct sk_buff *skb = alloc_skb(pdu_size, gfp_flags);
|
||||
|
||||
if (skb) {
|
||||
atomic_add(skb->truesize-guess,
|
||||
&sk->sk_rmem_alloc);
|
||||
return skb;
|
||||
}
|
||||
}
|
||||
atm_return(vcc, guess);
|
||||
atomic_inc(&vcc->stats->rx_drop);
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(atm_alloc_charge);
|
||||
|
||||
|
||||
/*
|
||||
* atm_pcr_goal returns the positive PCR if it should be rounded up, the
|
||||
* negative PCR if it should be rounded down, and zero if the maximum available
|
||||
* bandwidth should be used.
|
||||
*
|
||||
* The rules are as follows (* = maximum, - = absent (0), x = value "x",
|
||||
* (x+ = x or next value above x, x- = x or next value below):
|
||||
*
|
||||
* min max pcr result min max pcr result
|
||||
* - - - * (UBR only) x - - x+
|
||||
* - - * * x - * *
|
||||
* - - z z- x - z z-
|
||||
* - * - * x * - x+
|
||||
* - * * * x * * *
|
||||
* - * z z- x * z z-
|
||||
* - y - y- x y - x+
|
||||
* - y * y- x y * y-
|
||||
* - y z z- x y z z-
|
||||
*
|
||||
* All non-error cases can be converted with the following simple set of rules:
|
||||
*
|
||||
* if pcr == z then z-
|
||||
* else if min == x && pcr == - then x+
|
||||
* else if max == y then y-
|
||||
* else *
|
||||
*/
|
||||
|
||||
int atm_pcr_goal(const struct atm_trafprm *tp)
|
||||
{
|
||||
if (tp->pcr && tp->pcr != ATM_MAX_PCR)
|
||||
return -tp->pcr;
|
||||
if (tp->min_pcr && !tp->pcr)
|
||||
return tp->min_pcr;
|
||||
if (tp->max_pcr != ATM_MAX_PCR)
|
||||
return -tp->max_pcr;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(atm_pcr_goal);
|
||||
|
||||
void sonet_copy_stats(struct k_sonet_stats *from, struct sonet_stats *to)
|
||||
{
|
||||
#define __HANDLE_ITEM(i) to->i = atomic_read(&from->i)
|
||||
__SONET_ITEMS
|
||||
#undef __HANDLE_ITEM
|
||||
}
|
||||
EXPORT_SYMBOL(sonet_copy_stats);
|
||||
|
||||
void sonet_subtract_stats(struct k_sonet_stats *from, struct sonet_stats *to)
|
||||
{
|
||||
#define __HANDLE_ITEM(i) atomic_sub(to->i, &from->i)
|
||||
__SONET_ITEMS
|
||||
#undef __HANDLE_ITEM
|
||||
}
|
||||
EXPORT_SYMBOL(sonet_subtract_stats);
|
190
net/atm/atm_sysfs.c
Normal file
190
net/atm/atm_sysfs.c
Normal file
|
@ -0,0 +1,190 @@
|
|||
/* ATM driver model support. */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include "common.h"
|
||||
#include "resources.h"
|
||||
|
||||
#define to_atm_dev(cldev) container_of(cldev, struct atm_dev, class_dev)
|
||||
|
||||
static ssize_t show_type(struct device *cdev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", adev->type);
|
||||
}
|
||||
|
||||
static ssize_t show_address(struct device *cdev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%pM\n", adev->esi);
|
||||
}
|
||||
|
||||
static ssize_t show_atmaddress(struct device *cdev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
struct atm_dev_addr *aaddr;
|
||||
int bin[] = { 1, 2, 10, 6, 1 }, *fmt = bin;
|
||||
int i, j, count = 0;
|
||||
|
||||
spin_lock_irqsave(&adev->lock, flags);
|
||||
list_for_each_entry(aaddr, &adev->local, entry) {
|
||||
for (i = 0, j = 0; i < ATM_ESA_LEN; ++i, ++j) {
|
||||
if (j == *fmt) {
|
||||
count += scnprintf(buf + count,
|
||||
PAGE_SIZE - count, ".");
|
||||
++fmt;
|
||||
j = 0;
|
||||
}
|
||||
count += scnprintf(buf + count,
|
||||
PAGE_SIZE - count, "%02x",
|
||||
aaddr->addr.sas_addr.prv[i]);
|
||||
}
|
||||
count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
|
||||
}
|
||||
spin_unlock_irqrestore(&adev->lock, flags);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_atmindex(struct device *cdev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", adev->number);
|
||||
}
|
||||
|
||||
static ssize_t show_carrier(struct device *cdev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||||
adev->signal == ATM_PHY_SIG_LOST ? 0 : 1);
|
||||
}
|
||||
|
||||
static ssize_t show_link_rate(struct device *cdev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
int link_rate;
|
||||
|
||||
/* show the link rate, not the data rate */
|
||||
switch (adev->link_rate) {
|
||||
case ATM_OC3_PCR:
|
||||
link_rate = 155520000;
|
||||
break;
|
||||
case ATM_OC12_PCR:
|
||||
link_rate = 622080000;
|
||||
break;
|
||||
case ATM_25_PCR:
|
||||
link_rate = 25600000;
|
||||
break;
|
||||
default:
|
||||
link_rate = adev->link_rate * 8 * 53;
|
||||
}
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", link_rate);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(address, S_IRUGO, show_address, NULL);
|
||||
static DEVICE_ATTR(atmaddress, S_IRUGO, show_atmaddress, NULL);
|
||||
static DEVICE_ATTR(atmindex, S_IRUGO, show_atmindex, NULL);
|
||||
static DEVICE_ATTR(carrier, S_IRUGO, show_carrier, NULL);
|
||||
static DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
|
||||
static DEVICE_ATTR(link_rate, S_IRUGO, show_link_rate, NULL);
|
||||
|
||||
static struct device_attribute *atm_attrs[] = {
|
||||
&dev_attr_atmaddress,
|
||||
&dev_attr_address,
|
||||
&dev_attr_atmindex,
|
||||
&dev_attr_carrier,
|
||||
&dev_attr_type,
|
||||
&dev_attr_link_rate,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
static int atm_uevent(struct device *cdev, struct kobj_uevent_env *env)
|
||||
{
|
||||
struct atm_dev *adev;
|
||||
|
||||
if (!cdev)
|
||||
return -ENODEV;
|
||||
|
||||
adev = to_atm_dev(cdev);
|
||||
if (!adev)
|
||||
return -ENODEV;
|
||||
|
||||
if (add_uevent_var(env, "NAME=%s%d", adev->type, adev->number))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atm_release(struct device *cdev)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
|
||||
kfree(adev);
|
||||
}
|
||||
|
||||
static struct class atm_class = {
|
||||
.name = "atm",
|
||||
.dev_release = atm_release,
|
||||
.dev_uevent = atm_uevent,
|
||||
};
|
||||
|
||||
int atm_register_sysfs(struct atm_dev *adev, struct device *parent)
|
||||
{
|
||||
struct device *cdev = &adev->class_dev;
|
||||
int i, j, err;
|
||||
|
||||
cdev->class = &atm_class;
|
||||
cdev->parent = parent;
|
||||
dev_set_drvdata(cdev, adev);
|
||||
|
||||
dev_set_name(cdev, "%s%d", adev->type, adev->number);
|
||||
err = device_register(cdev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; atm_attrs[i]; i++) {
|
||||
err = device_create_file(cdev, atm_attrs[i]);
|
||||
if (err)
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
for (j = 0; j < i; j++)
|
||||
device_remove_file(cdev, atm_attrs[j]);
|
||||
device_del(cdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
void atm_unregister_sysfs(struct atm_dev *adev)
|
||||
{
|
||||
struct device *cdev = &adev->class_dev;
|
||||
|
||||
device_del(cdev);
|
||||
}
|
||||
|
||||
int __init atm_sysfs_init(void)
|
||||
{
|
||||
return class_register(&atm_class);
|
||||
}
|
||||
|
||||
void __exit atm_sysfs_exit(void)
|
||||
{
|
||||
class_unregister(&atm_class);
|
||||
}
|
886
net/atm/br2684.c
Normal file
886
net/atm/br2684.c
Normal file
|
@ -0,0 +1,886 @@
|
|||
/*
|
||||
* Ethernet netdevice using ATM AAL5 as underlying carrier
|
||||
* (RFC1483 obsoleted by RFC2684) for Linux
|
||||
*
|
||||
* Authors: Marcell GAL, 2000, XDSL Ltd, Hungary
|
||||
* Eric Kinzie, 2006-2007, US Naval Research Laboratory
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/arp.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <linux/atmbr2684.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
static void skb_debug(const struct sk_buff *skb)
|
||||
{
|
||||
#ifdef SKB_DEBUG
|
||||
#define NUM2PRINT 50
|
||||
print_hex_dump(KERN_DEBUG, "br2684: skb: ", DUMP_OFFSET,
|
||||
16, 1, skb->data, min(NUM2PRINT, skb->len), true);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define BR2684_ETHERTYPE_LEN 2
|
||||
#define BR2684_PAD_LEN 2
|
||||
|
||||
#define LLC 0xaa, 0xaa, 0x03
|
||||
#define SNAP_BRIDGED 0x00, 0x80, 0xc2
|
||||
#define SNAP_ROUTED 0x00, 0x00, 0x00
|
||||
#define PID_ETHERNET 0x00, 0x07
|
||||
#define ETHERTYPE_IPV4 0x08, 0x00
|
||||
#define ETHERTYPE_IPV6 0x86, 0xdd
|
||||
#define PAD_BRIDGED 0x00, 0x00
|
||||
|
||||
static const unsigned char ethertype_ipv4[] = { ETHERTYPE_IPV4 };
|
||||
static const unsigned char ethertype_ipv6[] = { ETHERTYPE_IPV6 };
|
||||
static const unsigned char llc_oui_pid_pad[] =
|
||||
{ LLC, SNAP_BRIDGED, PID_ETHERNET, PAD_BRIDGED };
|
||||
static const unsigned char pad[] = { PAD_BRIDGED };
|
||||
static const unsigned char llc_oui_ipv4[] = { LLC, SNAP_ROUTED, ETHERTYPE_IPV4 };
|
||||
static const unsigned char llc_oui_ipv6[] = { LLC, SNAP_ROUTED, ETHERTYPE_IPV6 };
|
||||
|
||||
enum br2684_encaps {
|
||||
e_vc = BR2684_ENCAPS_VC,
|
||||
e_llc = BR2684_ENCAPS_LLC,
|
||||
};
|
||||
|
||||
struct br2684_vcc {
|
||||
struct atm_vcc *atmvcc;
|
||||
struct net_device *device;
|
||||
/* keep old push, pop functions for chaining */
|
||||
void (*old_push)(struct atm_vcc *vcc, struct sk_buff *skb);
|
||||
void (*old_pop)(struct atm_vcc *vcc, struct sk_buff *skb);
|
||||
void (*old_release_cb)(struct atm_vcc *vcc);
|
||||
struct module *old_owner;
|
||||
enum br2684_encaps encaps;
|
||||
struct list_head brvccs;
|
||||
#ifdef CONFIG_ATM_BR2684_IPFILTER
|
||||
struct br2684_filter filter;
|
||||
#endif /* CONFIG_ATM_BR2684_IPFILTER */
|
||||
unsigned int copies_needed, copies_failed;
|
||||
atomic_t qspace;
|
||||
};
|
||||
|
||||
struct br2684_dev {
|
||||
struct net_device *net_dev;
|
||||
struct list_head br2684_devs;
|
||||
int number;
|
||||
struct list_head brvccs; /* one device <=> one vcc (before xmas) */
|
||||
int mac_was_set;
|
||||
enum br2684_payload payload;
|
||||
};
|
||||
|
||||
/*
|
||||
* This lock should be held for writing any time the list of devices or
|
||||
* their attached vcc's could be altered. It should be held for reading
|
||||
* any time these are being queried. Note that we sometimes need to
|
||||
* do read-locking under interrupt context, so write locking must block
|
||||
* the current CPU's interrupts
|
||||
*/
|
||||
static DEFINE_RWLOCK(devs_lock);
|
||||
|
||||
static LIST_HEAD(br2684_devs);
|
||||
|
||||
static inline struct br2684_dev *BRPRIV(const struct net_device *net_dev)
|
||||
{
|
||||
return netdev_priv(net_dev);
|
||||
}
|
||||
|
||||
static inline struct net_device *list_entry_brdev(const struct list_head *le)
|
||||
{
|
||||
return list_entry(le, struct br2684_dev, br2684_devs)->net_dev;
|
||||
}
|
||||
|
||||
static inline struct br2684_vcc *BR2684_VCC(const struct atm_vcc *atmvcc)
|
||||
{
|
||||
return (struct br2684_vcc *)(atmvcc->user_back);
|
||||
}
|
||||
|
||||
static inline struct br2684_vcc *list_entry_brvcc(const struct list_head *le)
|
||||
{
|
||||
return list_entry(le, struct br2684_vcc, brvccs);
|
||||
}
|
||||
|
||||
/* Caller should hold read_lock(&devs_lock) */
|
||||
static struct net_device *br2684_find_dev(const struct br2684_if_spec *s)
|
||||
{
|
||||
struct list_head *lh;
|
||||
struct net_device *net_dev;
|
||||
switch (s->method) {
|
||||
case BR2684_FIND_BYNUM:
|
||||
list_for_each(lh, &br2684_devs) {
|
||||
net_dev = list_entry_brdev(lh);
|
||||
if (BRPRIV(net_dev)->number == s->spec.devnum)
|
||||
return net_dev;
|
||||
}
|
||||
break;
|
||||
case BR2684_FIND_BYIFNAME:
|
||||
list_for_each(lh, &br2684_devs) {
|
||||
net_dev = list_entry_brdev(lh);
|
||||
if (!strncmp(net_dev->name, s->spec.ifname, IFNAMSIZ))
|
||||
return net_dev;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int atm_dev_event(struct notifier_block *this, unsigned long event,
|
||||
void *arg)
|
||||
{
|
||||
struct atm_dev *atm_dev = arg;
|
||||
struct list_head *lh;
|
||||
struct net_device *net_dev;
|
||||
struct br2684_vcc *brvcc;
|
||||
struct atm_vcc *atm_vcc;
|
||||
unsigned long flags;
|
||||
|
||||
pr_debug("event=%ld dev=%p\n", event, atm_dev);
|
||||
|
||||
read_lock_irqsave(&devs_lock, flags);
|
||||
list_for_each(lh, &br2684_devs) {
|
||||
net_dev = list_entry_brdev(lh);
|
||||
|
||||
list_for_each_entry(brvcc, &BRPRIV(net_dev)->brvccs, brvccs) {
|
||||
atm_vcc = brvcc->atmvcc;
|
||||
if (atm_vcc && brvcc->atmvcc->dev == atm_dev) {
|
||||
|
||||
if (atm_vcc->dev->signal == ATM_PHY_SIG_LOST)
|
||||
netif_carrier_off(net_dev);
|
||||
else
|
||||
netif_carrier_on(net_dev);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
read_unlock_irqrestore(&devs_lock, flags);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block atm_dev_notifier = {
|
||||
.notifier_call = atm_dev_event,
|
||||
};
|
||||
|
||||
/* chained vcc->pop function. Check if we should wake the netif_queue */
|
||||
static void br2684_pop(struct atm_vcc *vcc, struct sk_buff *skb)
|
||||
{
|
||||
struct br2684_vcc *brvcc = BR2684_VCC(vcc);
|
||||
|
||||
pr_debug("(vcc %p ; net_dev %p )\n", vcc, brvcc->device);
|
||||
brvcc->old_pop(vcc, skb);
|
||||
|
||||
/* If the queue space just went up from zero, wake */
|
||||
if (atomic_inc_return(&brvcc->qspace) == 1)
|
||||
netif_wake_queue(brvcc->device);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a packet out a particular vcc. Not to useful right now, but paves
|
||||
* the way for multiple vcc's per itf. Returns true if we can send,
|
||||
* otherwise false
|
||||
*/
|
||||
static int br2684_xmit_vcc(struct sk_buff *skb, struct net_device *dev,
|
||||
struct br2684_vcc *brvcc)
|
||||
{
|
||||
struct br2684_dev *brdev = BRPRIV(dev);
|
||||
struct atm_vcc *atmvcc;
|
||||
int minheadroom = (brvcc->encaps == e_llc) ?
|
||||
((brdev->payload == p_bridged) ?
|
||||
sizeof(llc_oui_pid_pad) : sizeof(llc_oui_ipv4)) :
|
||||
((brdev->payload == p_bridged) ? BR2684_PAD_LEN : 0);
|
||||
|
||||
if (skb_headroom(skb) < minheadroom) {
|
||||
struct sk_buff *skb2 = skb_realloc_headroom(skb, minheadroom);
|
||||
brvcc->copies_needed++;
|
||||
dev_kfree_skb(skb);
|
||||
if (skb2 == NULL) {
|
||||
brvcc->copies_failed++;
|
||||
return 0;
|
||||
}
|
||||
skb = skb2;
|
||||
}
|
||||
|
||||
if (brvcc->encaps == e_llc) {
|
||||
if (brdev->payload == p_bridged) {
|
||||
skb_push(skb, sizeof(llc_oui_pid_pad));
|
||||
skb_copy_to_linear_data(skb, llc_oui_pid_pad,
|
||||
sizeof(llc_oui_pid_pad));
|
||||
} else if (brdev->payload == p_routed) {
|
||||
unsigned short prot = ntohs(skb->protocol);
|
||||
|
||||
skb_push(skb, sizeof(llc_oui_ipv4));
|
||||
switch (prot) {
|
||||
case ETH_P_IP:
|
||||
skb_copy_to_linear_data(skb, llc_oui_ipv4,
|
||||
sizeof(llc_oui_ipv4));
|
||||
break;
|
||||
case ETH_P_IPV6:
|
||||
skb_copy_to_linear_data(skb, llc_oui_ipv6,
|
||||
sizeof(llc_oui_ipv6));
|
||||
break;
|
||||
default:
|
||||
dev_kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else { /* e_vc */
|
||||
if (brdev->payload == p_bridged) {
|
||||
skb_push(skb, 2);
|
||||
memset(skb->data, 0, 2);
|
||||
}
|
||||
}
|
||||
skb_debug(skb);
|
||||
|
||||
ATM_SKB(skb)->vcc = atmvcc = brvcc->atmvcc;
|
||||
pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, atmvcc, atmvcc->dev);
|
||||
atomic_add(skb->truesize, &sk_atm(atmvcc)->sk_wmem_alloc);
|
||||
ATM_SKB(skb)->atm_options = atmvcc->atm_options;
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += skb->len;
|
||||
|
||||
if (atomic_dec_return(&brvcc->qspace) < 1) {
|
||||
/* No more please! */
|
||||
netif_stop_queue(brvcc->device);
|
||||
/* We might have raced with br2684_pop() */
|
||||
if (unlikely(atomic_read(&brvcc->qspace) > 0))
|
||||
netif_wake_queue(brvcc->device);
|
||||
}
|
||||
|
||||
/* If this fails immediately, the skb will be freed and br2684_pop()
|
||||
will wake the queue if appropriate. Just return an error so that
|
||||
the stats are updated correctly */
|
||||
return !atmvcc->send(atmvcc, skb);
|
||||
}
|
||||
|
||||
static void br2684_release_cb(struct atm_vcc *atmvcc)
|
||||
{
|
||||
struct br2684_vcc *brvcc = BR2684_VCC(atmvcc);
|
||||
|
||||
if (atomic_read(&brvcc->qspace) > 0)
|
||||
netif_wake_queue(brvcc->device);
|
||||
|
||||
if (brvcc->old_release_cb)
|
||||
brvcc->old_release_cb(atmvcc);
|
||||
}
|
||||
|
||||
static inline struct br2684_vcc *pick_outgoing_vcc(const struct sk_buff *skb,
|
||||
const struct br2684_dev *brdev)
|
||||
{
|
||||
return list_empty(&brdev->brvccs) ? NULL : list_entry_brvcc(brdev->brvccs.next); /* 1 vcc/dev right now */
|
||||
}
|
||||
|
||||
static netdev_tx_t br2684_start_xmit(struct sk_buff *skb,
|
||||
struct net_device *dev)
|
||||
{
|
||||
struct br2684_dev *brdev = BRPRIV(dev);
|
||||
struct br2684_vcc *brvcc;
|
||||
struct atm_vcc *atmvcc;
|
||||
netdev_tx_t ret = NETDEV_TX_OK;
|
||||
|
||||
pr_debug("skb_dst(skb)=%p\n", skb_dst(skb));
|
||||
read_lock(&devs_lock);
|
||||
brvcc = pick_outgoing_vcc(skb, brdev);
|
||||
if (brvcc == NULL) {
|
||||
pr_debug("no vcc attached to dev %s\n", dev->name);
|
||||
dev->stats.tx_errors++;
|
||||
dev->stats.tx_carrier_errors++;
|
||||
/* netif_stop_queue(dev); */
|
||||
dev_kfree_skb(skb);
|
||||
goto out_devs;
|
||||
}
|
||||
atmvcc = brvcc->atmvcc;
|
||||
|
||||
bh_lock_sock(sk_atm(atmvcc));
|
||||
|
||||
if (test_bit(ATM_VF_RELEASED, &atmvcc->flags) ||
|
||||
test_bit(ATM_VF_CLOSE, &atmvcc->flags) ||
|
||||
!test_bit(ATM_VF_READY, &atmvcc->flags)) {
|
||||
dev->stats.tx_dropped++;
|
||||
dev_kfree_skb(skb);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (sock_owned_by_user(sk_atm(atmvcc))) {
|
||||
netif_stop_queue(brvcc->device);
|
||||
ret = NETDEV_TX_BUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!br2684_xmit_vcc(skb, dev, brvcc)) {
|
||||
/*
|
||||
* We should probably use netif_*_queue() here, but that
|
||||
* involves added complication. We need to walk before
|
||||
* we can run.
|
||||
*
|
||||
* Don't free here! this pointer might be no longer valid!
|
||||
*/
|
||||
dev->stats.tx_errors++;
|
||||
dev->stats.tx_fifo_errors++;
|
||||
}
|
||||
out:
|
||||
bh_unlock_sock(sk_atm(atmvcc));
|
||||
out_devs:
|
||||
read_unlock(&devs_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We remember when the MAC gets set, so we don't override it later with
|
||||
* the ESI of the ATM card of the first VC
|
||||
*/
|
||||
static int br2684_mac_addr(struct net_device *dev, void *p)
|
||||
{
|
||||
int err = eth_mac_addr(dev, p);
|
||||
if (!err)
|
||||
BRPRIV(dev)->mac_was_set = 1;
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ATM_BR2684_IPFILTER
|
||||
/* this IOCTL is experimental. */
|
||||
static int br2684_setfilt(struct atm_vcc *atmvcc, void __user * arg)
|
||||
{
|
||||
struct br2684_vcc *brvcc;
|
||||
struct br2684_filter_set fs;
|
||||
|
||||
if (copy_from_user(&fs, arg, sizeof fs))
|
||||
return -EFAULT;
|
||||
if (fs.ifspec.method != BR2684_FIND_BYNOTHING) {
|
||||
/*
|
||||
* This is really a per-vcc thing, but we can also search
|
||||
* by device.
|
||||
*/
|
||||
struct br2684_dev *brdev;
|
||||
read_lock(&devs_lock);
|
||||
brdev = BRPRIV(br2684_find_dev(&fs.ifspec));
|
||||
if (brdev == NULL || list_empty(&brdev->brvccs) ||
|
||||
brdev->brvccs.next != brdev->brvccs.prev) /* >1 VCC */
|
||||
brvcc = NULL;
|
||||
else
|
||||
brvcc = list_entry_brvcc(brdev->brvccs.next);
|
||||
read_unlock(&devs_lock);
|
||||
if (brvcc == NULL)
|
||||
return -ESRCH;
|
||||
} else
|
||||
brvcc = BR2684_VCC(atmvcc);
|
||||
memcpy(&brvcc->filter, &fs.filter, sizeof(brvcc->filter));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns 1 if packet should be dropped */
|
||||
static inline int
|
||||
packet_fails_filter(__be16 type, struct br2684_vcc *brvcc, struct sk_buff *skb)
|
||||
{
|
||||
if (brvcc->filter.netmask == 0)
|
||||
return 0; /* no filter in place */
|
||||
if (type == htons(ETH_P_IP) &&
|
||||
(((struct iphdr *)(skb->data))->daddr & brvcc->filter.
|
||||
netmask) == brvcc->filter.prefix)
|
||||
return 0;
|
||||
if (type == htons(ETH_P_ARP))
|
||||
return 0;
|
||||
/*
|
||||
* TODO: we should probably filter ARPs too.. don't want to have
|
||||
* them returning values that don't make sense, or is that ok?
|
||||
*/
|
||||
return 1; /* drop */
|
||||
}
|
||||
#endif /* CONFIG_ATM_BR2684_IPFILTER */
|
||||
|
||||
static void br2684_close_vcc(struct br2684_vcc *brvcc)
|
||||
{
|
||||
pr_debug("removing VCC %p from dev %p\n", brvcc, brvcc->device);
|
||||
write_lock_irq(&devs_lock);
|
||||
list_del(&brvcc->brvccs);
|
||||
write_unlock_irq(&devs_lock);
|
||||
brvcc->atmvcc->user_back = NULL; /* what about vcc->recvq ??? */
|
||||
brvcc->atmvcc->release_cb = brvcc->old_release_cb;
|
||||
brvcc->old_push(brvcc->atmvcc, NULL); /* pass on the bad news */
|
||||
module_put(brvcc->old_owner);
|
||||
kfree(brvcc);
|
||||
}
|
||||
|
||||
/* when AAL5 PDU comes in: */
|
||||
static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
|
||||
{
|
||||
struct br2684_vcc *brvcc = BR2684_VCC(atmvcc);
|
||||
struct net_device *net_dev = brvcc->device;
|
||||
struct br2684_dev *brdev = BRPRIV(net_dev);
|
||||
|
||||
pr_debug("\n");
|
||||
|
||||
if (unlikely(skb == NULL)) {
|
||||
/* skb==NULL means VCC is being destroyed */
|
||||
br2684_close_vcc(brvcc);
|
||||
if (list_empty(&brdev->brvccs)) {
|
||||
write_lock_irq(&devs_lock);
|
||||
list_del(&brdev->br2684_devs);
|
||||
write_unlock_irq(&devs_lock);
|
||||
unregister_netdev(net_dev);
|
||||
free_netdev(net_dev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
skb_debug(skb);
|
||||
atm_return(atmvcc, skb->truesize);
|
||||
pr_debug("skb from brdev %p\n", brdev);
|
||||
if (brvcc->encaps == e_llc) {
|
||||
|
||||
if (skb->len > 7 && skb->data[7] == 0x01)
|
||||
__skb_trim(skb, skb->len - 4);
|
||||
|
||||
/* accept packets that have "ipv[46]" in the snap header */
|
||||
if ((skb->len >= (sizeof(llc_oui_ipv4))) &&
|
||||
(memcmp(skb->data, llc_oui_ipv4,
|
||||
sizeof(llc_oui_ipv4) - BR2684_ETHERTYPE_LEN) == 0)) {
|
||||
if (memcmp(skb->data + 6, ethertype_ipv6,
|
||||
sizeof(ethertype_ipv6)) == 0)
|
||||
skb->protocol = htons(ETH_P_IPV6);
|
||||
else if (memcmp(skb->data + 6, ethertype_ipv4,
|
||||
sizeof(ethertype_ipv4)) == 0)
|
||||
skb->protocol = htons(ETH_P_IP);
|
||||
else
|
||||
goto error;
|
||||
skb_pull(skb, sizeof(llc_oui_ipv4));
|
||||
skb_reset_network_header(skb);
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
/*
|
||||
* Let us waste some time for checking the encapsulation.
|
||||
* Note, that only 7 char is checked so frames with a valid FCS
|
||||
* are also accepted (but FCS is not checked of course).
|
||||
*/
|
||||
} else if ((skb->len >= sizeof(llc_oui_pid_pad)) &&
|
||||
(memcmp(skb->data, llc_oui_pid_pad, 7) == 0)) {
|
||||
skb_pull(skb, sizeof(llc_oui_pid_pad));
|
||||
skb->protocol = eth_type_trans(skb, net_dev);
|
||||
} else
|
||||
goto error;
|
||||
|
||||
} else { /* e_vc */
|
||||
if (brdev->payload == p_routed) {
|
||||
struct iphdr *iph;
|
||||
|
||||
skb_reset_network_header(skb);
|
||||
iph = ip_hdr(skb);
|
||||
if (iph->version == 4)
|
||||
skb->protocol = htons(ETH_P_IP);
|
||||
else if (iph->version == 6)
|
||||
skb->protocol = htons(ETH_P_IPV6);
|
||||
else
|
||||
goto error;
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
} else { /* p_bridged */
|
||||
/* first 2 chars should be 0 */
|
||||
if (memcmp(skb->data, pad, BR2684_PAD_LEN) != 0)
|
||||
goto error;
|
||||
skb_pull(skb, BR2684_PAD_LEN);
|
||||
skb->protocol = eth_type_trans(skb, net_dev);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ATM_BR2684_IPFILTER
|
||||
if (unlikely(packet_fails_filter(skb->protocol, brvcc, skb)))
|
||||
goto dropped;
|
||||
#endif /* CONFIG_ATM_BR2684_IPFILTER */
|
||||
skb->dev = net_dev;
|
||||
ATM_SKB(skb)->vcc = atmvcc; /* needed ? */
|
||||
pr_debug("received packet's protocol: %x\n", ntohs(skb->protocol));
|
||||
skb_debug(skb);
|
||||
/* sigh, interface is down? */
|
||||
if (unlikely(!(net_dev->flags & IFF_UP)))
|
||||
goto dropped;
|
||||
net_dev->stats.rx_packets++;
|
||||
net_dev->stats.rx_bytes += skb->len;
|
||||
memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data));
|
||||
netif_rx(skb);
|
||||
return;
|
||||
|
||||
dropped:
|
||||
net_dev->stats.rx_dropped++;
|
||||
goto free_skb;
|
||||
error:
|
||||
net_dev->stats.rx_errors++;
|
||||
free_skb:
|
||||
dev_kfree_skb(skb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign a vcc to a dev
|
||||
* Note: we do not have explicit unassign, but look at _push()
|
||||
*/
|
||||
static int br2684_regvcc(struct atm_vcc *atmvcc, void __user * arg)
|
||||
{
|
||||
struct br2684_vcc *brvcc;
|
||||
struct br2684_dev *brdev;
|
||||
struct net_device *net_dev;
|
||||
struct atm_backend_br2684 be;
|
||||
int err;
|
||||
|
||||
if (copy_from_user(&be, arg, sizeof be))
|
||||
return -EFAULT;
|
||||
brvcc = kzalloc(sizeof(struct br2684_vcc), GFP_KERNEL);
|
||||
if (!brvcc)
|
||||
return -ENOMEM;
|
||||
/*
|
||||
* Allow two packets in the ATM queue. One actually being sent, and one
|
||||
* for the ATM 'TX done' handler to send. It shouldn't take long to get
|
||||
* the next one from the netdev queue, when we need it. More than that
|
||||
* would be bufferbloat.
|
||||
*/
|
||||
atomic_set(&brvcc->qspace, 2);
|
||||
write_lock_irq(&devs_lock);
|
||||
net_dev = br2684_find_dev(&be.ifspec);
|
||||
if (net_dev == NULL) {
|
||||
pr_err("tried to attach to non-existent device\n");
|
||||
err = -ENXIO;
|
||||
goto error;
|
||||
}
|
||||
brdev = BRPRIV(net_dev);
|
||||
if (atmvcc->push == NULL) {
|
||||
err = -EBADFD;
|
||||
goto error;
|
||||
}
|
||||
if (!list_empty(&brdev->brvccs)) {
|
||||
/* Only 1 VCC/dev right now */
|
||||
err = -EEXIST;
|
||||
goto error;
|
||||
}
|
||||
if (be.fcs_in != BR2684_FCSIN_NO ||
|
||||
be.fcs_out != BR2684_FCSOUT_NO ||
|
||||
be.fcs_auto || be.has_vpiid || be.send_padding ||
|
||||
(be.encaps != BR2684_ENCAPS_VC &&
|
||||
be.encaps != BR2684_ENCAPS_LLC) ||
|
||||
be.min_size != 0) {
|
||||
err = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
pr_debug("vcc=%p, encaps=%d, brvcc=%p\n", atmvcc, be.encaps, brvcc);
|
||||
if (list_empty(&brdev->brvccs) && !brdev->mac_was_set) {
|
||||
unsigned char *esi = atmvcc->dev->esi;
|
||||
if (esi[0] | esi[1] | esi[2] | esi[3] | esi[4] | esi[5])
|
||||
memcpy(net_dev->dev_addr, esi, net_dev->addr_len);
|
||||
else
|
||||
net_dev->dev_addr[2] = 1;
|
||||
}
|
||||
list_add(&brvcc->brvccs, &brdev->brvccs);
|
||||
write_unlock_irq(&devs_lock);
|
||||
brvcc->device = net_dev;
|
||||
brvcc->atmvcc = atmvcc;
|
||||
atmvcc->user_back = brvcc;
|
||||
brvcc->encaps = (enum br2684_encaps)be.encaps;
|
||||
brvcc->old_push = atmvcc->push;
|
||||
brvcc->old_pop = atmvcc->pop;
|
||||
brvcc->old_release_cb = atmvcc->release_cb;
|
||||
brvcc->old_owner = atmvcc->owner;
|
||||
barrier();
|
||||
atmvcc->push = br2684_push;
|
||||
atmvcc->pop = br2684_pop;
|
||||
atmvcc->release_cb = br2684_release_cb;
|
||||
atmvcc->owner = THIS_MODULE;
|
||||
|
||||
/* initialize netdev carrier state */
|
||||
if (atmvcc->dev->signal == ATM_PHY_SIG_LOST)
|
||||
netif_carrier_off(net_dev);
|
||||
else
|
||||
netif_carrier_on(net_dev);
|
||||
|
||||
__module_get(THIS_MODULE);
|
||||
|
||||
/* re-process everything received between connection setup and
|
||||
backend setup */
|
||||
vcc_process_recv_queue(atmvcc);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
write_unlock_irq(&devs_lock);
|
||||
kfree(brvcc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct net_device_ops br2684_netdev_ops = {
|
||||
.ndo_start_xmit = br2684_start_xmit,
|
||||
.ndo_set_mac_address = br2684_mac_addr,
|
||||
.ndo_change_mtu = eth_change_mtu,
|
||||
.ndo_validate_addr = eth_validate_addr,
|
||||
};
|
||||
|
||||
static const struct net_device_ops br2684_netdev_ops_routed = {
|
||||
.ndo_start_xmit = br2684_start_xmit,
|
||||
.ndo_set_mac_address = br2684_mac_addr,
|
||||
.ndo_change_mtu = eth_change_mtu
|
||||
};
|
||||
|
||||
static void br2684_setup(struct net_device *netdev)
|
||||
{
|
||||
struct br2684_dev *brdev = BRPRIV(netdev);
|
||||
|
||||
ether_setup(netdev);
|
||||
netdev->hard_header_len += sizeof(llc_oui_pid_pad); /* worst case */
|
||||
brdev->net_dev = netdev;
|
||||
|
||||
netdev->netdev_ops = &br2684_netdev_ops;
|
||||
|
||||
INIT_LIST_HEAD(&brdev->brvccs);
|
||||
}
|
||||
|
||||
static void br2684_setup_routed(struct net_device *netdev)
|
||||
{
|
||||
struct br2684_dev *brdev = BRPRIV(netdev);
|
||||
|
||||
brdev->net_dev = netdev;
|
||||
netdev->hard_header_len = sizeof(llc_oui_ipv4); /* worst case */
|
||||
netdev->netdev_ops = &br2684_netdev_ops_routed;
|
||||
netdev->addr_len = 0;
|
||||
netdev->mtu = 1500;
|
||||
netdev->type = ARPHRD_PPP;
|
||||
netdev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
|
||||
netdev->tx_queue_len = 100;
|
||||
INIT_LIST_HEAD(&brdev->brvccs);
|
||||
}
|
||||
|
||||
static int br2684_create(void __user *arg)
|
||||
{
|
||||
int err;
|
||||
struct net_device *netdev;
|
||||
struct br2684_dev *brdev;
|
||||
struct atm_newif_br2684 ni;
|
||||
enum br2684_payload payload;
|
||||
|
||||
pr_debug("\n");
|
||||
|
||||
if (copy_from_user(&ni, arg, sizeof ni))
|
||||
return -EFAULT;
|
||||
|
||||
if (ni.media & BR2684_FLAG_ROUTED)
|
||||
payload = p_routed;
|
||||
else
|
||||
payload = p_bridged;
|
||||
ni.media &= 0xffff; /* strip flags */
|
||||
|
||||
if (ni.media != BR2684_MEDIA_ETHERNET || ni.mtu != 1500)
|
||||
return -EINVAL;
|
||||
|
||||
netdev = alloc_netdev(sizeof(struct br2684_dev),
|
||||
ni.ifname[0] ? ni.ifname : "nas%d",
|
||||
(payload == p_routed) ?
|
||||
br2684_setup_routed : br2684_setup);
|
||||
if (!netdev)
|
||||
return -ENOMEM;
|
||||
|
||||
brdev = BRPRIV(netdev);
|
||||
|
||||
pr_debug("registered netdev %s\n", netdev->name);
|
||||
/* open, stop, do_ioctl ? */
|
||||
err = register_netdev(netdev);
|
||||
if (err < 0) {
|
||||
pr_err("register_netdev failed\n");
|
||||
free_netdev(netdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
write_lock_irq(&devs_lock);
|
||||
|
||||
brdev->payload = payload;
|
||||
|
||||
if (list_empty(&br2684_devs)) {
|
||||
/* 1st br2684 device */
|
||||
brdev->number = 1;
|
||||
} else
|
||||
brdev->number = BRPRIV(list_entry_brdev(br2684_devs.prev))->number + 1;
|
||||
|
||||
list_add_tail(&brdev->br2684_devs, &br2684_devs);
|
||||
write_unlock_irq(&devs_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This handles ioctls actually performed on our vcc - we must return
|
||||
* -ENOIOCTLCMD for any unrecognized ioctl
|
||||
*/
|
||||
static int br2684_ioctl(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct atm_vcc *atmvcc = ATM_SD(sock);
|
||||
void __user *argp = (void __user *)arg;
|
||||
atm_backend_t b;
|
||||
|
||||
int err;
|
||||
switch (cmd) {
|
||||
case ATM_SETBACKEND:
|
||||
case ATM_NEWBACKENDIF:
|
||||
err = get_user(b, (atm_backend_t __user *) argp);
|
||||
if (err)
|
||||
return -EFAULT;
|
||||
if (b != ATM_BACKEND_BR2684)
|
||||
return -ENOIOCTLCMD;
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
if (cmd == ATM_SETBACKEND) {
|
||||
if (sock->state != SS_CONNECTED)
|
||||
return -EINVAL;
|
||||
return br2684_regvcc(atmvcc, argp);
|
||||
} else {
|
||||
return br2684_create(argp);
|
||||
}
|
||||
#ifdef CONFIG_ATM_BR2684_IPFILTER
|
||||
case BR2684_SETFILT:
|
||||
if (atmvcc->push != br2684_push)
|
||||
return -ENOIOCTLCMD;
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
err = br2684_setfilt(atmvcc, argp);
|
||||
|
||||
return err;
|
||||
#endif /* CONFIG_ATM_BR2684_IPFILTER */
|
||||
}
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
static struct atm_ioctl br2684_ioctl_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.ioctl = br2684_ioctl,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static void *br2684_seq_start(struct seq_file *seq, loff_t * pos)
|
||||
__acquires(devs_lock)
|
||||
{
|
||||
read_lock(&devs_lock);
|
||||
return seq_list_start(&br2684_devs, *pos);
|
||||
}
|
||||
|
||||
static void *br2684_seq_next(struct seq_file *seq, void *v, loff_t * pos)
|
||||
{
|
||||
return seq_list_next(v, &br2684_devs, pos);
|
||||
}
|
||||
|
||||
static void br2684_seq_stop(struct seq_file *seq, void *v)
|
||||
__releases(devs_lock)
|
||||
{
|
||||
read_unlock(&devs_lock);
|
||||
}
|
||||
|
||||
static int br2684_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
const struct br2684_dev *brdev = list_entry(v, struct br2684_dev,
|
||||
br2684_devs);
|
||||
const struct net_device *net_dev = brdev->net_dev;
|
||||
const struct br2684_vcc *brvcc;
|
||||
|
||||
seq_printf(seq, "dev %.16s: num=%d, mac=%pM (%s)\n",
|
||||
net_dev->name,
|
||||
brdev->number,
|
||||
net_dev->dev_addr,
|
||||
brdev->mac_was_set ? "set" : "auto");
|
||||
|
||||
list_for_each_entry(brvcc, &brdev->brvccs, brvccs) {
|
||||
seq_printf(seq, " vcc %d.%d.%d: encaps=%s payload=%s"
|
||||
", failed copies %u/%u"
|
||||
"\n", brvcc->atmvcc->dev->number,
|
||||
brvcc->atmvcc->vpi, brvcc->atmvcc->vci,
|
||||
(brvcc->encaps == e_llc) ? "LLC" : "VC",
|
||||
(brdev->payload == p_bridged) ? "bridged" : "routed",
|
||||
brvcc->copies_failed, brvcc->copies_needed);
|
||||
#ifdef CONFIG_ATM_BR2684_IPFILTER
|
||||
#define b1(var, byte) ((u8 *) &brvcc->filter.var)[byte]
|
||||
#define bs(var) b1(var, 0), b1(var, 1), b1(var, 2), b1(var, 3)
|
||||
if (brvcc->filter.netmask != 0)
|
||||
seq_printf(seq, " filter=%d.%d.%d.%d/"
|
||||
"%d.%d.%d.%d\n", bs(prefix), bs(netmask));
|
||||
#undef bs
|
||||
#undef b1
|
||||
#endif /* CONFIG_ATM_BR2684_IPFILTER */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations br2684_seq_ops = {
|
||||
.start = br2684_seq_start,
|
||||
.next = br2684_seq_next,
|
||||
.stop = br2684_seq_stop,
|
||||
.show = br2684_seq_show,
|
||||
};
|
||||
|
||||
static int br2684_proc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_open(file, &br2684_seq_ops);
|
||||
}
|
||||
|
||||
static const struct file_operations br2684_proc_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = br2684_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
extern struct proc_dir_entry *atm_proc_root; /* from proc.c */
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
static int __init br2684_init(void)
|
||||
{
|
||||
#ifdef CONFIG_PROC_FS
|
||||
struct proc_dir_entry *p;
|
||||
p = proc_create("br2684", 0, atm_proc_root, &br2684_proc_ops);
|
||||
if (p == NULL)
|
||||
return -ENOMEM;
|
||||
#endif
|
||||
register_atm_ioctl(&br2684_ioctl_ops);
|
||||
register_atmdevice_notifier(&atm_dev_notifier);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit br2684_exit(void)
|
||||
{
|
||||
struct net_device *net_dev;
|
||||
struct br2684_dev *brdev;
|
||||
struct br2684_vcc *brvcc;
|
||||
deregister_atm_ioctl(&br2684_ioctl_ops);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
remove_proc_entry("br2684", atm_proc_root);
|
||||
#endif
|
||||
|
||||
|
||||
unregister_atmdevice_notifier(&atm_dev_notifier);
|
||||
|
||||
while (!list_empty(&br2684_devs)) {
|
||||
net_dev = list_entry_brdev(br2684_devs.next);
|
||||
brdev = BRPRIV(net_dev);
|
||||
while (!list_empty(&brdev->brvccs)) {
|
||||
brvcc = list_entry_brvcc(brdev->brvccs.next);
|
||||
br2684_close_vcc(brvcc);
|
||||
}
|
||||
|
||||
list_del(&brdev->br2684_devs);
|
||||
unregister_netdev(net_dev);
|
||||
free_netdev(net_dev);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(br2684_init);
|
||||
module_exit(br2684_exit);
|
||||
|
||||
MODULE_AUTHOR("Marcell GAL");
|
||||
MODULE_DESCRIPTION("RFC2684 bridged protocols over ATM/AAL5");
|
||||
MODULE_LICENSE("GPL");
|
936
net/atm/clip.c
Normal file
936
net/atm/clip.c
Normal file
|
@ -0,0 +1,936 @@
|
|||
/* net/atm/clip.c - RFC1577 Classical IP over ATM */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/string.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h> /* for UINT_MAX */
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/if_arp.h> /* for some manifest constants */
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/atmclip.h>
|
||||
#include <linux/atmarp.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/ip.h> /* for net/route.h */
|
||||
#include <linux/in.h> /* for struct sockaddr_in */
|
||||
#include <linux/if.h> /* for IFF_UP */
|
||||
#include <linux/inetdevice.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/poison.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/jhash.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/route.h> /* for struct rtable and routing */
|
||||
#include <net/icmp.h> /* icmp_send */
|
||||
#include <net/arp.h>
|
||||
#include <linux/param.h> /* for HZ */
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/byteorder.h> /* for htons etc. */
|
||||
#include <linux/atomic.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "resources.h"
|
||||
#include <net/atmclip.h>
|
||||
|
||||
static struct net_device *clip_devs;
|
||||
static struct atm_vcc *atmarpd;
|
||||
static struct timer_list idle_timer;
|
||||
static const struct neigh_ops clip_neigh_ops;
|
||||
|
||||
static int to_atmarpd(enum atmarp_ctrl_type type, int itf, __be32 ip)
|
||||
{
|
||||
struct sock *sk;
|
||||
struct atmarp_ctrl *ctrl;
|
||||
struct sk_buff *skb;
|
||||
|
||||
pr_debug("(%d)\n", type);
|
||||
if (!atmarpd)
|
||||
return -EUNATCH;
|
||||
skb = alloc_skb(sizeof(struct atmarp_ctrl), GFP_ATOMIC);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
ctrl = (struct atmarp_ctrl *)skb_put(skb, sizeof(struct atmarp_ctrl));
|
||||
ctrl->type = type;
|
||||
ctrl->itf_num = itf;
|
||||
ctrl->ip = ip;
|
||||
atm_force_charge(atmarpd, skb->truesize);
|
||||
|
||||
sk = sk_atm(atmarpd);
|
||||
skb_queue_tail(&sk->sk_receive_queue, skb);
|
||||
sk->sk_data_ready(sk, skb->len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void link_vcc(struct clip_vcc *clip_vcc, struct atmarp_entry *entry)
|
||||
{
|
||||
pr_debug("%p to entry %p (neigh %p)\n", clip_vcc, entry, entry->neigh);
|
||||
clip_vcc->entry = entry;
|
||||
clip_vcc->xoff = 0; /* @@@ may overrun buffer by one packet */
|
||||
clip_vcc->next = entry->vccs;
|
||||
entry->vccs = clip_vcc;
|
||||
entry->neigh->used = jiffies;
|
||||
}
|
||||
|
||||
static void unlink_clip_vcc(struct clip_vcc *clip_vcc)
|
||||
{
|
||||
struct atmarp_entry *entry = clip_vcc->entry;
|
||||
struct clip_vcc **walk;
|
||||
|
||||
if (!entry) {
|
||||
pr_crit("!clip_vcc->entry (clip_vcc %p)\n", clip_vcc);
|
||||
return;
|
||||
}
|
||||
netif_tx_lock_bh(entry->neigh->dev); /* block clip_start_xmit() */
|
||||
entry->neigh->used = jiffies;
|
||||
for (walk = &entry->vccs; *walk; walk = &(*walk)->next)
|
||||
if (*walk == clip_vcc) {
|
||||
int error;
|
||||
|
||||
*walk = clip_vcc->next; /* atomic */
|
||||
clip_vcc->entry = NULL;
|
||||
if (clip_vcc->xoff)
|
||||
netif_wake_queue(entry->neigh->dev);
|
||||
if (entry->vccs)
|
||||
goto out;
|
||||
entry->expires = jiffies - 1;
|
||||
/* force resolution or expiration */
|
||||
error = neigh_update(entry->neigh, NULL, NUD_NONE,
|
||||
NEIGH_UPDATE_F_ADMIN);
|
||||
if (error)
|
||||
pr_crit("neigh_update failed with %d\n", error);
|
||||
goto out;
|
||||
}
|
||||
pr_crit("ATMARP: failed (entry %p, vcc 0x%p)\n", entry, clip_vcc);
|
||||
out:
|
||||
netif_tx_unlock_bh(entry->neigh->dev);
|
||||
}
|
||||
|
||||
/* The neighbour entry n->lock is held. */
|
||||
static int neigh_check_cb(struct neighbour *n)
|
||||
{
|
||||
struct atmarp_entry *entry = neighbour_priv(n);
|
||||
struct clip_vcc *cv;
|
||||
|
||||
if (n->ops != &clip_neigh_ops)
|
||||
return 0;
|
||||
for (cv = entry->vccs; cv; cv = cv->next) {
|
||||
unsigned long exp = cv->last_use + cv->idle_timeout;
|
||||
|
||||
if (cv->idle_timeout && time_after(jiffies, exp)) {
|
||||
pr_debug("releasing vcc %p->%p of entry %p\n",
|
||||
cv, cv->vcc, entry);
|
||||
vcc_release_async(cv->vcc, -ETIMEDOUT);
|
||||
}
|
||||
}
|
||||
|
||||
if (entry->vccs || time_before(jiffies, entry->expires))
|
||||
return 0;
|
||||
|
||||
if (atomic_read(&n->refcnt) > 1) {
|
||||
struct sk_buff *skb;
|
||||
|
||||
pr_debug("destruction postponed with ref %d\n",
|
||||
atomic_read(&n->refcnt));
|
||||
|
||||
while ((skb = skb_dequeue(&n->arp_queue)) != NULL)
|
||||
dev_kfree_skb(skb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
pr_debug("expired neigh %p\n", n);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void idle_timer_check(unsigned long dummy)
|
||||
{
|
||||
write_lock(&arp_tbl.lock);
|
||||
__neigh_for_each_release(&arp_tbl, neigh_check_cb);
|
||||
mod_timer(&idle_timer, jiffies + CLIP_CHECK_INTERVAL * HZ);
|
||||
write_unlock(&arp_tbl.lock);
|
||||
}
|
||||
|
||||
static int clip_arp_rcv(struct sk_buff *skb)
|
||||
{
|
||||
struct atm_vcc *vcc;
|
||||
|
||||
pr_debug("\n");
|
||||
vcc = ATM_SKB(skb)->vcc;
|
||||
if (!vcc || !atm_charge(vcc, skb->truesize)) {
|
||||
dev_kfree_skb_any(skb);
|
||||
return 0;
|
||||
}
|
||||
pr_debug("pushing to %p\n", vcc);
|
||||
pr_debug("using %p\n", CLIP_VCC(vcc)->old_push);
|
||||
CLIP_VCC(vcc)->old_push(vcc, skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const unsigned char llc_oui[] = {
|
||||
0xaa, /* DSAP: non-ISO */
|
||||
0xaa, /* SSAP: non-ISO */
|
||||
0x03, /* Ctrl: Unnumbered Information Command PDU */
|
||||
0x00, /* OUI: EtherType */
|
||||
0x00,
|
||||
0x00
|
||||
};
|
||||
|
||||
static void clip_push(struct atm_vcc *vcc, struct sk_buff *skb)
|
||||
{
|
||||
struct clip_vcc *clip_vcc = CLIP_VCC(vcc);
|
||||
|
||||
pr_debug("\n");
|
||||
|
||||
if (!clip_devs) {
|
||||
atm_return(vcc, skb->truesize);
|
||||
kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!skb) {
|
||||
pr_debug("removing VCC %p\n", clip_vcc);
|
||||
if (clip_vcc->entry)
|
||||
unlink_clip_vcc(clip_vcc);
|
||||
clip_vcc->old_push(vcc, NULL); /* pass on the bad news */
|
||||
kfree(clip_vcc);
|
||||
return;
|
||||
}
|
||||
atm_return(vcc, skb->truesize);
|
||||
skb->dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : clip_devs;
|
||||
/* clip_vcc->entry == NULL if we don't have an IP address yet */
|
||||
if (!skb->dev) {
|
||||
dev_kfree_skb_any(skb);
|
||||
return;
|
||||
}
|
||||
ATM_SKB(skb)->vcc = vcc;
|
||||
skb_reset_mac_header(skb);
|
||||
if (!clip_vcc->encap ||
|
||||
skb->len < RFC1483LLC_LEN ||
|
||||
memcmp(skb->data, llc_oui, sizeof(llc_oui)))
|
||||
skb->protocol = htons(ETH_P_IP);
|
||||
else {
|
||||
skb->protocol = ((__be16 *)skb->data)[3];
|
||||
skb_pull(skb, RFC1483LLC_LEN);
|
||||
if (skb->protocol == htons(ETH_P_ARP)) {
|
||||
skb->dev->stats.rx_packets++;
|
||||
skb->dev->stats.rx_bytes += skb->len;
|
||||
clip_arp_rcv(skb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
clip_vcc->last_use = jiffies;
|
||||
skb->dev->stats.rx_packets++;
|
||||
skb->dev->stats.rx_bytes += skb->len;
|
||||
memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data));
|
||||
netif_rx(skb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: these spinlocks _must_not_ block on non-SMP. The only goal is that
|
||||
* clip_pop is atomic with respect to the critical section in clip_start_xmit.
|
||||
*/
|
||||
|
||||
static void clip_pop(struct atm_vcc *vcc, struct sk_buff *skb)
|
||||
{
|
||||
struct clip_vcc *clip_vcc = CLIP_VCC(vcc);
|
||||
struct net_device *dev = skb->dev;
|
||||
int old;
|
||||
unsigned long flags;
|
||||
|
||||
pr_debug("(vcc %p)\n", vcc);
|
||||
clip_vcc->old_pop(vcc, skb);
|
||||
/* skb->dev == NULL in outbound ARP packets */
|
||||
if (!dev)
|
||||
return;
|
||||
spin_lock_irqsave(&PRIV(dev)->xoff_lock, flags);
|
||||
if (atm_may_send(vcc, 0)) {
|
||||
old = xchg(&clip_vcc->xoff, 0);
|
||||
if (old)
|
||||
netif_wake_queue(dev);
|
||||
}
|
||||
spin_unlock_irqrestore(&PRIV(dev)->xoff_lock, flags);
|
||||
}
|
||||
|
||||
static void clip_neigh_solicit(struct neighbour *neigh, struct sk_buff *skb)
|
||||
{
|
||||
__be32 *ip = (__be32 *) neigh->primary_key;
|
||||
|
||||
pr_debug("(neigh %p, skb %p)\n", neigh, skb);
|
||||
to_atmarpd(act_need, PRIV(neigh->dev)->number, *ip);
|
||||
}
|
||||
|
||||
static void clip_neigh_error(struct neighbour *neigh, struct sk_buff *skb)
|
||||
{
|
||||
#ifndef CONFIG_ATM_CLIP_NO_ICMP
|
||||
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
|
||||
#endif
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
static const struct neigh_ops clip_neigh_ops = {
|
||||
.family = AF_INET,
|
||||
.solicit = clip_neigh_solicit,
|
||||
.error_report = clip_neigh_error,
|
||||
.output = neigh_direct_output,
|
||||
.connected_output = neigh_direct_output,
|
||||
};
|
||||
|
||||
static int clip_constructor(struct neighbour *neigh)
|
||||
{
|
||||
struct atmarp_entry *entry = neighbour_priv(neigh);
|
||||
|
||||
if (neigh->tbl->family != AF_INET)
|
||||
return -EINVAL;
|
||||
|
||||
if (neigh->type != RTN_UNICAST)
|
||||
return -EINVAL;
|
||||
|
||||
neigh->nud_state = NUD_NONE;
|
||||
neigh->ops = &clip_neigh_ops;
|
||||
neigh->output = neigh->ops->output;
|
||||
entry->neigh = neigh;
|
||||
entry->vccs = NULL;
|
||||
entry->expires = jiffies - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @@@ copy bh locking from arp.c -- need to bh-enable atm code before */
|
||||
|
||||
/*
|
||||
* We play with the resolve flag: 0 and 1 have the usual meaning, but -1 means
|
||||
* to allocate the neighbour entry but not to ask atmarpd for resolution. Also,
|
||||
* don't increment the usage count. This is used to create entries in
|
||||
* clip_setentry.
|
||||
*/
|
||||
|
||||
static int clip_encap(struct atm_vcc *vcc, int mode)
|
||||
{
|
||||
CLIP_VCC(vcc)->encap = mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static netdev_tx_t clip_start_xmit(struct sk_buff *skb,
|
||||
struct net_device *dev)
|
||||
{
|
||||
struct clip_priv *clip_priv = PRIV(dev);
|
||||
struct dst_entry *dst = skb_dst(skb);
|
||||
struct atmarp_entry *entry;
|
||||
struct neighbour *n;
|
||||
struct atm_vcc *vcc;
|
||||
struct rtable *rt;
|
||||
__be32 *daddr;
|
||||
int old;
|
||||
unsigned long flags;
|
||||
|
||||
pr_debug("(skb %p)\n", skb);
|
||||
if (!dst) {
|
||||
pr_err("skb_dst(skb) == NULL\n");
|
||||
dev_kfree_skb(skb);
|
||||
dev->stats.tx_dropped++;
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
rt = (struct rtable *) dst;
|
||||
if (rt->rt_gateway)
|
||||
daddr = &rt->rt_gateway;
|
||||
else
|
||||
daddr = &ip_hdr(skb)->daddr;
|
||||
n = dst_neigh_lookup(dst, daddr);
|
||||
if (!n) {
|
||||
pr_err("NO NEIGHBOUR !\n");
|
||||
dev_kfree_skb(skb);
|
||||
dev->stats.tx_dropped++;
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
entry = neighbour_priv(n);
|
||||
if (!entry->vccs) {
|
||||
if (time_after(jiffies, entry->expires)) {
|
||||
/* should be resolved */
|
||||
entry->expires = jiffies + ATMARP_RETRY_DELAY * HZ;
|
||||
to_atmarpd(act_need, PRIV(dev)->number, *((__be32 *)n->primary_key));
|
||||
}
|
||||
if (entry->neigh->arp_queue.qlen < ATMARP_MAX_UNRES_PACKETS)
|
||||
skb_queue_tail(&entry->neigh->arp_queue, skb);
|
||||
else {
|
||||
dev_kfree_skb(skb);
|
||||
dev->stats.tx_dropped++;
|
||||
}
|
||||
goto out_release_neigh;
|
||||
}
|
||||
pr_debug("neigh %p, vccs %p\n", entry, entry->vccs);
|
||||
ATM_SKB(skb)->vcc = vcc = entry->vccs->vcc;
|
||||
pr_debug("using neighbour %p, vcc %p\n", n, vcc);
|
||||
if (entry->vccs->encap) {
|
||||
void *here;
|
||||
|
||||
here = skb_push(skb, RFC1483LLC_LEN);
|
||||
memcpy(here, llc_oui, sizeof(llc_oui));
|
||||
((__be16 *) here)[3] = skb->protocol;
|
||||
}
|
||||
atomic_add(skb->truesize, &sk_atm(vcc)->sk_wmem_alloc);
|
||||
ATM_SKB(skb)->atm_options = vcc->atm_options;
|
||||
entry->vccs->last_use = jiffies;
|
||||
pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, vcc, vcc->dev);
|
||||
old = xchg(&entry->vccs->xoff, 1); /* assume XOFF ... */
|
||||
if (old) {
|
||||
pr_warning("XOFF->XOFF transition\n");
|
||||
goto out_release_neigh;
|
||||
}
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += skb->len;
|
||||
vcc->send(vcc, skb);
|
||||
if (atm_may_send(vcc, 0)) {
|
||||
entry->vccs->xoff = 0;
|
||||
goto out_release_neigh;
|
||||
}
|
||||
spin_lock_irqsave(&clip_priv->xoff_lock, flags);
|
||||
netif_stop_queue(dev); /* XOFF -> throttle immediately */
|
||||
barrier();
|
||||
if (!entry->vccs->xoff)
|
||||
netif_start_queue(dev);
|
||||
/* Oh, we just raced with clip_pop. netif_start_queue should be
|
||||
good enough, because nothing should really be asleep because
|
||||
of the brief netif_stop_queue. If this isn't true or if it
|
||||
changes, use netif_wake_queue instead. */
|
||||
spin_unlock_irqrestore(&clip_priv->xoff_lock, flags);
|
||||
out_release_neigh:
|
||||
neigh_release(n);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int clip_mkip(struct atm_vcc *vcc, int timeout)
|
||||
{
|
||||
struct clip_vcc *clip_vcc;
|
||||
|
||||
if (!vcc->push)
|
||||
return -EBADFD;
|
||||
clip_vcc = kmalloc(sizeof(struct clip_vcc), GFP_KERNEL);
|
||||
if (!clip_vcc)
|
||||
return -ENOMEM;
|
||||
pr_debug("%p vcc %p\n", clip_vcc, vcc);
|
||||
clip_vcc->vcc = vcc;
|
||||
vcc->user_back = clip_vcc;
|
||||
set_bit(ATM_VF_IS_CLIP, &vcc->flags);
|
||||
clip_vcc->entry = NULL;
|
||||
clip_vcc->xoff = 0;
|
||||
clip_vcc->encap = 1;
|
||||
clip_vcc->last_use = jiffies;
|
||||
clip_vcc->idle_timeout = timeout * HZ;
|
||||
clip_vcc->old_push = vcc->push;
|
||||
clip_vcc->old_pop = vcc->pop;
|
||||
vcc->push = clip_push;
|
||||
vcc->pop = clip_pop;
|
||||
|
||||
/* re-process everything received between connection setup and MKIP */
|
||||
vcc_process_recv_queue(vcc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clip_setentry(struct atm_vcc *vcc, __be32 ip)
|
||||
{
|
||||
struct neighbour *neigh;
|
||||
struct atmarp_entry *entry;
|
||||
int error;
|
||||
struct clip_vcc *clip_vcc;
|
||||
struct rtable *rt;
|
||||
|
||||
if (vcc->push != clip_push) {
|
||||
pr_warning("non-CLIP VCC\n");
|
||||
return -EBADF;
|
||||
}
|
||||
clip_vcc = CLIP_VCC(vcc);
|
||||
if (!ip) {
|
||||
if (!clip_vcc->entry) {
|
||||
pr_err("hiding hidden ATMARP entry\n");
|
||||
return 0;
|
||||
}
|
||||
pr_debug("remove\n");
|
||||
unlink_clip_vcc(clip_vcc);
|
||||
return 0;
|
||||
}
|
||||
rt = ip_route_output(&init_net, ip, 0, 1, 0);
|
||||
if (IS_ERR(rt))
|
||||
return PTR_ERR(rt);
|
||||
neigh = __neigh_lookup(&arp_tbl, &ip, rt->dst.dev, 1);
|
||||
ip_rt_put(rt);
|
||||
if (!neigh)
|
||||
return -ENOMEM;
|
||||
entry = neighbour_priv(neigh);
|
||||
if (entry != clip_vcc->entry) {
|
||||
if (!clip_vcc->entry)
|
||||
pr_debug("add\n");
|
||||
else {
|
||||
pr_debug("update\n");
|
||||
unlink_clip_vcc(clip_vcc);
|
||||
}
|
||||
link_vcc(clip_vcc, entry);
|
||||
}
|
||||
error = neigh_update(neigh, llc_oui, NUD_PERMANENT,
|
||||
NEIGH_UPDATE_F_OVERRIDE | NEIGH_UPDATE_F_ADMIN);
|
||||
neigh_release(neigh);
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct net_device_ops clip_netdev_ops = {
|
||||
.ndo_start_xmit = clip_start_xmit,
|
||||
.ndo_neigh_construct = clip_constructor,
|
||||
};
|
||||
|
||||
static void clip_setup(struct net_device *dev)
|
||||
{
|
||||
dev->netdev_ops = &clip_netdev_ops;
|
||||
dev->type = ARPHRD_ATM;
|
||||
dev->neigh_priv_len = sizeof(struct atmarp_entry);
|
||||
dev->hard_header_len = RFC1483LLC_LEN;
|
||||
dev->mtu = RFC1626_MTU;
|
||||
dev->tx_queue_len = 100; /* "normal" queue (packets) */
|
||||
/* When using a "real" qdisc, the qdisc determines the queue */
|
||||
/* length. tx_queue_len is only used for the default case, */
|
||||
/* without any more elaborate queuing. 100 is a reasonable */
|
||||
/* compromise between decent burst-tolerance and protection */
|
||||
/* against memory hogs. */
|
||||
dev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
|
||||
}
|
||||
|
||||
static int clip_create(int number)
|
||||
{
|
||||
struct net_device *dev;
|
||||
struct clip_priv *clip_priv;
|
||||
int error;
|
||||
|
||||
if (number != -1) {
|
||||
for (dev = clip_devs; dev; dev = PRIV(dev)->next)
|
||||
if (PRIV(dev)->number == number)
|
||||
return -EEXIST;
|
||||
} else {
|
||||
number = 0;
|
||||
for (dev = clip_devs; dev; dev = PRIV(dev)->next)
|
||||
if (PRIV(dev)->number >= number)
|
||||
number = PRIV(dev)->number + 1;
|
||||
}
|
||||
dev = alloc_netdev(sizeof(struct clip_priv), "", clip_setup);
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
clip_priv = PRIV(dev);
|
||||
sprintf(dev->name, "atm%d", number);
|
||||
spin_lock_init(&clip_priv->xoff_lock);
|
||||
clip_priv->number = number;
|
||||
error = register_netdev(dev);
|
||||
if (error) {
|
||||
free_netdev(dev);
|
||||
return error;
|
||||
}
|
||||
clip_priv->next = clip_devs;
|
||||
clip_devs = dev;
|
||||
pr_debug("registered (net:%s)\n", dev->name);
|
||||
return number;
|
||||
}
|
||||
|
||||
static int clip_device_event(struct notifier_block *this, unsigned long event,
|
||||
void *arg)
|
||||
{
|
||||
struct net_device *dev = arg;
|
||||
|
||||
if (!net_eq(dev_net(dev), &init_net))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
if (event == NETDEV_UNREGISTER)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
/* ignore non-CLIP devices */
|
||||
if (dev->type != ARPHRD_ATM || dev->netdev_ops != &clip_netdev_ops)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
switch (event) {
|
||||
case NETDEV_UP:
|
||||
pr_debug("NETDEV_UP\n");
|
||||
to_atmarpd(act_up, PRIV(dev)->number, 0);
|
||||
break;
|
||||
case NETDEV_GOING_DOWN:
|
||||
pr_debug("NETDEV_DOWN\n");
|
||||
to_atmarpd(act_down, PRIV(dev)->number, 0);
|
||||
break;
|
||||
case NETDEV_CHANGE:
|
||||
case NETDEV_CHANGEMTU:
|
||||
pr_debug("NETDEV_CHANGE*\n");
|
||||
to_atmarpd(act_change, PRIV(dev)->number, 0);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int clip_inet_event(struct notifier_block *this, unsigned long event,
|
||||
void *ifa)
|
||||
{
|
||||
struct in_device *in_dev;
|
||||
|
||||
in_dev = ((struct in_ifaddr *)ifa)->ifa_dev;
|
||||
/*
|
||||
* Transitions are of the down-change-up type, so it's sufficient to
|
||||
* handle the change on up.
|
||||
*/
|
||||
if (event != NETDEV_UP)
|
||||
return NOTIFY_DONE;
|
||||
return clip_device_event(this, NETDEV_CHANGE, in_dev->dev);
|
||||
}
|
||||
|
||||
static struct notifier_block clip_dev_notifier = {
|
||||
.notifier_call = clip_device_event,
|
||||
};
|
||||
|
||||
|
||||
|
||||
static struct notifier_block clip_inet_notifier = {
|
||||
.notifier_call = clip_inet_event,
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void atmarpd_close(struct atm_vcc *vcc)
|
||||
{
|
||||
pr_debug("\n");
|
||||
|
||||
rtnl_lock();
|
||||
atmarpd = NULL;
|
||||
skb_queue_purge(&sk_atm(vcc)->sk_receive_queue);
|
||||
rtnl_unlock();
|
||||
|
||||
pr_debug("(done)\n");
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
static struct atmdev_ops atmarpd_dev_ops = {
|
||||
.close = atmarpd_close
|
||||
};
|
||||
|
||||
|
||||
static struct atm_dev atmarpd_dev = {
|
||||
.ops = &atmarpd_dev_ops,
|
||||
.type = "arpd",
|
||||
.number = 999,
|
||||
.lock = __SPIN_LOCK_UNLOCKED(atmarpd_dev.lock)
|
||||
};
|
||||
|
||||
|
||||
static int atm_init_atmarp(struct atm_vcc *vcc)
|
||||
{
|
||||
rtnl_lock();
|
||||
if (atmarpd) {
|
||||
rtnl_unlock();
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
|
||||
mod_timer(&idle_timer, jiffies + CLIP_CHECK_INTERVAL * HZ);
|
||||
|
||||
atmarpd = vcc;
|
||||
set_bit(ATM_VF_META, &vcc->flags);
|
||||
set_bit(ATM_VF_READY, &vcc->flags);
|
||||
/* allow replies and avoid getting closed if signaling dies */
|
||||
vcc->dev = &atmarpd_dev;
|
||||
vcc_insert_socket(sk_atm(vcc));
|
||||
vcc->push = NULL;
|
||||
vcc->pop = NULL; /* crash */
|
||||
vcc->push_oam = NULL; /* crash */
|
||||
rtnl_unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clip_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
int err = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SIOCMKCLIP:
|
||||
case ATMARPD_CTRL:
|
||||
case ATMARP_MKIP:
|
||||
case ATMARP_SETENTRY:
|
||||
case ATMARP_ENCAP:
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
break;
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case SIOCMKCLIP:
|
||||
err = clip_create(arg);
|
||||
break;
|
||||
case ATMARPD_CTRL:
|
||||
err = atm_init_atmarp(vcc);
|
||||
if (!err) {
|
||||
sock->state = SS_CONNECTED;
|
||||
__module_get(THIS_MODULE);
|
||||
}
|
||||
break;
|
||||
case ATMARP_MKIP:
|
||||
err = clip_mkip(vcc, arg);
|
||||
break;
|
||||
case ATMARP_SETENTRY:
|
||||
err = clip_setentry(vcc, (__force __be32)arg);
|
||||
break;
|
||||
case ATMARP_ENCAP:
|
||||
err = clip_encap(vcc, arg);
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct atm_ioctl clip_ioctl_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.ioctl = clip_ioctl,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
static void svc_addr(struct seq_file *seq, struct sockaddr_atmsvc *addr)
|
||||
{
|
||||
static int code[] = { 1, 2, 10, 6, 1, 0 };
|
||||
static int e164[] = { 1, 8, 4, 6, 1, 0 };
|
||||
|
||||
if (*addr->sas_addr.pub) {
|
||||
seq_printf(seq, "%s", addr->sas_addr.pub);
|
||||
if (*addr->sas_addr.prv)
|
||||
seq_putc(seq, '+');
|
||||
} else if (!*addr->sas_addr.prv) {
|
||||
seq_printf(seq, "%s", "(none)");
|
||||
return;
|
||||
}
|
||||
if (*addr->sas_addr.prv) {
|
||||
unsigned char *prv = addr->sas_addr.prv;
|
||||
int *fields;
|
||||
int i, j;
|
||||
|
||||
fields = *prv == ATM_AFI_E164 ? e164 : code;
|
||||
for (i = 0; fields[i]; i++) {
|
||||
for (j = fields[i]; j; j--)
|
||||
seq_printf(seq, "%02X", *prv++);
|
||||
if (fields[i + 1])
|
||||
seq_putc(seq, '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This means the neighbour entry has no attached VCC objects. */
|
||||
#define SEQ_NO_VCC_TOKEN ((void *) 2)
|
||||
|
||||
static void atmarp_info(struct seq_file *seq, struct neighbour *n,
|
||||
struct atmarp_entry *entry, struct clip_vcc *clip_vcc)
|
||||
{
|
||||
struct net_device *dev = n->dev;
|
||||
unsigned long exp;
|
||||
char buf[17];
|
||||
int svc, llc, off;
|
||||
|
||||
svc = ((clip_vcc == SEQ_NO_VCC_TOKEN) ||
|
||||
(sk_atm(clip_vcc->vcc)->sk_family == AF_ATMSVC));
|
||||
|
||||
llc = ((clip_vcc == SEQ_NO_VCC_TOKEN) || clip_vcc->encap);
|
||||
|
||||
if (clip_vcc == SEQ_NO_VCC_TOKEN)
|
||||
exp = entry->neigh->used;
|
||||
else
|
||||
exp = clip_vcc->last_use;
|
||||
|
||||
exp = (jiffies - exp) / HZ;
|
||||
|
||||
seq_printf(seq, "%-6s%-4s%-4s%5ld ",
|
||||
dev->name, svc ? "SVC" : "PVC", llc ? "LLC" : "NULL", exp);
|
||||
|
||||
off = scnprintf(buf, sizeof(buf) - 1, "%pI4", n->primary_key);
|
||||
while (off < 16)
|
||||
buf[off++] = ' ';
|
||||
buf[off] = '\0';
|
||||
seq_printf(seq, "%s", buf);
|
||||
|
||||
if (clip_vcc == SEQ_NO_VCC_TOKEN) {
|
||||
if (time_before(jiffies, entry->expires))
|
||||
seq_printf(seq, "(resolving)\n");
|
||||
else
|
||||
seq_printf(seq, "(expired, ref %d)\n",
|
||||
atomic_read(&entry->neigh->refcnt));
|
||||
} else if (!svc) {
|
||||
seq_printf(seq, "%d.%d.%d\n",
|
||||
clip_vcc->vcc->dev->number,
|
||||
clip_vcc->vcc->vpi, clip_vcc->vcc->vci);
|
||||
} else {
|
||||
svc_addr(seq, &clip_vcc->vcc->remote);
|
||||
seq_putc(seq, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
struct clip_seq_state {
|
||||
/* This member must be first. */
|
||||
struct neigh_seq_state ns;
|
||||
|
||||
/* Local to clip specific iteration. */
|
||||
struct clip_vcc *vcc;
|
||||
};
|
||||
|
||||
static struct clip_vcc *clip_seq_next_vcc(struct atmarp_entry *e,
|
||||
struct clip_vcc *curr)
|
||||
{
|
||||
if (!curr) {
|
||||
curr = e->vccs;
|
||||
if (!curr)
|
||||
return SEQ_NO_VCC_TOKEN;
|
||||
return curr;
|
||||
}
|
||||
if (curr == SEQ_NO_VCC_TOKEN)
|
||||
return NULL;
|
||||
|
||||
curr = curr->next;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
static void *clip_seq_vcc_walk(struct clip_seq_state *state,
|
||||
struct atmarp_entry *e, loff_t * pos)
|
||||
{
|
||||
struct clip_vcc *vcc = state->vcc;
|
||||
|
||||
vcc = clip_seq_next_vcc(e, vcc);
|
||||
if (vcc && pos != NULL) {
|
||||
while (*pos) {
|
||||
vcc = clip_seq_next_vcc(e, vcc);
|
||||
if (!vcc)
|
||||
break;
|
||||
--(*pos);
|
||||
}
|
||||
}
|
||||
state->vcc = vcc;
|
||||
|
||||
return vcc;
|
||||
}
|
||||
|
||||
static void *clip_seq_sub_iter(struct neigh_seq_state *_state,
|
||||
struct neighbour *n, loff_t * pos)
|
||||
{
|
||||
struct clip_seq_state *state = (struct clip_seq_state *)_state;
|
||||
|
||||
if (n->dev->type != ARPHRD_ATM)
|
||||
return NULL;
|
||||
|
||||
return clip_seq_vcc_walk(state, neighbour_priv(n), pos);
|
||||
}
|
||||
|
||||
static void *clip_seq_start(struct seq_file *seq, loff_t * pos)
|
||||
{
|
||||
struct clip_seq_state *state = seq->private;
|
||||
state->ns.neigh_sub_iter = clip_seq_sub_iter;
|
||||
return neigh_seq_start(seq, pos, &arp_tbl, NEIGH_SEQ_NEIGH_ONLY);
|
||||
}
|
||||
|
||||
static int clip_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
static char atm_arp_banner[] =
|
||||
"IPitf TypeEncp Idle IP address ATM address\n";
|
||||
|
||||
if (v == SEQ_START_TOKEN) {
|
||||
seq_puts(seq, atm_arp_banner);
|
||||
} else {
|
||||
struct clip_seq_state *state = seq->private;
|
||||
struct clip_vcc *vcc = state->vcc;
|
||||
struct neighbour *n = v;
|
||||
|
||||
atmarp_info(seq, n, neighbour_priv(n), vcc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations arp_seq_ops = {
|
||||
.start = clip_seq_start,
|
||||
.next = neigh_seq_next,
|
||||
.stop = neigh_seq_stop,
|
||||
.show = clip_seq_show,
|
||||
};
|
||||
|
||||
static int arp_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_open_net(inode, file, &arp_seq_ops,
|
||||
sizeof(struct clip_seq_state));
|
||||
}
|
||||
|
||||
static const struct file_operations arp_seq_fops = {
|
||||
.open = arp_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release_net,
|
||||
.owner = THIS_MODULE
|
||||
};
|
||||
#endif
|
||||
|
||||
static void atm_clip_exit_noproc(void);
|
||||
|
||||
static int __init atm_clip_init(void)
|
||||
{
|
||||
register_atm_ioctl(&clip_ioctl_ops);
|
||||
register_netdevice_notifier(&clip_dev_notifier);
|
||||
register_inetaddr_notifier(&clip_inet_notifier);
|
||||
|
||||
setup_timer(&idle_timer, idle_timer_check, 0);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
{
|
||||
struct proc_dir_entry *p;
|
||||
|
||||
p = proc_create("arp", S_IRUGO, atm_proc_root, &arp_seq_fops);
|
||||
if (!p) {
|
||||
pr_err("Unable to initialize /proc/net/atm/arp\n");
|
||||
atm_clip_exit_noproc();
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atm_clip_exit_noproc(void)
|
||||
{
|
||||
struct net_device *dev, *next;
|
||||
|
||||
unregister_inetaddr_notifier(&clip_inet_notifier);
|
||||
unregister_netdevice_notifier(&clip_dev_notifier);
|
||||
|
||||
deregister_atm_ioctl(&clip_ioctl_ops);
|
||||
|
||||
/* First, stop the idle timer, so it stops banging
|
||||
* on the table.
|
||||
*/
|
||||
del_timer_sync(&idle_timer);
|
||||
|
||||
dev = clip_devs;
|
||||
while (dev) {
|
||||
next = PRIV(dev)->next;
|
||||
unregister_netdev(dev);
|
||||
free_netdev(dev);
|
||||
dev = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void __exit atm_clip_exit(void)
|
||||
{
|
||||
remove_proc_entry("arp", atm_proc_root);
|
||||
|
||||
atm_clip_exit_noproc();
|
||||
}
|
||||
|
||||
module_init(atm_clip_init);
|
||||
module_exit(atm_clip_exit);
|
||||
MODULE_AUTHOR("Werner Almesberger");
|
||||
MODULE_DESCRIPTION("Classical/IP over ATM interface");
|
||||
MODULE_LICENSE("GPL");
|
910
net/atm/common.c
Normal file
910
net/atm/common.c
Normal file
|
@ -0,0 +1,910 @@
|
|||
/* net/atm/common.c - ATM sockets (common part for PVC and SVC) */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/net.h> /* struct socket, struct proto_ops */
|
||||
#include <linux/atm.h> /* ATM stuff */
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/socket.h> /* SOL_SOCKET */
|
||||
#include <linux/errno.h> /* error codes */
|
||||
#include <linux/capability.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/time.h> /* struct timeval */
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/sock.h> /* struct sock */
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
#include <linux/atomic.h>
|
||||
|
||||
#include "resources.h" /* atm_find_dev */
|
||||
#include "common.h" /* prototypes */
|
||||
#include "protocols.h" /* atm_init_<transport> */
|
||||
#include "addr.h" /* address registry */
|
||||
#include "signaling.h" /* for WAITING and sigd_attach */
|
||||
|
||||
struct hlist_head vcc_hash[VCC_HTABLE_SIZE];
|
||||
EXPORT_SYMBOL(vcc_hash);
|
||||
|
||||
DEFINE_RWLOCK(vcc_sklist_lock);
|
||||
EXPORT_SYMBOL(vcc_sklist_lock);
|
||||
|
||||
static ATOMIC_NOTIFIER_HEAD(atm_dev_notify_chain);
|
||||
|
||||
static void __vcc_insert_socket(struct sock *sk)
|
||||
{
|
||||
struct atm_vcc *vcc = atm_sk(sk);
|
||||
struct hlist_head *head = &vcc_hash[vcc->vci & (VCC_HTABLE_SIZE - 1)];
|
||||
sk->sk_hash = vcc->vci & (VCC_HTABLE_SIZE - 1);
|
||||
sk_add_node(sk, head);
|
||||
}
|
||||
|
||||
void vcc_insert_socket(struct sock *sk)
|
||||
{
|
||||
write_lock_irq(&vcc_sklist_lock);
|
||||
__vcc_insert_socket(sk);
|
||||
write_unlock_irq(&vcc_sklist_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(vcc_insert_socket);
|
||||
|
||||
static void vcc_remove_socket(struct sock *sk)
|
||||
{
|
||||
write_lock_irq(&vcc_sklist_lock);
|
||||
sk_del_node_init(sk);
|
||||
write_unlock_irq(&vcc_sklist_lock);
|
||||
}
|
||||
|
||||
static struct sk_buff *alloc_tx(struct atm_vcc *vcc, unsigned int size)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
|
||||
if (sk_wmem_alloc_get(sk) && !atm_may_send(vcc, size)) {
|
||||
pr_debug("Sorry: wmem_alloc = %d, size = %d, sndbuf = %d\n",
|
||||
sk_wmem_alloc_get(sk), size, sk->sk_sndbuf);
|
||||
return NULL;
|
||||
}
|
||||
while (!(skb = alloc_skb(size, GFP_KERNEL)))
|
||||
schedule();
|
||||
pr_debug("%d += %d\n", sk_wmem_alloc_get(sk), skb->truesize);
|
||||
atomic_add(skb->truesize, &sk->sk_wmem_alloc);
|
||||
return skb;
|
||||
}
|
||||
|
||||
static void vcc_sock_destruct(struct sock *sk)
|
||||
{
|
||||
if (atomic_read(&sk->sk_rmem_alloc))
|
||||
printk(KERN_DEBUG "%s: rmem leakage (%d bytes) detected.\n",
|
||||
__func__, atomic_read(&sk->sk_rmem_alloc));
|
||||
|
||||
if (atomic_read(&sk->sk_wmem_alloc))
|
||||
printk(KERN_DEBUG "%s: wmem leakage (%d bytes) detected.\n",
|
||||
__func__, atomic_read(&sk->sk_wmem_alloc));
|
||||
}
|
||||
|
||||
static void vcc_def_wakeup(struct sock *sk)
|
||||
{
|
||||
struct socket_wq *wq;
|
||||
|
||||
rcu_read_lock();
|
||||
wq = rcu_dereference(sk->sk_wq);
|
||||
if (wq_has_sleeper(wq))
|
||||
wake_up(&wq->wait);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static inline int vcc_writable(struct sock *sk)
|
||||
{
|
||||
struct atm_vcc *vcc = atm_sk(sk);
|
||||
|
||||
return (vcc->qos.txtp.max_sdu +
|
||||
atomic_read(&sk->sk_wmem_alloc)) <= sk->sk_sndbuf;
|
||||
}
|
||||
|
||||
static void vcc_write_space(struct sock *sk)
|
||||
{
|
||||
struct socket_wq *wq;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
if (vcc_writable(sk)) {
|
||||
wq = rcu_dereference(sk->sk_wq);
|
||||
if (wq_has_sleeper(wq))
|
||||
wake_up_interruptible(&wq->wait);
|
||||
|
||||
sk_wake_async(sk, SOCK_WAKE_SPACE, POLL_OUT);
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static void vcc_release_cb(struct sock *sk)
|
||||
{
|
||||
struct atm_vcc *vcc = atm_sk(sk);
|
||||
|
||||
if (vcc->release_cb)
|
||||
vcc->release_cb(vcc);
|
||||
}
|
||||
|
||||
static struct proto vcc_proto = {
|
||||
.name = "VCC",
|
||||
.owner = THIS_MODULE,
|
||||
.obj_size = sizeof(struct atm_vcc),
|
||||
.release_cb = vcc_release_cb,
|
||||
};
|
||||
|
||||
int vcc_create(struct net *net, struct socket *sock, int protocol, int family)
|
||||
{
|
||||
struct sock *sk;
|
||||
struct atm_vcc *vcc;
|
||||
|
||||
sock->sk = NULL;
|
||||
if (sock->type == SOCK_STREAM)
|
||||
return -EINVAL;
|
||||
sk = sk_alloc(net, family, GFP_KERNEL, &vcc_proto);
|
||||
if (!sk)
|
||||
return -ENOMEM;
|
||||
sock_init_data(sock, sk);
|
||||
sk->sk_state_change = vcc_def_wakeup;
|
||||
sk->sk_write_space = vcc_write_space;
|
||||
|
||||
vcc = atm_sk(sk);
|
||||
vcc->dev = NULL;
|
||||
memset(&vcc->local, 0, sizeof(struct sockaddr_atmsvc));
|
||||
memset(&vcc->remote, 0, sizeof(struct sockaddr_atmsvc));
|
||||
vcc->qos.txtp.max_sdu = 1 << 16; /* for meta VCs */
|
||||
atomic_set(&sk->sk_wmem_alloc, 1);
|
||||
atomic_set(&sk->sk_rmem_alloc, 0);
|
||||
vcc->push = NULL;
|
||||
vcc->pop = NULL;
|
||||
vcc->owner = NULL;
|
||||
vcc->push_oam = NULL;
|
||||
vcc->release_cb = NULL;
|
||||
vcc->vpi = vcc->vci = 0; /* no VCI/VPI yet */
|
||||
vcc->atm_options = vcc->aal_options = 0;
|
||||
sk->sk_destruct = vcc_sock_destruct;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vcc_destroy_socket(struct sock *sk)
|
||||
{
|
||||
struct atm_vcc *vcc = atm_sk(sk);
|
||||
struct sk_buff *skb;
|
||||
|
||||
set_bit(ATM_VF_CLOSE, &vcc->flags);
|
||||
clear_bit(ATM_VF_READY, &vcc->flags);
|
||||
if (vcc->dev) {
|
||||
if (vcc->dev->ops->close)
|
||||
vcc->dev->ops->close(vcc);
|
||||
if (vcc->push)
|
||||
vcc->push(vcc, NULL); /* atmarpd has no push */
|
||||
module_put(vcc->owner);
|
||||
|
||||
while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
|
||||
atm_return(vcc, skb->truesize);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
module_put(vcc->dev->ops->owner);
|
||||
atm_dev_put(vcc->dev);
|
||||
}
|
||||
|
||||
vcc_remove_socket(sk);
|
||||
}
|
||||
|
||||
int vcc_release(struct socket *sock)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
if (sk) {
|
||||
lock_sock(sk);
|
||||
vcc_destroy_socket(sock->sk);
|
||||
release_sock(sk);
|
||||
sock_put(sk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vcc_release_async(struct atm_vcc *vcc, int reply)
|
||||
{
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
|
||||
set_bit(ATM_VF_CLOSE, &vcc->flags);
|
||||
sk->sk_shutdown |= RCV_SHUTDOWN;
|
||||
sk->sk_err = -reply;
|
||||
clear_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
sk->sk_state_change(sk);
|
||||
}
|
||||
EXPORT_SYMBOL(vcc_release_async);
|
||||
|
||||
void vcc_process_recv_queue(struct atm_vcc *vcc)
|
||||
{
|
||||
struct sk_buff_head queue, *rq;
|
||||
struct sk_buff *skb, *tmp;
|
||||
unsigned long flags;
|
||||
|
||||
__skb_queue_head_init(&queue);
|
||||
rq = &sk_atm(vcc)->sk_receive_queue;
|
||||
|
||||
spin_lock_irqsave(&rq->lock, flags);
|
||||
skb_queue_splice_init(rq, &queue);
|
||||
spin_unlock_irqrestore(&rq->lock, flags);
|
||||
|
||||
skb_queue_walk_safe(&queue, skb, tmp) {
|
||||
__skb_unlink(skb, &queue);
|
||||
vcc->push(vcc, skb);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(vcc_process_recv_queue);
|
||||
|
||||
void atm_dev_signal_change(struct atm_dev *dev, char signal)
|
||||
{
|
||||
pr_debug("%s signal=%d dev=%p number=%d dev->signal=%d\n",
|
||||
__func__, signal, dev, dev->number, dev->signal);
|
||||
|
||||
/* atm driver sending invalid signal */
|
||||
WARN_ON(signal < ATM_PHY_SIG_LOST || signal > ATM_PHY_SIG_FOUND);
|
||||
|
||||
if (dev->signal == signal)
|
||||
return; /* no change */
|
||||
|
||||
dev->signal = signal;
|
||||
|
||||
atomic_notifier_call_chain(&atm_dev_notify_chain, signal, dev);
|
||||
}
|
||||
EXPORT_SYMBOL(atm_dev_signal_change);
|
||||
|
||||
void atm_dev_release_vccs(struct atm_dev *dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
write_lock_irq(&vcc_sklist_lock);
|
||||
for (i = 0; i < VCC_HTABLE_SIZE; i++) {
|
||||
struct hlist_head *head = &vcc_hash[i];
|
||||
struct hlist_node *tmp;
|
||||
struct sock *s;
|
||||
struct atm_vcc *vcc;
|
||||
|
||||
sk_for_each_safe(s, tmp, head) {
|
||||
vcc = atm_sk(s);
|
||||
if (vcc->dev == dev) {
|
||||
vcc_release_async(vcc, -EPIPE);
|
||||
sk_del_node_init(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
write_unlock_irq(&vcc_sklist_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(atm_dev_release_vccs);
|
||||
|
||||
static int adjust_tp(struct atm_trafprm *tp, unsigned char aal)
|
||||
{
|
||||
int max_sdu;
|
||||
|
||||
if (!tp->traffic_class)
|
||||
return 0;
|
||||
switch (aal) {
|
||||
case ATM_AAL0:
|
||||
max_sdu = ATM_CELL_SIZE-1;
|
||||
break;
|
||||
case ATM_AAL34:
|
||||
max_sdu = ATM_MAX_AAL34_PDU;
|
||||
break;
|
||||
default:
|
||||
pr_warning("AAL problems ... (%d)\n", aal);
|
||||
/* fall through */
|
||||
case ATM_AAL5:
|
||||
max_sdu = ATM_MAX_AAL5_PDU;
|
||||
}
|
||||
if (!tp->max_sdu)
|
||||
tp->max_sdu = max_sdu;
|
||||
else if (tp->max_sdu > max_sdu)
|
||||
return -EINVAL;
|
||||
if (!tp->max_cdv)
|
||||
tp->max_cdv = ATM_MAX_CDV;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_ci(const struct atm_vcc *vcc, short vpi, int vci)
|
||||
{
|
||||
struct hlist_head *head = &vcc_hash[vci & (VCC_HTABLE_SIZE - 1)];
|
||||
struct sock *s;
|
||||
struct atm_vcc *walk;
|
||||
|
||||
sk_for_each(s, head) {
|
||||
walk = atm_sk(s);
|
||||
if (walk->dev != vcc->dev)
|
||||
continue;
|
||||
if (test_bit(ATM_VF_ADDR, &walk->flags) && walk->vpi == vpi &&
|
||||
walk->vci == vci && ((walk->qos.txtp.traffic_class !=
|
||||
ATM_NONE && vcc->qos.txtp.traffic_class != ATM_NONE) ||
|
||||
(walk->qos.rxtp.traffic_class != ATM_NONE &&
|
||||
vcc->qos.rxtp.traffic_class != ATM_NONE)))
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
|
||||
/* allow VCCs with same VPI/VCI iff they don't collide on
|
||||
TX/RX (but we may refuse such sharing for other reasons,
|
||||
e.g. if protocol requires to have both channels) */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_ci(const struct atm_vcc *vcc, short *vpi, int *vci)
|
||||
{
|
||||
static short p; /* poor man's per-device cache */
|
||||
static int c;
|
||||
short old_p;
|
||||
int old_c;
|
||||
int err;
|
||||
|
||||
if (*vpi != ATM_VPI_ANY && *vci != ATM_VCI_ANY) {
|
||||
err = check_ci(vcc, *vpi, *vci);
|
||||
return err;
|
||||
}
|
||||
/* last scan may have left values out of bounds for current device */
|
||||
if (*vpi != ATM_VPI_ANY)
|
||||
p = *vpi;
|
||||
else if (p >= 1 << vcc->dev->ci_range.vpi_bits)
|
||||
p = 0;
|
||||
if (*vci != ATM_VCI_ANY)
|
||||
c = *vci;
|
||||
else if (c < ATM_NOT_RSV_VCI || c >= 1 << vcc->dev->ci_range.vci_bits)
|
||||
c = ATM_NOT_RSV_VCI;
|
||||
old_p = p;
|
||||
old_c = c;
|
||||
do {
|
||||
if (!check_ci(vcc, p, c)) {
|
||||
*vpi = p;
|
||||
*vci = c;
|
||||
return 0;
|
||||
}
|
||||
if (*vci == ATM_VCI_ANY) {
|
||||
c++;
|
||||
if (c >= 1 << vcc->dev->ci_range.vci_bits)
|
||||
c = ATM_NOT_RSV_VCI;
|
||||
}
|
||||
if ((c == ATM_NOT_RSV_VCI || *vci != ATM_VCI_ANY) &&
|
||||
*vpi == ATM_VPI_ANY) {
|
||||
p++;
|
||||
if (p >= 1 << vcc->dev->ci_range.vpi_bits)
|
||||
p = 0;
|
||||
}
|
||||
} while (old_p != p || old_c != c);
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
|
||||
static int __vcc_connect(struct atm_vcc *vcc, struct atm_dev *dev, short vpi,
|
||||
int vci)
|
||||
{
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
int error;
|
||||
|
||||
if ((vpi != ATM_VPI_UNSPEC && vpi != ATM_VPI_ANY &&
|
||||
vpi >> dev->ci_range.vpi_bits) || (vci != ATM_VCI_UNSPEC &&
|
||||
vci != ATM_VCI_ANY && vci >> dev->ci_range.vci_bits))
|
||||
return -EINVAL;
|
||||
if (vci > 0 && vci < ATM_NOT_RSV_VCI && !capable(CAP_NET_BIND_SERVICE))
|
||||
return -EPERM;
|
||||
error = -ENODEV;
|
||||
if (!try_module_get(dev->ops->owner))
|
||||
return error;
|
||||
vcc->dev = dev;
|
||||
write_lock_irq(&vcc_sklist_lock);
|
||||
if (test_bit(ATM_DF_REMOVED, &dev->flags) ||
|
||||
(error = find_ci(vcc, &vpi, &vci))) {
|
||||
write_unlock_irq(&vcc_sklist_lock);
|
||||
goto fail_module_put;
|
||||
}
|
||||
vcc->vpi = vpi;
|
||||
vcc->vci = vci;
|
||||
__vcc_insert_socket(sk);
|
||||
write_unlock_irq(&vcc_sklist_lock);
|
||||
switch (vcc->qos.aal) {
|
||||
case ATM_AAL0:
|
||||
error = atm_init_aal0(vcc);
|
||||
vcc->stats = &dev->stats.aal0;
|
||||
break;
|
||||
case ATM_AAL34:
|
||||
error = atm_init_aal34(vcc);
|
||||
vcc->stats = &dev->stats.aal34;
|
||||
break;
|
||||
case ATM_NO_AAL:
|
||||
/* ATM_AAL5 is also used in the "0 for default" case */
|
||||
vcc->qos.aal = ATM_AAL5;
|
||||
/* fall through */
|
||||
case ATM_AAL5:
|
||||
error = atm_init_aal5(vcc);
|
||||
vcc->stats = &dev->stats.aal5;
|
||||
break;
|
||||
default:
|
||||
error = -EPROTOTYPE;
|
||||
}
|
||||
if (!error)
|
||||
error = adjust_tp(&vcc->qos.txtp, vcc->qos.aal);
|
||||
if (!error)
|
||||
error = adjust_tp(&vcc->qos.rxtp, vcc->qos.aal);
|
||||
if (error)
|
||||
goto fail;
|
||||
pr_debug("VCC %d.%d, AAL %d\n", vpi, vci, vcc->qos.aal);
|
||||
pr_debug(" TX: %d, PCR %d..%d, SDU %d\n",
|
||||
vcc->qos.txtp.traffic_class,
|
||||
vcc->qos.txtp.min_pcr,
|
||||
vcc->qos.txtp.max_pcr,
|
||||
vcc->qos.txtp.max_sdu);
|
||||
pr_debug(" RX: %d, PCR %d..%d, SDU %d\n",
|
||||
vcc->qos.rxtp.traffic_class,
|
||||
vcc->qos.rxtp.min_pcr,
|
||||
vcc->qos.rxtp.max_pcr,
|
||||
vcc->qos.rxtp.max_sdu);
|
||||
|
||||
if (dev->ops->open) {
|
||||
error = dev->ops->open(vcc);
|
||||
if (error)
|
||||
goto fail;
|
||||
}
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
vcc_remove_socket(sk);
|
||||
fail_module_put:
|
||||
module_put(dev->ops->owner);
|
||||
/* ensure we get dev module ref count correct */
|
||||
vcc->dev = NULL;
|
||||
return error;
|
||||
}
|
||||
|
||||
int vcc_connect(struct socket *sock, int itf, short vpi, int vci)
|
||||
{
|
||||
struct atm_dev *dev;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
int error;
|
||||
|
||||
pr_debug("(vpi %d, vci %d)\n", vpi, vci);
|
||||
if (sock->state == SS_CONNECTED)
|
||||
return -EISCONN;
|
||||
if (sock->state != SS_UNCONNECTED)
|
||||
return -EINVAL;
|
||||
if (!(vpi || vci))
|
||||
return -EINVAL;
|
||||
|
||||
if (vpi != ATM_VPI_UNSPEC && vci != ATM_VCI_UNSPEC)
|
||||
clear_bit(ATM_VF_PARTIAL, &vcc->flags);
|
||||
else
|
||||
if (test_bit(ATM_VF_PARTIAL, &vcc->flags))
|
||||
return -EINVAL;
|
||||
pr_debug("(TX: cl %d,bw %d-%d,sdu %d; "
|
||||
"RX: cl %d,bw %d-%d,sdu %d,AAL %s%d)\n",
|
||||
vcc->qos.txtp.traffic_class, vcc->qos.txtp.min_pcr,
|
||||
vcc->qos.txtp.max_pcr, vcc->qos.txtp.max_sdu,
|
||||
vcc->qos.rxtp.traffic_class, vcc->qos.rxtp.min_pcr,
|
||||
vcc->qos.rxtp.max_pcr, vcc->qos.rxtp.max_sdu,
|
||||
vcc->qos.aal == ATM_AAL5 ? "" :
|
||||
vcc->qos.aal == ATM_AAL0 ? "" : " ??? code ",
|
||||
vcc->qos.aal == ATM_AAL0 ? 0 : vcc->qos.aal);
|
||||
if (!test_bit(ATM_VF_HASQOS, &vcc->flags))
|
||||
return -EBADFD;
|
||||
if (vcc->qos.txtp.traffic_class == ATM_ANYCLASS ||
|
||||
vcc->qos.rxtp.traffic_class == ATM_ANYCLASS)
|
||||
return -EINVAL;
|
||||
if (likely(itf != ATM_ITF_ANY)) {
|
||||
dev = try_then_request_module(atm_dev_lookup(itf),
|
||||
"atm-device-%d", itf);
|
||||
} else {
|
||||
dev = NULL;
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
if (!list_empty(&atm_devs)) {
|
||||
dev = list_entry(atm_devs.next,
|
||||
struct atm_dev, dev_list);
|
||||
atm_dev_hold(dev);
|
||||
}
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
}
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
error = __vcc_connect(vcc, dev, vpi, vci);
|
||||
if (error) {
|
||||
atm_dev_put(dev);
|
||||
return error;
|
||||
}
|
||||
if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC)
|
||||
set_bit(ATM_VF_PARTIAL, &vcc->flags);
|
||||
if (test_bit(ATM_VF_READY, &ATM_SD(sock)->flags))
|
||||
sock->state = SS_CONNECTED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vcc_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
|
||||
size_t size, int flags)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc;
|
||||
struct sk_buff *skb;
|
||||
int copied, error = -EINVAL;
|
||||
|
||||
if (sock->state != SS_CONNECTED)
|
||||
return -ENOTCONN;
|
||||
|
||||
/* only handle MSG_DONTWAIT and MSG_PEEK */
|
||||
if (flags & ~(MSG_DONTWAIT | MSG_PEEK))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
vcc = ATM_SD(sock);
|
||||
if (test_bit(ATM_VF_RELEASED, &vcc->flags) ||
|
||||
test_bit(ATM_VF_CLOSE, &vcc->flags) ||
|
||||
!test_bit(ATM_VF_READY, &vcc->flags))
|
||||
return 0;
|
||||
|
||||
skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &error);
|
||||
if (!skb)
|
||||
return error;
|
||||
|
||||
copied = skb->len;
|
||||
if (copied > size) {
|
||||
copied = size;
|
||||
msg->msg_flags |= MSG_TRUNC;
|
||||
}
|
||||
|
||||
error = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
||||
if (error)
|
||||
return error;
|
||||
sock_recv_ts_and_drops(msg, sk, skb);
|
||||
|
||||
if (!(flags & MSG_PEEK)) {
|
||||
pr_debug("%d -= %d\n", atomic_read(&sk->sk_rmem_alloc),
|
||||
skb->truesize);
|
||||
atm_return(vcc, skb->truesize);
|
||||
}
|
||||
|
||||
skb_free_datagram(sk, skb);
|
||||
return copied;
|
||||
}
|
||||
|
||||
int vcc_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m,
|
||||
size_t total_len)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
DEFINE_WAIT(wait);
|
||||
struct atm_vcc *vcc;
|
||||
struct sk_buff *skb;
|
||||
int eff, error;
|
||||
const void __user *buff;
|
||||
int size;
|
||||
|
||||
lock_sock(sk);
|
||||
if (sock->state != SS_CONNECTED) {
|
||||
error = -ENOTCONN;
|
||||
goto out;
|
||||
}
|
||||
if (m->msg_name) {
|
||||
error = -EISCONN;
|
||||
goto out;
|
||||
}
|
||||
if (m->msg_iovlen != 1) {
|
||||
error = -ENOSYS; /* fix this later @@@ */
|
||||
goto out;
|
||||
}
|
||||
buff = m->msg_iov->iov_base;
|
||||
size = m->msg_iov->iov_len;
|
||||
vcc = ATM_SD(sock);
|
||||
if (test_bit(ATM_VF_RELEASED, &vcc->flags) ||
|
||||
test_bit(ATM_VF_CLOSE, &vcc->flags) ||
|
||||
!test_bit(ATM_VF_READY, &vcc->flags)) {
|
||||
error = -EPIPE;
|
||||
send_sig(SIGPIPE, current, 0);
|
||||
goto out;
|
||||
}
|
||||
if (!size) {
|
||||
error = 0;
|
||||
goto out;
|
||||
}
|
||||
if (size < 0 || size > vcc->qos.txtp.max_sdu) {
|
||||
error = -EMSGSIZE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
eff = (size+3) & ~3; /* align to word boundary */
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
error = 0;
|
||||
while (!(skb = alloc_tx(vcc, eff))) {
|
||||
if (m->msg_flags & MSG_DONTWAIT) {
|
||||
error = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
schedule();
|
||||
if (signal_pending(current)) {
|
||||
error = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
if (test_bit(ATM_VF_RELEASED, &vcc->flags) ||
|
||||
test_bit(ATM_VF_CLOSE, &vcc->flags) ||
|
||||
!test_bit(ATM_VF_READY, &vcc->flags)) {
|
||||
error = -EPIPE;
|
||||
send_sig(SIGPIPE, current, 0);
|
||||
break;
|
||||
}
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
if (error)
|
||||
goto out;
|
||||
skb->dev = NULL; /* for paths shared with net_device interfaces */
|
||||
ATM_SKB(skb)->atm_options = vcc->atm_options;
|
||||
if (copy_from_user(skb_put(skb, size), buff, size)) {
|
||||
kfree_skb(skb);
|
||||
error = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
if (eff != size)
|
||||
memset(skb->data + size, 0, eff-size);
|
||||
error = vcc->dev->ops->send(vcc, skb);
|
||||
error = error ? error : size;
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
unsigned int vcc_poll(struct file *file, struct socket *sock, poll_table *wait)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc;
|
||||
unsigned int mask;
|
||||
|
||||
sock_poll_wait(file, sk_sleep(sk), wait);
|
||||
mask = 0;
|
||||
|
||||
vcc = ATM_SD(sock);
|
||||
|
||||
/* exceptional events */
|
||||
if (sk->sk_err)
|
||||
mask = POLLERR;
|
||||
|
||||
if (test_bit(ATM_VF_RELEASED, &vcc->flags) ||
|
||||
test_bit(ATM_VF_CLOSE, &vcc->flags))
|
||||
mask |= POLLHUP;
|
||||
|
||||
/* readable? */
|
||||
if (!skb_queue_empty(&sk->sk_receive_queue))
|
||||
mask |= POLLIN | POLLRDNORM;
|
||||
|
||||
/* writable? */
|
||||
if (sock->state == SS_CONNECTING &&
|
||||
test_bit(ATM_VF_WAITING, &vcc->flags))
|
||||
return mask;
|
||||
|
||||
if (vcc->qos.txtp.traffic_class != ATM_NONE &&
|
||||
vcc_writable(sk))
|
||||
mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static int atm_change_qos(struct atm_vcc *vcc, struct atm_qos *qos)
|
||||
{
|
||||
int error;
|
||||
|
||||
/*
|
||||
* Don't let the QoS change the already connected AAL type nor the
|
||||
* traffic class.
|
||||
*/
|
||||
if (qos->aal != vcc->qos.aal ||
|
||||
qos->rxtp.traffic_class != vcc->qos.rxtp.traffic_class ||
|
||||
qos->txtp.traffic_class != vcc->qos.txtp.traffic_class)
|
||||
return -EINVAL;
|
||||
error = adjust_tp(&qos->txtp, qos->aal);
|
||||
if (!error)
|
||||
error = adjust_tp(&qos->rxtp, qos->aal);
|
||||
if (error)
|
||||
return error;
|
||||
if (!vcc->dev->ops->change_qos)
|
||||
return -EOPNOTSUPP;
|
||||
if (sk_atm(vcc)->sk_family == AF_ATMPVC)
|
||||
return vcc->dev->ops->change_qos(vcc, qos, ATM_MF_SET);
|
||||
return svc_change_qos(vcc, qos);
|
||||
}
|
||||
|
||||
static int check_tp(const struct atm_trafprm *tp)
|
||||
{
|
||||
/* @@@ Should be merged with adjust_tp */
|
||||
if (!tp->traffic_class || tp->traffic_class == ATM_ANYCLASS)
|
||||
return 0;
|
||||
if (tp->traffic_class != ATM_UBR && !tp->min_pcr && !tp->pcr &&
|
||||
!tp->max_pcr)
|
||||
return -EINVAL;
|
||||
if (tp->min_pcr == ATM_MAX_PCR)
|
||||
return -EINVAL;
|
||||
if (tp->min_pcr && tp->max_pcr && tp->max_pcr != ATM_MAX_PCR &&
|
||||
tp->min_pcr > tp->max_pcr)
|
||||
return -EINVAL;
|
||||
/*
|
||||
* We allow pcr to be outside [min_pcr,max_pcr], because later
|
||||
* adjustment may still push it in the valid range.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_qos(const struct atm_qos *qos)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!qos->txtp.traffic_class && !qos->rxtp.traffic_class)
|
||||
return -EINVAL;
|
||||
if (qos->txtp.traffic_class != qos->rxtp.traffic_class &&
|
||||
qos->txtp.traffic_class && qos->rxtp.traffic_class &&
|
||||
qos->txtp.traffic_class != ATM_ANYCLASS &&
|
||||
qos->rxtp.traffic_class != ATM_ANYCLASS)
|
||||
return -EINVAL;
|
||||
error = check_tp(&qos->txtp);
|
||||
if (error)
|
||||
return error;
|
||||
return check_tp(&qos->rxtp);
|
||||
}
|
||||
|
||||
int vcc_setsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, unsigned int optlen)
|
||||
{
|
||||
struct atm_vcc *vcc;
|
||||
unsigned long value;
|
||||
int error;
|
||||
|
||||
if (__SO_LEVEL_MATCH(optname, level) && optlen != __SO_SIZE(optname))
|
||||
return -EINVAL;
|
||||
|
||||
vcc = ATM_SD(sock);
|
||||
switch (optname) {
|
||||
case SO_ATMQOS:
|
||||
{
|
||||
struct atm_qos qos;
|
||||
|
||||
if (copy_from_user(&qos, optval, sizeof(qos)))
|
||||
return -EFAULT;
|
||||
error = check_qos(&qos);
|
||||
if (error)
|
||||
return error;
|
||||
if (sock->state == SS_CONNECTED)
|
||||
return atm_change_qos(vcc, &qos);
|
||||
if (sock->state != SS_UNCONNECTED)
|
||||
return -EBADFD;
|
||||
vcc->qos = qos;
|
||||
set_bit(ATM_VF_HASQOS, &vcc->flags);
|
||||
return 0;
|
||||
}
|
||||
case SO_SETCLP:
|
||||
if (get_user(value, (unsigned long __user *)optval))
|
||||
return -EFAULT;
|
||||
if (value)
|
||||
vcc->atm_options |= ATM_ATMOPT_CLP;
|
||||
else
|
||||
vcc->atm_options &= ~ATM_ATMOPT_CLP;
|
||||
return 0;
|
||||
default:
|
||||
if (level == SOL_SOCKET)
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (!vcc->dev || !vcc->dev->ops->setsockopt)
|
||||
return -EINVAL;
|
||||
return vcc->dev->ops->setsockopt(vcc, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
int vcc_getsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
struct atm_vcc *vcc;
|
||||
int len;
|
||||
|
||||
if (get_user(len, optlen))
|
||||
return -EFAULT;
|
||||
if (__SO_LEVEL_MATCH(optname, level) && len != __SO_SIZE(optname))
|
||||
return -EINVAL;
|
||||
|
||||
vcc = ATM_SD(sock);
|
||||
switch (optname) {
|
||||
case SO_ATMQOS:
|
||||
if (!test_bit(ATM_VF_HASQOS, &vcc->flags))
|
||||
return -EINVAL;
|
||||
return copy_to_user(optval, &vcc->qos, sizeof(vcc->qos))
|
||||
? -EFAULT : 0;
|
||||
case SO_SETCLP:
|
||||
return put_user(vcc->atm_options & ATM_ATMOPT_CLP ? 1 : 0,
|
||||
(unsigned long __user *)optval) ? -EFAULT : 0;
|
||||
case SO_ATMPVC:
|
||||
{
|
||||
struct sockaddr_atmpvc pvc;
|
||||
|
||||
if (!vcc->dev || !test_bit(ATM_VF_ADDR, &vcc->flags))
|
||||
return -ENOTCONN;
|
||||
memset(&pvc, 0, sizeof(pvc));
|
||||
pvc.sap_family = AF_ATMPVC;
|
||||
pvc.sap_addr.itf = vcc->dev->number;
|
||||
pvc.sap_addr.vpi = vcc->vpi;
|
||||
pvc.sap_addr.vci = vcc->vci;
|
||||
return copy_to_user(optval, &pvc, sizeof(pvc)) ? -EFAULT : 0;
|
||||
}
|
||||
default:
|
||||
if (level == SOL_SOCKET)
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (!vcc->dev || !vcc->dev->ops->getsockopt)
|
||||
return -EINVAL;
|
||||
return vcc->dev->ops->getsockopt(vcc, level, optname, optval, len);
|
||||
}
|
||||
|
||||
int register_atmdevice_notifier(struct notifier_block *nb)
|
||||
{
|
||||
return atomic_notifier_chain_register(&atm_dev_notify_chain, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_atmdevice_notifier);
|
||||
|
||||
void unregister_atmdevice_notifier(struct notifier_block *nb)
|
||||
{
|
||||
atomic_notifier_chain_unregister(&atm_dev_notify_chain, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unregister_atmdevice_notifier);
|
||||
|
||||
static int __init atm_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = proto_register(&vcc_proto, 0);
|
||||
if (error < 0)
|
||||
goto out;
|
||||
error = atmpvc_init();
|
||||
if (error < 0) {
|
||||
pr_err("atmpvc_init() failed with %d\n", error);
|
||||
goto out_unregister_vcc_proto;
|
||||
}
|
||||
error = atmsvc_init();
|
||||
if (error < 0) {
|
||||
pr_err("atmsvc_init() failed with %d\n", error);
|
||||
goto out_atmpvc_exit;
|
||||
}
|
||||
error = atm_proc_init();
|
||||
if (error < 0) {
|
||||
pr_err("atm_proc_init() failed with %d\n", error);
|
||||
goto out_atmsvc_exit;
|
||||
}
|
||||
error = atm_sysfs_init();
|
||||
if (error < 0) {
|
||||
pr_err("atm_sysfs_init() failed with %d\n", error);
|
||||
goto out_atmproc_exit;
|
||||
}
|
||||
out:
|
||||
return error;
|
||||
out_atmproc_exit:
|
||||
atm_proc_exit();
|
||||
out_atmsvc_exit:
|
||||
atmsvc_exit();
|
||||
out_atmpvc_exit:
|
||||
atmsvc_exit();
|
||||
out_unregister_vcc_proto:
|
||||
proto_unregister(&vcc_proto);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static void __exit atm_exit(void)
|
||||
{
|
||||
atm_proc_exit();
|
||||
atm_sysfs_exit();
|
||||
atmsvc_exit();
|
||||
atmpvc_exit();
|
||||
proto_unregister(&vcc_proto);
|
||||
}
|
||||
|
||||
subsys_initcall(atm_init);
|
||||
|
||||
module_exit(atm_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_NETPROTO(PF_ATMPVC);
|
||||
MODULE_ALIAS_NETPROTO(PF_ATMSVC);
|
56
net/atm/common.h
Normal file
56
net/atm/common.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/* net/atm/common.h - ATM sockets (common part for PVC and SVC) */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#ifndef NET_ATM_COMMON_H
|
||||
#define NET_ATM_COMMON_H
|
||||
|
||||
#include <linux/net.h>
|
||||
#include <linux/poll.h> /* for poll_table */
|
||||
|
||||
|
||||
int vcc_create(struct net *net, struct socket *sock, int protocol, int family);
|
||||
int vcc_release(struct socket *sock);
|
||||
int vcc_connect(struct socket *sock, int itf, short vpi, int vci);
|
||||
int vcc_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
|
||||
size_t size, int flags);
|
||||
int vcc_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m,
|
||||
size_t total_len);
|
||||
unsigned int vcc_poll(struct file *file, struct socket *sock, poll_table *wait);
|
||||
int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
|
||||
int vcc_compat_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
|
||||
int vcc_setsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, unsigned int optlen);
|
||||
int vcc_getsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen);
|
||||
void vcc_process_recv_queue(struct atm_vcc *vcc);
|
||||
|
||||
int atmpvc_init(void);
|
||||
void atmpvc_exit(void);
|
||||
int atmsvc_init(void);
|
||||
void atmsvc_exit(void);
|
||||
int atm_sysfs_init(void);
|
||||
void atm_sysfs_exit(void);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
int atm_proc_init(void);
|
||||
void atm_proc_exit(void);
|
||||
#else
|
||||
static inline int atm_proc_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void atm_proc_exit(void)
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
/* SVC */
|
||||
int svc_change_qos(struct atm_vcc *vcc,struct atm_qos *qos);
|
||||
|
||||
void atm_dev_release_vccs(struct atm_dev *dev);
|
||||
|
||||
#endif
|
369
net/atm/ioctl.c
Normal file
369
net/atm/ioctl.c
Normal file
|
@ -0,0 +1,369 @@
|
|||
/* ATM ioctl handling */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
/* 2003 John Levon <levon@movementarian.org> */
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/net.h> /* struct socket, struct proto_ops */
|
||||
#include <linux/atm.h> /* ATM stuff */
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/atmclip.h> /* CLIP_*ENCAP */
|
||||
#include <linux/atmarp.h> /* manifest constants */
|
||||
#include <linux/capability.h>
|
||||
#include <linux/sonet.h> /* for ioctls */
|
||||
#include <linux/atmsvc.h>
|
||||
#include <linux/atmmpc.h>
|
||||
#include <net/atmclip.h>
|
||||
#include <linux/atmlec.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include <net/compat.h>
|
||||
|
||||
#include "resources.h"
|
||||
#include "signaling.h" /* for WAITING and sigd_attach */
|
||||
#include "common.h"
|
||||
|
||||
|
||||
static DEFINE_MUTEX(ioctl_mutex);
|
||||
static LIST_HEAD(ioctl_list);
|
||||
|
||||
|
||||
void register_atm_ioctl(struct atm_ioctl *ioctl)
|
||||
{
|
||||
mutex_lock(&ioctl_mutex);
|
||||
list_add_tail(&ioctl->list, &ioctl_list);
|
||||
mutex_unlock(&ioctl_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(register_atm_ioctl);
|
||||
|
||||
void deregister_atm_ioctl(struct atm_ioctl *ioctl)
|
||||
{
|
||||
mutex_lock(&ioctl_mutex);
|
||||
list_del(&ioctl->list);
|
||||
mutex_unlock(&ioctl_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(deregister_atm_ioctl);
|
||||
|
||||
static int do_vcc_ioctl(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg, int compat)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc;
|
||||
int error;
|
||||
struct list_head *pos;
|
||||
void __user *argp = (void __user *)arg;
|
||||
|
||||
vcc = ATM_SD(sock);
|
||||
switch (cmd) {
|
||||
case SIOCOUTQ:
|
||||
if (sock->state != SS_CONNECTED ||
|
||||
!test_bit(ATM_VF_READY, &vcc->flags)) {
|
||||
error = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
error = put_user(sk->sk_sndbuf - sk_wmem_alloc_get(sk),
|
||||
(int __user *)argp) ? -EFAULT : 0;
|
||||
goto done;
|
||||
case SIOCINQ:
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
|
||||
if (sock->state != SS_CONNECTED) {
|
||||
error = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
skb = skb_peek(&sk->sk_receive_queue);
|
||||
error = put_user(skb ? skb->len : 0,
|
||||
(int __user *)argp) ? -EFAULT : 0;
|
||||
goto done;
|
||||
}
|
||||
case SIOCGSTAMP: /* borrowed from IP */
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (compat)
|
||||
error = compat_sock_get_timestamp(sk, argp);
|
||||
else
|
||||
#endif
|
||||
error = sock_get_timestamp(sk, argp);
|
||||
goto done;
|
||||
case SIOCGSTAMPNS: /* borrowed from IP */
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (compat)
|
||||
error = compat_sock_get_timestampns(sk, argp);
|
||||
else
|
||||
#endif
|
||||
error = sock_get_timestampns(sk, argp);
|
||||
goto done;
|
||||
case ATM_SETSC:
|
||||
net_warn_ratelimited("ATM_SETSC is obsolete; used by %s:%d\n",
|
||||
current->comm, task_pid_nr(current));
|
||||
error = 0;
|
||||
goto done;
|
||||
case ATMSIGD_CTRL:
|
||||
if (!capable(CAP_NET_ADMIN)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
/*
|
||||
* The user/kernel protocol for exchanging signalling
|
||||
* info uses kernel pointers as opaque references,
|
||||
* so the holder of the file descriptor can scribble
|
||||
* on the kernel... so we should make sure that we
|
||||
* have the same privileges that /proc/kcore needs
|
||||
*/
|
||||
if (!capable(CAP_SYS_RAWIO)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
#ifdef CONFIG_COMPAT
|
||||
/* WTF? I don't even want to _think_ about making this
|
||||
work for 32-bit userspace. TBH I don't really want
|
||||
to think about it at all. dwmw2. */
|
||||
if (compat) {
|
||||
net_warn_ratelimited("32-bit task cannot be atmsigd\n");
|
||||
error = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
error = sigd_attach(vcc);
|
||||
if (!error)
|
||||
sock->state = SS_CONNECTED;
|
||||
goto done;
|
||||
case ATM_SETBACKEND:
|
||||
case ATM_NEWBACKENDIF:
|
||||
{
|
||||
atm_backend_t backend;
|
||||
error = get_user(backend, (atm_backend_t __user *)argp);
|
||||
if (error)
|
||||
goto done;
|
||||
switch (backend) {
|
||||
case ATM_BACKEND_PPP:
|
||||
request_module("pppoatm");
|
||||
break;
|
||||
case ATM_BACKEND_BR2684:
|
||||
request_module("br2684");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ATMMPC_CTRL:
|
||||
case ATMMPC_DATA:
|
||||
request_module("mpoa");
|
||||
break;
|
||||
case ATMARPD_CTRL:
|
||||
request_module("clip");
|
||||
break;
|
||||
case ATMLEC_CTRL:
|
||||
request_module("lec");
|
||||
break;
|
||||
}
|
||||
|
||||
error = -ENOIOCTLCMD;
|
||||
|
||||
mutex_lock(&ioctl_mutex);
|
||||
list_for_each(pos, &ioctl_list) {
|
||||
struct atm_ioctl *ic = list_entry(pos, struct atm_ioctl, list);
|
||||
if (try_module_get(ic->owner)) {
|
||||
error = ic->ioctl(sock, cmd, arg);
|
||||
module_put(ic->owner);
|
||||
if (error != -ENOIOCTLCMD)
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ioctl_mutex);
|
||||
|
||||
if (error != -ENOIOCTLCMD)
|
||||
goto done;
|
||||
|
||||
error = atm_dev_ioctl(cmd, argp, compat);
|
||||
|
||||
done:
|
||||
return error;
|
||||
}
|
||||
|
||||
int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return do_vcc_ioctl(sock, cmd, arg, 0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
/*
|
||||
* FIXME:
|
||||
* The compat_ioctl handling is duplicated, using both these conversion
|
||||
* routines and the compat argument to the actual handlers. Both
|
||||
* versions are somewhat incomplete and should be merged, e.g. by
|
||||
* moving the ioctl number translation into the actual handlers and
|
||||
* killing the conversion code.
|
||||
*
|
||||
* -arnd, November 2009
|
||||
*/
|
||||
#define ATM_GETLINKRATE32 _IOW('a', ATMIOC_ITF+1, struct compat_atmif_sioc)
|
||||
#define ATM_GETNAMES32 _IOW('a', ATMIOC_ITF+3, struct compat_atm_iobuf)
|
||||
#define ATM_GETTYPE32 _IOW('a', ATMIOC_ITF+4, struct compat_atmif_sioc)
|
||||
#define ATM_GETESI32 _IOW('a', ATMIOC_ITF+5, struct compat_atmif_sioc)
|
||||
#define ATM_GETADDR32 _IOW('a', ATMIOC_ITF+6, struct compat_atmif_sioc)
|
||||
#define ATM_RSTADDR32 _IOW('a', ATMIOC_ITF+7, struct compat_atmif_sioc)
|
||||
#define ATM_ADDADDR32 _IOW('a', ATMIOC_ITF+8, struct compat_atmif_sioc)
|
||||
#define ATM_DELADDR32 _IOW('a', ATMIOC_ITF+9, struct compat_atmif_sioc)
|
||||
#define ATM_GETCIRANGE32 _IOW('a', ATMIOC_ITF+10, struct compat_atmif_sioc)
|
||||
#define ATM_SETCIRANGE32 _IOW('a', ATMIOC_ITF+11, struct compat_atmif_sioc)
|
||||
#define ATM_SETESI32 _IOW('a', ATMIOC_ITF+12, struct compat_atmif_sioc)
|
||||
#define ATM_SETESIF32 _IOW('a', ATMIOC_ITF+13, struct compat_atmif_sioc)
|
||||
#define ATM_GETSTAT32 _IOW('a', ATMIOC_SARCOM+0, struct compat_atmif_sioc)
|
||||
#define ATM_GETSTATZ32 _IOW('a', ATMIOC_SARCOM+1, struct compat_atmif_sioc)
|
||||
#define ATM_GETLOOP32 _IOW('a', ATMIOC_SARCOM+2, struct compat_atmif_sioc)
|
||||
#define ATM_SETLOOP32 _IOW('a', ATMIOC_SARCOM+3, struct compat_atmif_sioc)
|
||||
#define ATM_QUERYLOOP32 _IOW('a', ATMIOC_SARCOM+4, struct compat_atmif_sioc)
|
||||
|
||||
static struct {
|
||||
unsigned int cmd32;
|
||||
unsigned int cmd;
|
||||
} atm_ioctl_map[] = {
|
||||
{ ATM_GETLINKRATE32, ATM_GETLINKRATE },
|
||||
{ ATM_GETNAMES32, ATM_GETNAMES },
|
||||
{ ATM_GETTYPE32, ATM_GETTYPE },
|
||||
{ ATM_GETESI32, ATM_GETESI },
|
||||
{ ATM_GETADDR32, ATM_GETADDR },
|
||||
{ ATM_RSTADDR32, ATM_RSTADDR },
|
||||
{ ATM_ADDADDR32, ATM_ADDADDR },
|
||||
{ ATM_DELADDR32, ATM_DELADDR },
|
||||
{ ATM_GETCIRANGE32, ATM_GETCIRANGE },
|
||||
{ ATM_SETCIRANGE32, ATM_SETCIRANGE },
|
||||
{ ATM_SETESI32, ATM_SETESI },
|
||||
{ ATM_SETESIF32, ATM_SETESIF },
|
||||
{ ATM_GETSTAT32, ATM_GETSTAT },
|
||||
{ ATM_GETSTATZ32, ATM_GETSTATZ },
|
||||
{ ATM_GETLOOP32, ATM_GETLOOP },
|
||||
{ ATM_SETLOOP32, ATM_SETLOOP },
|
||||
{ ATM_QUERYLOOP32, ATM_QUERYLOOP },
|
||||
};
|
||||
|
||||
#define NR_ATM_IOCTL ARRAY_SIZE(atm_ioctl_map)
|
||||
|
||||
static int do_atm_iobuf(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct atm_iobuf __user *iobuf;
|
||||
struct compat_atm_iobuf __user *iobuf32;
|
||||
u32 data;
|
||||
void __user *datap;
|
||||
int len, err;
|
||||
|
||||
iobuf = compat_alloc_user_space(sizeof(*iobuf));
|
||||
iobuf32 = compat_ptr(arg);
|
||||
|
||||
if (get_user(len, &iobuf32->length) ||
|
||||
get_user(data, &iobuf32->buffer))
|
||||
return -EFAULT;
|
||||
datap = compat_ptr(data);
|
||||
if (put_user(len, &iobuf->length) ||
|
||||
put_user(datap, &iobuf->buffer))
|
||||
return -EFAULT;
|
||||
|
||||
err = do_vcc_ioctl(sock, cmd, (unsigned long) iobuf, 0);
|
||||
|
||||
if (!err) {
|
||||
if (copy_in_user(&iobuf32->length, &iobuf->length,
|
||||
sizeof(int)))
|
||||
err = -EFAULT;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int do_atmif_sioc(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct atmif_sioc __user *sioc;
|
||||
struct compat_atmif_sioc __user *sioc32;
|
||||
u32 data;
|
||||
void __user *datap;
|
||||
int err;
|
||||
|
||||
sioc = compat_alloc_user_space(sizeof(*sioc));
|
||||
sioc32 = compat_ptr(arg);
|
||||
|
||||
if (copy_in_user(&sioc->number, &sioc32->number, 2 * sizeof(int)) ||
|
||||
get_user(data, &sioc32->arg))
|
||||
return -EFAULT;
|
||||
datap = compat_ptr(data);
|
||||
if (put_user(datap, &sioc->arg))
|
||||
return -EFAULT;
|
||||
|
||||
err = do_vcc_ioctl(sock, cmd, (unsigned long) sioc, 0);
|
||||
|
||||
if (!err) {
|
||||
if (copy_in_user(&sioc32->length, &sioc->length,
|
||||
sizeof(int)))
|
||||
err = -EFAULT;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int do_atm_ioctl(struct socket *sock, unsigned int cmd32,
|
||||
unsigned long arg)
|
||||
{
|
||||
int i;
|
||||
unsigned int cmd = 0;
|
||||
|
||||
switch (cmd32) {
|
||||
case SONET_GETSTAT:
|
||||
case SONET_GETSTATZ:
|
||||
case SONET_GETDIAG:
|
||||
case SONET_SETDIAG:
|
||||
case SONET_CLRDIAG:
|
||||
case SONET_SETFRAMING:
|
||||
case SONET_GETFRAMING:
|
||||
case SONET_GETFRSENSE:
|
||||
return do_atmif_sioc(sock, cmd32, arg);
|
||||
}
|
||||
|
||||
for (i = 0; i < NR_ATM_IOCTL; i++) {
|
||||
if (cmd32 == atm_ioctl_map[i].cmd32) {
|
||||
cmd = atm_ioctl_map[i].cmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == NR_ATM_IOCTL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (cmd) {
|
||||
case ATM_GETNAMES:
|
||||
return do_atm_iobuf(sock, cmd, arg);
|
||||
|
||||
case ATM_GETLINKRATE:
|
||||
case ATM_GETTYPE:
|
||||
case ATM_GETESI:
|
||||
case ATM_GETADDR:
|
||||
case ATM_RSTADDR:
|
||||
case ATM_ADDADDR:
|
||||
case ATM_DELADDR:
|
||||
case ATM_GETCIRANGE:
|
||||
case ATM_SETCIRANGE:
|
||||
case ATM_SETESI:
|
||||
case ATM_SETESIF:
|
||||
case ATM_GETSTAT:
|
||||
case ATM_GETSTATZ:
|
||||
case ATM_GETLOOP:
|
||||
case ATM_SETLOOP:
|
||||
case ATM_QUERYLOOP:
|
||||
return do_atmif_sioc(sock, cmd, arg);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int vcc_compat_ioctl(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = do_vcc_ioctl(sock, cmd, arg, 1);
|
||||
if (ret != -ENOIOCTLCMD)
|
||||
return ret;
|
||||
|
||||
return do_atm_ioctl(sock, cmd, arg);
|
||||
}
|
||||
#endif
|
2285
net/atm/lec.c
Normal file
2285
net/atm/lec.c
Normal file
File diff suppressed because it is too large
Load diff
154
net/atm/lec.h
Normal file
154
net/atm/lec.h
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Lan Emulation client header file
|
||||
*
|
||||
* Marko Kiiskila <mkiiskila@yahoo.com>
|
||||
*/
|
||||
|
||||
#ifndef _LEC_H_
|
||||
#define _LEC_H_
|
||||
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/atmlec.h>
|
||||
|
||||
#define LEC_HEADER_LEN 16
|
||||
|
||||
struct lecdatahdr_8023 {
|
||||
__be16 le_header;
|
||||
unsigned char h_dest[ETH_ALEN];
|
||||
unsigned char h_source[ETH_ALEN];
|
||||
__be16 h_type;
|
||||
};
|
||||
|
||||
struct lecdatahdr_8025 {
|
||||
__be16 le_header;
|
||||
unsigned char ac_pad;
|
||||
unsigned char fc;
|
||||
unsigned char h_dest[ETH_ALEN];
|
||||
unsigned char h_source[ETH_ALEN];
|
||||
};
|
||||
|
||||
#define LEC_MINIMUM_8023_SIZE 62
|
||||
#define LEC_MINIMUM_8025_SIZE 16
|
||||
|
||||
/*
|
||||
* Operations that LANE2 capable device can do. Two first functions
|
||||
* are used to make the device do things. See spec 3.1.3 and 3.1.4.
|
||||
*
|
||||
* The third function is intended for the MPOA component sitting on
|
||||
* top of the LANE device. The MPOA component assigns it's own function
|
||||
* to (*associate_indicator)() and the LANE device will use that
|
||||
* function to tell about TLVs it sees floating through.
|
||||
*
|
||||
*/
|
||||
struct lane2_ops {
|
||||
int (*resolve) (struct net_device *dev, const u8 *dst_mac, int force,
|
||||
u8 **tlvs, u32 *sizeoftlvs);
|
||||
int (*associate_req) (struct net_device *dev, const u8 *lan_dst,
|
||||
const u8 *tlvs, u32 sizeoftlvs);
|
||||
void (*associate_indicator) (struct net_device *dev, const u8 *mac_addr,
|
||||
const u8 *tlvs, u32 sizeoftlvs);
|
||||
};
|
||||
|
||||
/*
|
||||
* ATM LAN Emulation supports both LLC & Dix Ethernet EtherType
|
||||
* frames.
|
||||
*
|
||||
* 1. Dix Ethernet EtherType frames encoded by placing EtherType
|
||||
* field in h_type field. Data follows immediately after header.
|
||||
* 2. LLC Data frames whose total length, including LLC field and data,
|
||||
* but not padding required to meet the minimum data frame length,
|
||||
* is less than ETH_P_802_3_MIN MUST be encoded by placing that length
|
||||
* in the h_type field. The LLC field follows header immediately.
|
||||
* 3. LLC data frames longer than this maximum MUST be encoded by placing
|
||||
* the value 0 in the h_type field.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Hash table size */
|
||||
#define LEC_ARP_TABLE_SIZE 16
|
||||
|
||||
struct lec_priv {
|
||||
unsigned short lecid; /* Lecid of this client */
|
||||
struct hlist_head lec_arp_empty_ones;
|
||||
/* Used for storing VCC's that don't have a MAC address attached yet */
|
||||
struct hlist_head lec_arp_tables[LEC_ARP_TABLE_SIZE];
|
||||
/* Actual LE ARP table */
|
||||
struct hlist_head lec_no_forward;
|
||||
/*
|
||||
* Used for storing VCC's (and forward packets from) which are to
|
||||
* age out by not using them to forward packets.
|
||||
* This is because to some LE clients there will be 2 VCCs. Only
|
||||
* one of them gets used.
|
||||
*/
|
||||
struct hlist_head mcast_fwds;
|
||||
/*
|
||||
* With LANEv2 it is possible that BUS (or a special multicast server)
|
||||
* establishes multiple Multicast Forward VCCs to us. This list
|
||||
* collects all those VCCs. LANEv1 client has only one item in this
|
||||
* list. These entries are not aged out.
|
||||
*/
|
||||
spinlock_t lec_arp_lock;
|
||||
struct atm_vcc *mcast_vcc; /* Default Multicast Send VCC */
|
||||
struct atm_vcc *lecd;
|
||||
struct delayed_work lec_arp_work; /* C10 */
|
||||
unsigned int maximum_unknown_frame_count;
|
||||
/*
|
||||
* Within the period of time defined by this variable, the client will send
|
||||
* no more than C10 frames to BUS for a given unicast destination. (C11)
|
||||
*/
|
||||
unsigned long max_unknown_frame_time;
|
||||
/*
|
||||
* If no traffic has been sent in this vcc for this period of time,
|
||||
* vcc will be torn down (C12)
|
||||
*/
|
||||
unsigned long vcc_timeout_period;
|
||||
/*
|
||||
* An LE Client MUST not retry an LE_ARP_REQUEST for a
|
||||
* given frame's LAN Destination more than maximum retry count times,
|
||||
* after the first LEC_ARP_REQUEST (C13)
|
||||
*/
|
||||
unsigned short max_retry_count;
|
||||
/*
|
||||
* Max time the client will maintain an entry in its arp cache in
|
||||
* absence of a verification of that relationship (C17)
|
||||
*/
|
||||
unsigned long aging_time;
|
||||
/*
|
||||
* Max time the client will maintain an entry in cache when
|
||||
* topology change flag is true (C18)
|
||||
*/
|
||||
unsigned long forward_delay_time; /* Topology change flag (C19) */
|
||||
int topology_change;
|
||||
/*
|
||||
* Max time the client expects an LE_ARP_REQUEST/LE_ARP_RESPONSE
|
||||
* cycle to take (C20)
|
||||
*/
|
||||
unsigned long arp_response_time;
|
||||
/*
|
||||
* Time limit ot wait to receive an LE_FLUSH_RESPONSE after the
|
||||
* LE_FLUSH_REQUEST has been sent before taking recover action. (C21)
|
||||
*/
|
||||
unsigned long flush_timeout;
|
||||
/* The time since sending a frame to the bus after which the
|
||||
* LE Client may assume that the frame has been either discarded or
|
||||
* delivered to the recipient (C22)
|
||||
*/
|
||||
unsigned long path_switching_delay;
|
||||
|
||||
u8 *tlvs; /* LANE2: TLVs are new */
|
||||
u32 sizeoftlvs; /* The size of the tlv array in bytes */
|
||||
int lane_version; /* LANE2 */
|
||||
int itfnum; /* e.g. 2 for lec2, 5 for lec5 */
|
||||
struct lane2_ops *lane2_ops; /* can be NULL for LANE v1 */
|
||||
int is_proxy; /* bridge between ATM and Ethernet */
|
||||
};
|
||||
|
||||
struct lec_vcc_priv {
|
||||
void (*old_pop) (struct atm_vcc *vcc, struct sk_buff *skb);
|
||||
int xoff;
|
||||
};
|
||||
|
||||
#define LEC_VCC_PRIV(vcc) ((struct lec_vcc_priv *)((vcc)->user_back))
|
||||
|
||||
#endif /* _LEC_H_ */
|
96
net/atm/lec_arpc.h
Normal file
96
net/atm/lec_arpc.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Lec arp cache
|
||||
*
|
||||
* Marko Kiiskila <mkiiskila@yahoo.com>
|
||||
*/
|
||||
#ifndef _LEC_ARP_H_
|
||||
#define _LEC_ARP_H_
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/atmlec.h>
|
||||
|
||||
struct lec_arp_table {
|
||||
struct hlist_node next; /* Linked entry list */
|
||||
unsigned char atm_addr[ATM_ESA_LEN]; /* Atm address */
|
||||
unsigned char mac_addr[ETH_ALEN]; /* Mac address */
|
||||
int is_rdesc; /* Mac address is a route descriptor */
|
||||
struct atm_vcc *vcc; /* Vcc this entry is attached */
|
||||
struct atm_vcc *recv_vcc; /* Vcc we receive data from */
|
||||
|
||||
void (*old_push) (struct atm_vcc *vcc, struct sk_buff *skb);
|
||||
/* Push that leads to daemon */
|
||||
|
||||
void (*old_recv_push) (struct atm_vcc *vcc, struct sk_buff *skb);
|
||||
/* Push that leads to daemon */
|
||||
|
||||
unsigned long last_used; /* For expiry */
|
||||
unsigned long timestamp; /* Used for various timestamping things:
|
||||
* 1. FLUSH started
|
||||
* (status=ESI_FLUSH_PENDING)
|
||||
* 2. Counting to
|
||||
* max_unknown_frame_time
|
||||
* (status=ESI_ARP_PENDING||
|
||||
* status=ESI_VC_PENDING)
|
||||
*/
|
||||
unsigned char no_tries; /* No of times arp retry has been tried */
|
||||
unsigned char status; /* Status of this entry */
|
||||
unsigned short flags; /* Flags for this entry */
|
||||
unsigned short packets_flooded; /* Data packets flooded */
|
||||
unsigned long flush_tran_id; /* Transaction id in flush protocol */
|
||||
struct timer_list timer; /* Arping timer */
|
||||
struct lec_priv *priv; /* Pointer back */
|
||||
u8 *tlvs;
|
||||
u32 sizeoftlvs; /*
|
||||
* LANE2: Each MAC address can have TLVs
|
||||
* associated with it. sizeoftlvs tells the
|
||||
* the length of the tlvs array
|
||||
*/
|
||||
struct sk_buff_head tx_wait; /* wait queue for outgoing packets */
|
||||
atomic_t usage; /* usage count */
|
||||
};
|
||||
|
||||
/*
|
||||
* LANE2: Template tlv struct for accessing
|
||||
* the tlvs in the lec_arp_table->tlvs array
|
||||
*/
|
||||
struct tlv {
|
||||
u32 type;
|
||||
u8 length;
|
||||
u8 value[255];
|
||||
};
|
||||
|
||||
/* Status fields */
|
||||
#define ESI_UNKNOWN 0 /*
|
||||
* Next packet sent to this mac address
|
||||
* causes ARP-request to be sent
|
||||
*/
|
||||
#define ESI_ARP_PENDING 1 /*
|
||||
* There is no ATM address associated with this
|
||||
* 48-bit address. The LE-ARP protocol is in
|
||||
* progress.
|
||||
*/
|
||||
#define ESI_VC_PENDING 2 /*
|
||||
* There is a valid ATM address associated with
|
||||
* this 48-bit address but there is no VC set
|
||||
* up to that ATM address. The signaling
|
||||
* protocol is in process.
|
||||
*/
|
||||
#define ESI_FLUSH_PENDING 4 /*
|
||||
* The LEC has been notified of the FLUSH_START
|
||||
* status and it is assumed that the flush
|
||||
* protocol is in process.
|
||||
*/
|
||||
#define ESI_FORWARD_DIRECT 5 /*
|
||||
* Either the Path Switching Delay (C22) has
|
||||
* elapsed or the LEC has notified the Mapping
|
||||
* that the flush protocol has completed. In
|
||||
* either case, it is safe to forward packets
|
||||
* to this address via the data direct VC.
|
||||
*/
|
||||
|
||||
/* Flag values */
|
||||
#define LEC_REMOTE_FLAG 0x0001
|
||||
#define LEC_PERMANENT_FLAG 0x0002
|
||||
|
||||
#endif /* _LEC_ARP_H_ */
|
1536
net/atm/mpc.c
Normal file
1536
net/atm/mpc.c
Normal file
File diff suppressed because it is too large
Load diff
64
net/atm/mpc.h
Normal file
64
net/atm/mpc.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
#ifndef _MPC_H_
|
||||
#define _MPC_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmmpc.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include "mpoa_caches.h"
|
||||
|
||||
/* kernel -> mpc-daemon */
|
||||
int msg_to_mpoad(struct k_message *msg, struct mpoa_client *mpc);
|
||||
|
||||
struct mpoa_client {
|
||||
struct mpoa_client *next;
|
||||
struct net_device *dev; /* lec in question */
|
||||
int dev_num; /* e.g. 2 for lec2 */
|
||||
|
||||
struct atm_vcc *mpoad_vcc; /* control channel to mpoad */
|
||||
uint8_t mps_ctrl_addr[ATM_ESA_LEN]; /* MPS control ATM address */
|
||||
uint8_t our_ctrl_addr[ATM_ESA_LEN]; /* MPC's control ATM address */
|
||||
|
||||
rwlock_t ingress_lock;
|
||||
struct in_cache_ops *in_ops; /* ingress cache operations */
|
||||
in_cache_entry *in_cache; /* the ingress cache of this MPC */
|
||||
|
||||
rwlock_t egress_lock;
|
||||
struct eg_cache_ops *eg_ops; /* egress cache operations */
|
||||
eg_cache_entry *eg_cache; /* the egress cache of this MPC */
|
||||
|
||||
uint8_t *mps_macs; /* array of MPS MAC addresses, >=1 */
|
||||
int number_of_mps_macs; /* number of the above MAC addresses */
|
||||
struct mpc_parameters parameters; /* parameters for this client */
|
||||
|
||||
const struct net_device_ops *old_ops;
|
||||
struct net_device_ops new_ops;
|
||||
};
|
||||
|
||||
|
||||
struct atm_mpoa_qos {
|
||||
struct atm_mpoa_qos *next;
|
||||
__be32 ipaddr;
|
||||
struct atm_qos qos;
|
||||
};
|
||||
|
||||
|
||||
/* MPOA QoS operations */
|
||||
struct atm_mpoa_qos *atm_mpoa_add_qos(__be32 dst_ip, struct atm_qos *qos);
|
||||
struct atm_mpoa_qos *atm_mpoa_search_qos(__be32 dst_ip);
|
||||
int atm_mpoa_delete_qos(struct atm_mpoa_qos *qos);
|
||||
|
||||
/* Display QoS entries. This is for the procfs */
|
||||
struct seq_file;
|
||||
void atm_mpoa_disp_qos(struct seq_file *m);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
int mpc_proc_init(void);
|
||||
void mpc_proc_clean(void);
|
||||
#else
|
||||
#define mpc_proc_init() (0)
|
||||
#define mpc_proc_clean() do { } while(0)
|
||||
#endif
|
||||
|
||||
#endif /* _MPC_H_ */
|
569
net/atm/mpoa_caches.c
Normal file
569
net/atm/mpoa_caches.c
Normal file
|
@ -0,0 +1,569 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/atmmpc.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/time.h>
|
||||
|
||||
#include "mpoa_caches.h"
|
||||
#include "mpc.h"
|
||||
|
||||
/*
|
||||
* mpoa_caches.c: Implementation of ingress and egress cache
|
||||
* handling functions
|
||||
*/
|
||||
|
||||
#if 0
|
||||
#define dprintk(format, args...) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args) /* debug */
|
||||
#else
|
||||
#define dprintk(format, args...) \
|
||||
do { if (0) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args);\
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define ddprintk(format, args...) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args) /* debug */
|
||||
#else
|
||||
#define ddprintk(format, args...) \
|
||||
do { if (0) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args);\
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
static in_cache_entry *in_cache_get(__be32 dst_ip,
|
||||
struct mpoa_client *client)
|
||||
{
|
||||
in_cache_entry *entry;
|
||||
|
||||
read_lock_bh(&client->ingress_lock);
|
||||
entry = client->in_cache;
|
||||
while (entry != NULL) {
|
||||
if (entry->ctrl_info.in_dst_ip == dst_ip) {
|
||||
atomic_inc(&entry->use);
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static in_cache_entry *in_cache_get_with_mask(__be32 dst_ip,
|
||||
struct mpoa_client *client,
|
||||
__be32 mask)
|
||||
{
|
||||
in_cache_entry *entry;
|
||||
|
||||
read_lock_bh(&client->ingress_lock);
|
||||
entry = client->in_cache;
|
||||
while (entry != NULL) {
|
||||
if ((entry->ctrl_info.in_dst_ip & mask) == (dst_ip & mask)) {
|
||||
atomic_inc(&entry->use);
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
static in_cache_entry *in_cache_get_by_vcc(struct atm_vcc *vcc,
|
||||
struct mpoa_client *client)
|
||||
{
|
||||
in_cache_entry *entry;
|
||||
|
||||
read_lock_bh(&client->ingress_lock);
|
||||
entry = client->in_cache;
|
||||
while (entry != NULL) {
|
||||
if (entry->shortcut == vcc) {
|
||||
atomic_inc(&entry->use);
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static in_cache_entry *in_cache_add_entry(__be32 dst_ip,
|
||||
struct mpoa_client *client)
|
||||
{
|
||||
in_cache_entry *entry = kzalloc(sizeof(in_cache_entry), GFP_KERNEL);
|
||||
|
||||
if (entry == NULL) {
|
||||
pr_info("mpoa: mpoa_caches.c: new_in_cache_entry: out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dprintk("adding an ingress entry, ip = %pI4\n", &dst_ip);
|
||||
|
||||
atomic_set(&entry->use, 1);
|
||||
dprintk("new_in_cache_entry: about to lock\n");
|
||||
write_lock_bh(&client->ingress_lock);
|
||||
entry->next = client->in_cache;
|
||||
entry->prev = NULL;
|
||||
if (client->in_cache != NULL)
|
||||
client->in_cache->prev = entry;
|
||||
client->in_cache = entry;
|
||||
|
||||
memcpy(entry->MPS_ctrl_ATM_addr, client->mps_ctrl_addr, ATM_ESA_LEN);
|
||||
entry->ctrl_info.in_dst_ip = dst_ip;
|
||||
do_gettimeofday(&(entry->tv));
|
||||
entry->retry_time = client->parameters.mpc_p4;
|
||||
entry->count = 1;
|
||||
entry->entry_state = INGRESS_INVALID;
|
||||
entry->ctrl_info.holding_time = HOLDING_TIME_DEFAULT;
|
||||
atomic_inc(&entry->use);
|
||||
|
||||
write_unlock_bh(&client->ingress_lock);
|
||||
dprintk("new_in_cache_entry: unlocked\n");
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static int cache_hit(in_cache_entry *entry, struct mpoa_client *mpc)
|
||||
{
|
||||
struct atm_mpoa_qos *qos;
|
||||
struct k_message msg;
|
||||
|
||||
entry->count++;
|
||||
if (entry->entry_state == INGRESS_RESOLVED && entry->shortcut != NULL)
|
||||
return OPEN;
|
||||
|
||||
if (entry->entry_state == INGRESS_REFRESHING) {
|
||||
if (entry->count > mpc->parameters.mpc_p1) {
|
||||
msg.type = SND_MPOA_RES_RQST;
|
||||
msg.content.in_info = entry->ctrl_info;
|
||||
memcpy(msg.MPS_ctrl, mpc->mps_ctrl_addr, ATM_ESA_LEN);
|
||||
qos = atm_mpoa_search_qos(entry->ctrl_info.in_dst_ip);
|
||||
if (qos != NULL)
|
||||
msg.qos = qos->qos;
|
||||
msg_to_mpoad(&msg, mpc);
|
||||
do_gettimeofday(&(entry->reply_wait));
|
||||
entry->entry_state = INGRESS_RESOLVING;
|
||||
}
|
||||
if (entry->shortcut != NULL)
|
||||
return OPEN;
|
||||
return CLOSED;
|
||||
}
|
||||
|
||||
if (entry->entry_state == INGRESS_RESOLVING && entry->shortcut != NULL)
|
||||
return OPEN;
|
||||
|
||||
if (entry->count > mpc->parameters.mpc_p1 &&
|
||||
entry->entry_state == INGRESS_INVALID) {
|
||||
dprintk("(%s) threshold exceeded for ip %pI4, sending MPOA res req\n",
|
||||
mpc->dev->name, &entry->ctrl_info.in_dst_ip);
|
||||
entry->entry_state = INGRESS_RESOLVING;
|
||||
msg.type = SND_MPOA_RES_RQST;
|
||||
memcpy(msg.MPS_ctrl, mpc->mps_ctrl_addr, ATM_ESA_LEN);
|
||||
msg.content.in_info = entry->ctrl_info;
|
||||
qos = atm_mpoa_search_qos(entry->ctrl_info.in_dst_ip);
|
||||
if (qos != NULL)
|
||||
msg.qos = qos->qos;
|
||||
msg_to_mpoad(&msg, mpc);
|
||||
do_gettimeofday(&(entry->reply_wait));
|
||||
}
|
||||
|
||||
return CLOSED;
|
||||
}
|
||||
|
||||
static void in_cache_put(in_cache_entry *entry)
|
||||
{
|
||||
if (atomic_dec_and_test(&entry->use)) {
|
||||
memset(entry, 0, sizeof(in_cache_entry));
|
||||
kfree(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This should be called with write lock on
|
||||
*/
|
||||
static void in_cache_remove_entry(in_cache_entry *entry,
|
||||
struct mpoa_client *client)
|
||||
{
|
||||
struct atm_vcc *vcc;
|
||||
struct k_message msg;
|
||||
|
||||
vcc = entry->shortcut;
|
||||
dprintk("removing an ingress entry, ip = %pI4\n",
|
||||
&entry->ctrl_info.in_dst_ip);
|
||||
|
||||
if (entry->prev != NULL)
|
||||
entry->prev->next = entry->next;
|
||||
else
|
||||
client->in_cache = entry->next;
|
||||
if (entry->next != NULL)
|
||||
entry->next->prev = entry->prev;
|
||||
client->in_ops->put(entry);
|
||||
if (client->in_cache == NULL && client->eg_cache == NULL) {
|
||||
msg.type = STOP_KEEP_ALIVE_SM;
|
||||
msg_to_mpoad(&msg, client);
|
||||
}
|
||||
|
||||
/* Check if the egress side still uses this VCC */
|
||||
if (vcc != NULL) {
|
||||
eg_cache_entry *eg_entry = client->eg_ops->get_by_vcc(vcc,
|
||||
client);
|
||||
if (eg_entry != NULL) {
|
||||
client->eg_ops->put(eg_entry);
|
||||
return;
|
||||
}
|
||||
vcc_release_async(vcc, -EPIPE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Call this every MPC-p2 seconds... Not exactly correct solution,
|
||||
but an easy one... */
|
||||
static void clear_count_and_expired(struct mpoa_client *client)
|
||||
{
|
||||
in_cache_entry *entry, *next_entry;
|
||||
struct timeval now;
|
||||
|
||||
do_gettimeofday(&now);
|
||||
|
||||
write_lock_bh(&client->ingress_lock);
|
||||
entry = client->in_cache;
|
||||
while (entry != NULL) {
|
||||
entry->count = 0;
|
||||
next_entry = entry->next;
|
||||
if ((now.tv_sec - entry->tv.tv_sec)
|
||||
> entry->ctrl_info.holding_time) {
|
||||
dprintk("holding time expired, ip = %pI4\n",
|
||||
&entry->ctrl_info.in_dst_ip);
|
||||
client->in_ops->remove_entry(entry, client);
|
||||
}
|
||||
entry = next_entry;
|
||||
}
|
||||
write_unlock_bh(&client->ingress_lock);
|
||||
}
|
||||
|
||||
/* Call this every MPC-p4 seconds. */
|
||||
static void check_resolving_entries(struct mpoa_client *client)
|
||||
{
|
||||
|
||||
struct atm_mpoa_qos *qos;
|
||||
in_cache_entry *entry;
|
||||
struct timeval now;
|
||||
struct k_message msg;
|
||||
|
||||
do_gettimeofday(&now);
|
||||
|
||||
read_lock_bh(&client->ingress_lock);
|
||||
entry = client->in_cache;
|
||||
while (entry != NULL) {
|
||||
if (entry->entry_state == INGRESS_RESOLVING) {
|
||||
if ((now.tv_sec - entry->hold_down.tv_sec) <
|
||||
client->parameters.mpc_p6) {
|
||||
entry = entry->next; /* Entry in hold down */
|
||||
continue;
|
||||
}
|
||||
if ((now.tv_sec - entry->reply_wait.tv_sec) >
|
||||
entry->retry_time) {
|
||||
entry->retry_time = MPC_C1 * (entry->retry_time);
|
||||
/*
|
||||
* Retry time maximum exceeded,
|
||||
* put entry in hold down.
|
||||
*/
|
||||
if (entry->retry_time > client->parameters.mpc_p5) {
|
||||
do_gettimeofday(&(entry->hold_down));
|
||||
entry->retry_time = client->parameters.mpc_p4;
|
||||
entry = entry->next;
|
||||
continue;
|
||||
}
|
||||
/* Ask daemon to send a resolution request. */
|
||||
memset(&(entry->hold_down), 0, sizeof(struct timeval));
|
||||
msg.type = SND_MPOA_RES_RTRY;
|
||||
memcpy(msg.MPS_ctrl, client->mps_ctrl_addr, ATM_ESA_LEN);
|
||||
msg.content.in_info = entry->ctrl_info;
|
||||
qos = atm_mpoa_search_qos(entry->ctrl_info.in_dst_ip);
|
||||
if (qos != NULL)
|
||||
msg.qos = qos->qos;
|
||||
msg_to_mpoad(&msg, client);
|
||||
do_gettimeofday(&(entry->reply_wait));
|
||||
}
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
}
|
||||
|
||||
/* Call this every MPC-p5 seconds. */
|
||||
static void refresh_entries(struct mpoa_client *client)
|
||||
{
|
||||
struct timeval now;
|
||||
struct in_cache_entry *entry = client->in_cache;
|
||||
|
||||
ddprintk("refresh_entries\n");
|
||||
do_gettimeofday(&now);
|
||||
|
||||
read_lock_bh(&client->ingress_lock);
|
||||
while (entry != NULL) {
|
||||
if (entry->entry_state == INGRESS_RESOLVED) {
|
||||
if (!(entry->refresh_time))
|
||||
entry->refresh_time = (2 * (entry->ctrl_info.holding_time))/3;
|
||||
if ((now.tv_sec - entry->reply_wait.tv_sec) >
|
||||
entry->refresh_time) {
|
||||
dprintk("refreshing an entry.\n");
|
||||
entry->entry_state = INGRESS_REFRESHING;
|
||||
|
||||
}
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_bh(&client->ingress_lock);
|
||||
}
|
||||
|
||||
static void in_destroy_cache(struct mpoa_client *mpc)
|
||||
{
|
||||
write_lock_irq(&mpc->ingress_lock);
|
||||
while (mpc->in_cache != NULL)
|
||||
mpc->in_ops->remove_entry(mpc->in_cache, mpc);
|
||||
write_unlock_irq(&mpc->ingress_lock);
|
||||
}
|
||||
|
||||
static eg_cache_entry *eg_cache_get_by_cache_id(__be32 cache_id,
|
||||
struct mpoa_client *mpc)
|
||||
{
|
||||
eg_cache_entry *entry;
|
||||
|
||||
read_lock_irq(&mpc->egress_lock);
|
||||
entry = mpc->eg_cache;
|
||||
while (entry != NULL) {
|
||||
if (entry->ctrl_info.cache_id == cache_id) {
|
||||
atomic_inc(&entry->use);
|
||||
read_unlock_irq(&mpc->egress_lock);
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_irq(&mpc->egress_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* This can be called from any context since it saves CPU flags */
|
||||
static eg_cache_entry *eg_cache_get_by_tag(__be32 tag, struct mpoa_client *mpc)
|
||||
{
|
||||
unsigned long flags;
|
||||
eg_cache_entry *entry;
|
||||
|
||||
read_lock_irqsave(&mpc->egress_lock, flags);
|
||||
entry = mpc->eg_cache;
|
||||
while (entry != NULL) {
|
||||
if (entry->ctrl_info.tag == tag) {
|
||||
atomic_inc(&entry->use);
|
||||
read_unlock_irqrestore(&mpc->egress_lock, flags);
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_irqrestore(&mpc->egress_lock, flags);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* This can be called from any context since it saves CPU flags */
|
||||
static eg_cache_entry *eg_cache_get_by_vcc(struct atm_vcc *vcc,
|
||||
struct mpoa_client *mpc)
|
||||
{
|
||||
unsigned long flags;
|
||||
eg_cache_entry *entry;
|
||||
|
||||
read_lock_irqsave(&mpc->egress_lock, flags);
|
||||
entry = mpc->eg_cache;
|
||||
while (entry != NULL) {
|
||||
if (entry->shortcut == vcc) {
|
||||
atomic_inc(&entry->use);
|
||||
read_unlock_irqrestore(&mpc->egress_lock, flags);
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_irqrestore(&mpc->egress_lock, flags);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static eg_cache_entry *eg_cache_get_by_src_ip(__be32 ipaddr,
|
||||
struct mpoa_client *mpc)
|
||||
{
|
||||
eg_cache_entry *entry;
|
||||
|
||||
read_lock_irq(&mpc->egress_lock);
|
||||
entry = mpc->eg_cache;
|
||||
while (entry != NULL) {
|
||||
if (entry->latest_ip_addr == ipaddr) {
|
||||
atomic_inc(&entry->use);
|
||||
read_unlock_irq(&mpc->egress_lock);
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
read_unlock_irq(&mpc->egress_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void eg_cache_put(eg_cache_entry *entry)
|
||||
{
|
||||
if (atomic_dec_and_test(&entry->use)) {
|
||||
memset(entry, 0, sizeof(eg_cache_entry));
|
||||
kfree(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This should be called with write lock on
|
||||
*/
|
||||
static void eg_cache_remove_entry(eg_cache_entry *entry,
|
||||
struct mpoa_client *client)
|
||||
{
|
||||
struct atm_vcc *vcc;
|
||||
struct k_message msg;
|
||||
|
||||
vcc = entry->shortcut;
|
||||
dprintk("removing an egress entry.\n");
|
||||
if (entry->prev != NULL)
|
||||
entry->prev->next = entry->next;
|
||||
else
|
||||
client->eg_cache = entry->next;
|
||||
if (entry->next != NULL)
|
||||
entry->next->prev = entry->prev;
|
||||
client->eg_ops->put(entry);
|
||||
if (client->in_cache == NULL && client->eg_cache == NULL) {
|
||||
msg.type = STOP_KEEP_ALIVE_SM;
|
||||
msg_to_mpoad(&msg, client);
|
||||
}
|
||||
|
||||
/* Check if the ingress side still uses this VCC */
|
||||
if (vcc != NULL) {
|
||||
in_cache_entry *in_entry = client->in_ops->get_by_vcc(vcc, client);
|
||||
if (in_entry != NULL) {
|
||||
client->in_ops->put(in_entry);
|
||||
return;
|
||||
}
|
||||
vcc_release_async(vcc, -EPIPE);
|
||||
}
|
||||
}
|
||||
|
||||
static eg_cache_entry *eg_cache_add_entry(struct k_message *msg,
|
||||
struct mpoa_client *client)
|
||||
{
|
||||
eg_cache_entry *entry = kzalloc(sizeof(eg_cache_entry), GFP_KERNEL);
|
||||
|
||||
if (entry == NULL) {
|
||||
pr_info("out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dprintk("adding an egress entry, ip = %pI4, this should be our IP\n",
|
||||
&msg->content.eg_info.eg_dst_ip);
|
||||
|
||||
atomic_set(&entry->use, 1);
|
||||
dprintk("new_eg_cache_entry: about to lock\n");
|
||||
write_lock_irq(&client->egress_lock);
|
||||
entry->next = client->eg_cache;
|
||||
entry->prev = NULL;
|
||||
if (client->eg_cache != NULL)
|
||||
client->eg_cache->prev = entry;
|
||||
client->eg_cache = entry;
|
||||
|
||||
memcpy(entry->MPS_ctrl_ATM_addr, client->mps_ctrl_addr, ATM_ESA_LEN);
|
||||
entry->ctrl_info = msg->content.eg_info;
|
||||
do_gettimeofday(&(entry->tv));
|
||||
entry->entry_state = EGRESS_RESOLVED;
|
||||
dprintk("new_eg_cache_entry cache_id %u\n",
|
||||
ntohl(entry->ctrl_info.cache_id));
|
||||
dprintk("mps_ip = %pI4\n", &entry->ctrl_info.mps_ip);
|
||||
atomic_inc(&entry->use);
|
||||
|
||||
write_unlock_irq(&client->egress_lock);
|
||||
dprintk("new_eg_cache_entry: unlocked\n");
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static void update_eg_cache_entry(eg_cache_entry *entry, uint16_t holding_time)
|
||||
{
|
||||
do_gettimeofday(&(entry->tv));
|
||||
entry->entry_state = EGRESS_RESOLVED;
|
||||
entry->ctrl_info.holding_time = holding_time;
|
||||
}
|
||||
|
||||
static void clear_expired(struct mpoa_client *client)
|
||||
{
|
||||
eg_cache_entry *entry, *next_entry;
|
||||
struct timeval now;
|
||||
struct k_message msg;
|
||||
|
||||
do_gettimeofday(&now);
|
||||
|
||||
write_lock_irq(&client->egress_lock);
|
||||
entry = client->eg_cache;
|
||||
while (entry != NULL) {
|
||||
next_entry = entry->next;
|
||||
if ((now.tv_sec - entry->tv.tv_sec)
|
||||
> entry->ctrl_info.holding_time) {
|
||||
msg.type = SND_EGRESS_PURGE;
|
||||
msg.content.eg_info = entry->ctrl_info;
|
||||
dprintk("egress_cache: holding time expired, cache_id = %u.\n",
|
||||
ntohl(entry->ctrl_info.cache_id));
|
||||
msg_to_mpoad(&msg, client);
|
||||
client->eg_ops->remove_entry(entry, client);
|
||||
}
|
||||
entry = next_entry;
|
||||
}
|
||||
write_unlock_irq(&client->egress_lock);
|
||||
}
|
||||
|
||||
static void eg_destroy_cache(struct mpoa_client *mpc)
|
||||
{
|
||||
write_lock_irq(&mpc->egress_lock);
|
||||
while (mpc->eg_cache != NULL)
|
||||
mpc->eg_ops->remove_entry(mpc->eg_cache, mpc);
|
||||
write_unlock_irq(&mpc->egress_lock);
|
||||
}
|
||||
|
||||
|
||||
static struct in_cache_ops ingress_ops = {
|
||||
in_cache_add_entry, /* add_entry */
|
||||
in_cache_get, /* get */
|
||||
in_cache_get_with_mask, /* get_with_mask */
|
||||
in_cache_get_by_vcc, /* get_by_vcc */
|
||||
in_cache_put, /* put */
|
||||
in_cache_remove_entry, /* remove_entry */
|
||||
cache_hit, /* cache_hit */
|
||||
clear_count_and_expired, /* clear_count */
|
||||
check_resolving_entries, /* check_resolving */
|
||||
refresh_entries, /* refresh */
|
||||
in_destroy_cache /* destroy_cache */
|
||||
};
|
||||
|
||||
static struct eg_cache_ops egress_ops = {
|
||||
eg_cache_add_entry, /* add_entry */
|
||||
eg_cache_get_by_cache_id, /* get_by_cache_id */
|
||||
eg_cache_get_by_tag, /* get_by_tag */
|
||||
eg_cache_get_by_vcc, /* get_by_vcc */
|
||||
eg_cache_get_by_src_ip, /* get_by_src_ip */
|
||||
eg_cache_put, /* put */
|
||||
eg_cache_remove_entry, /* remove_entry */
|
||||
update_eg_cache_entry, /* update */
|
||||
clear_expired, /* clear_expired */
|
||||
eg_destroy_cache /* destroy_cache */
|
||||
};
|
||||
|
||||
|
||||
void atm_mpoa_init_cache(struct mpoa_client *mpc)
|
||||
{
|
||||
mpc->in_ops = &ingress_ops;
|
||||
mpc->eg_ops = &egress_ops;
|
||||
}
|
96
net/atm/mpoa_caches.h
Normal file
96
net/atm/mpoa_caches.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
#ifndef MPOA_CACHES_H
|
||||
#define MPOA_CACHES_H
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/atmmpc.h>
|
||||
|
||||
struct mpoa_client;
|
||||
|
||||
void atm_mpoa_init_cache(struct mpoa_client *mpc);
|
||||
|
||||
typedef struct in_cache_entry {
|
||||
struct in_cache_entry *next;
|
||||
struct in_cache_entry *prev;
|
||||
struct timeval tv;
|
||||
struct timeval reply_wait;
|
||||
struct timeval hold_down;
|
||||
uint32_t packets_fwded;
|
||||
uint16_t entry_state;
|
||||
uint32_t retry_time;
|
||||
uint32_t refresh_time;
|
||||
uint32_t count;
|
||||
struct atm_vcc *shortcut;
|
||||
uint8_t MPS_ctrl_ATM_addr[ATM_ESA_LEN];
|
||||
struct in_ctrl_info ctrl_info;
|
||||
atomic_t use;
|
||||
} in_cache_entry;
|
||||
|
||||
struct in_cache_ops{
|
||||
in_cache_entry *(*add_entry)(__be32 dst_ip,
|
||||
struct mpoa_client *client);
|
||||
in_cache_entry *(*get)(__be32 dst_ip, struct mpoa_client *client);
|
||||
in_cache_entry *(*get_with_mask)(__be32 dst_ip,
|
||||
struct mpoa_client *client,
|
||||
__be32 mask);
|
||||
in_cache_entry *(*get_by_vcc)(struct atm_vcc *vcc,
|
||||
struct mpoa_client *client);
|
||||
void (*put)(in_cache_entry *entry);
|
||||
void (*remove_entry)(in_cache_entry *delEntry,
|
||||
struct mpoa_client *client );
|
||||
int (*cache_hit)(in_cache_entry *entry,
|
||||
struct mpoa_client *client);
|
||||
void (*clear_count)(struct mpoa_client *client);
|
||||
void (*check_resolving)(struct mpoa_client *client);
|
||||
void (*refresh)(struct mpoa_client *client);
|
||||
void (*destroy_cache)(struct mpoa_client *mpc);
|
||||
};
|
||||
|
||||
typedef struct eg_cache_entry{
|
||||
struct eg_cache_entry *next;
|
||||
struct eg_cache_entry *prev;
|
||||
struct timeval tv;
|
||||
uint8_t MPS_ctrl_ATM_addr[ATM_ESA_LEN];
|
||||
struct atm_vcc *shortcut;
|
||||
uint32_t packets_rcvd;
|
||||
uint16_t entry_state;
|
||||
__be32 latest_ip_addr; /* The src IP address of the last packet */
|
||||
struct eg_ctrl_info ctrl_info;
|
||||
atomic_t use;
|
||||
} eg_cache_entry;
|
||||
|
||||
struct eg_cache_ops{
|
||||
eg_cache_entry *(*add_entry)(struct k_message *msg, struct mpoa_client *client);
|
||||
eg_cache_entry *(*get_by_cache_id)(__be32 cache_id, struct mpoa_client *client);
|
||||
eg_cache_entry *(*get_by_tag)(__be32 cache_id, struct mpoa_client *client);
|
||||
eg_cache_entry *(*get_by_vcc)(struct atm_vcc *vcc, struct mpoa_client *client);
|
||||
eg_cache_entry *(*get_by_src_ip)(__be32 ipaddr, struct mpoa_client *client);
|
||||
void (*put)(eg_cache_entry *entry);
|
||||
void (*remove_entry)(eg_cache_entry *entry, struct mpoa_client *client);
|
||||
void (*update)(eg_cache_entry *entry, uint16_t holding_time);
|
||||
void (*clear_expired)(struct mpoa_client *client);
|
||||
void (*destroy_cache)(struct mpoa_client *mpc);
|
||||
};
|
||||
|
||||
|
||||
/* Ingress cache entry states */
|
||||
|
||||
#define INGRESS_REFRESHING 3
|
||||
#define INGRESS_RESOLVED 2
|
||||
#define INGRESS_RESOLVING 1
|
||||
#define INGRESS_INVALID 0
|
||||
|
||||
/* VCC states */
|
||||
|
||||
#define OPEN 1
|
||||
#define CLOSED 0
|
||||
|
||||
/* Egress cache entry states */
|
||||
|
||||
#define EGRESS_RESOLVED 2
|
||||
#define EGRESS_PURGE 1
|
||||
#define EGRESS_INVALID 0
|
||||
|
||||
#endif
|
312
net/atm/mpoa_proc.c
Normal file
312
net/atm/mpoa_proc.c
Normal file
|
@ -0,0 +1,312 @@
|
|||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/atmmpc.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/gfp.h>
|
||||
#include "mpc.h"
|
||||
#include "mpoa_caches.h"
|
||||
|
||||
/*
|
||||
* mpoa_proc.c: Implementation MPOA client's proc
|
||||
* file system statistics
|
||||
*/
|
||||
|
||||
#if 1
|
||||
#define dprintk(format, args...) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args) /* debug */
|
||||
#else
|
||||
#define dprintk(format, args...) \
|
||||
do { if (0) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args);\
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define ddprintk(format, args...) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args) /* debug */
|
||||
#else
|
||||
#define ddprintk(format, args...) \
|
||||
do { if (0) \
|
||||
printk(KERN_DEBUG "mpoa:%s: " format, __FILE__, ##args);\
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define STAT_FILE_NAME "mpc" /* Our statistic file's name */
|
||||
|
||||
extern struct mpoa_client *mpcs;
|
||||
extern struct proc_dir_entry *atm_proc_root; /* from proc.c. */
|
||||
|
||||
static int proc_mpc_open(struct inode *inode, struct file *file);
|
||||
static ssize_t proc_mpc_write(struct file *file, const char __user *buff,
|
||||
size_t nbytes, loff_t *ppos);
|
||||
|
||||
static int parse_qos(const char *buff);
|
||||
|
||||
/*
|
||||
* Define allowed FILE OPERATIONS
|
||||
*/
|
||||
static const struct file_operations mpc_file_operations = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = proc_mpc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.write = proc_mpc_write,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns the state of an ingress cache entry as a string
|
||||
*/
|
||||
static const char *ingress_state_string(int state)
|
||||
{
|
||||
switch (state) {
|
||||
case INGRESS_RESOLVING:
|
||||
return "resolving ";
|
||||
case INGRESS_RESOLVED:
|
||||
return "resolved ";
|
||||
case INGRESS_INVALID:
|
||||
return "invalid ";
|
||||
case INGRESS_REFRESHING:
|
||||
return "refreshing ";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the state of an egress cache entry as a string
|
||||
*/
|
||||
static const char *egress_state_string(int state)
|
||||
{
|
||||
switch (state) {
|
||||
case EGRESS_RESOLVED:
|
||||
return "resolved ";
|
||||
case EGRESS_PURGE:
|
||||
return "purge ";
|
||||
case EGRESS_INVALID:
|
||||
return "invalid ";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: mpcs (and per-mpc lists) have no locking whatsoever.
|
||||
*/
|
||||
|
||||
static void *mpc_start(struct seq_file *m, loff_t *pos)
|
||||
{
|
||||
loff_t l = *pos;
|
||||
struct mpoa_client *mpc;
|
||||
|
||||
if (!l--)
|
||||
return SEQ_START_TOKEN;
|
||||
for (mpc = mpcs; mpc; mpc = mpc->next)
|
||||
if (!l--)
|
||||
return mpc;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *mpc_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
{
|
||||
struct mpoa_client *p = v;
|
||||
(*pos)++;
|
||||
return v == SEQ_START_TOKEN ? mpcs : p->next;
|
||||
}
|
||||
|
||||
static void mpc_stop(struct seq_file *m, void *v)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* READING function - called when the /proc/atm/mpoa file is read from.
|
||||
*/
|
||||
static int mpc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct mpoa_client *mpc = v;
|
||||
int i;
|
||||
in_cache_entry *in_entry;
|
||||
eg_cache_entry *eg_entry;
|
||||
struct timeval now;
|
||||
unsigned char ip_string[16];
|
||||
|
||||
if (v == SEQ_START_TOKEN) {
|
||||
atm_mpoa_disp_qos(m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
seq_printf(m, "\nInterface %d:\n\n", mpc->dev_num);
|
||||
seq_printf(m, "Ingress Entries:\nIP address State Holding time Packets fwded VPI VCI\n");
|
||||
do_gettimeofday(&now);
|
||||
|
||||
for (in_entry = mpc->in_cache; in_entry; in_entry = in_entry->next) {
|
||||
sprintf(ip_string, "%pI4", &in_entry->ctrl_info.in_dst_ip);
|
||||
seq_printf(m, "%-16s%s%-14lu%-12u",
|
||||
ip_string,
|
||||
ingress_state_string(in_entry->entry_state),
|
||||
in_entry->ctrl_info.holding_time -
|
||||
(now.tv_sec-in_entry->tv.tv_sec),
|
||||
in_entry->packets_fwded);
|
||||
if (in_entry->shortcut)
|
||||
seq_printf(m, " %-3d %-3d",
|
||||
in_entry->shortcut->vpi,
|
||||
in_entry->shortcut->vci);
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
|
||||
seq_printf(m, "\n");
|
||||
seq_printf(m, "Egress Entries:\nIngress MPC ATM addr\nCache-id State Holding time Packets recvd Latest IP addr VPI VCI\n");
|
||||
for (eg_entry = mpc->eg_cache; eg_entry; eg_entry = eg_entry->next) {
|
||||
unsigned char *p = eg_entry->ctrl_info.in_MPC_data_ATM_addr;
|
||||
for (i = 0; i < ATM_ESA_LEN; i++)
|
||||
seq_printf(m, "%02x", p[i]);
|
||||
seq_printf(m, "\n%-16lu%s%-14lu%-15u",
|
||||
(unsigned long)ntohl(eg_entry->ctrl_info.cache_id),
|
||||
egress_state_string(eg_entry->entry_state),
|
||||
(eg_entry->ctrl_info.holding_time -
|
||||
(now.tv_sec-eg_entry->tv.tv_sec)),
|
||||
eg_entry->packets_rcvd);
|
||||
|
||||
/* latest IP address */
|
||||
sprintf(ip_string, "%pI4", &eg_entry->latest_ip_addr);
|
||||
seq_printf(m, "%-16s", ip_string);
|
||||
|
||||
if (eg_entry->shortcut)
|
||||
seq_printf(m, " %-3d %-3d",
|
||||
eg_entry->shortcut->vpi,
|
||||
eg_entry->shortcut->vci);
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
seq_printf(m, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations mpc_op = {
|
||||
.start = mpc_start,
|
||||
.next = mpc_next,
|
||||
.stop = mpc_stop,
|
||||
.show = mpc_show
|
||||
};
|
||||
|
||||
static int proc_mpc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_open(file, &mpc_op);
|
||||
}
|
||||
|
||||
static ssize_t proc_mpc_write(struct file *file, const char __user *buff,
|
||||
size_t nbytes, loff_t *ppos)
|
||||
{
|
||||
char *page, *p;
|
||||
unsigned int len;
|
||||
|
||||
if (nbytes == 0)
|
||||
return 0;
|
||||
|
||||
if (nbytes >= PAGE_SIZE)
|
||||
nbytes = PAGE_SIZE-1;
|
||||
|
||||
page = (char *)__get_free_page(GFP_KERNEL);
|
||||
if (!page)
|
||||
return -ENOMEM;
|
||||
|
||||
for (p = page, len = 0; len < nbytes; p++, len++) {
|
||||
if (get_user(*p, buff++)) {
|
||||
free_page((unsigned long)page);
|
||||
return -EFAULT;
|
||||
}
|
||||
if (*p == '\0' || *p == '\n')
|
||||
break;
|
||||
}
|
||||
|
||||
*p = '\0';
|
||||
|
||||
if (!parse_qos(page))
|
||||
printk("mpoa: proc_mpc_write: could not parse '%s'\n", page);
|
||||
|
||||
free_page((unsigned long)page);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int parse_qos(const char *buff)
|
||||
{
|
||||
/* possible lines look like this
|
||||
* add 130.230.54.142 tx=max_pcr,max_sdu rx=max_pcr,max_sdu
|
||||
*/
|
||||
unsigned char ip[4];
|
||||
int tx_pcr, tx_sdu, rx_pcr, rx_sdu;
|
||||
__be32 ipaddr;
|
||||
struct atm_qos qos;
|
||||
|
||||
memset(&qos, 0, sizeof(struct atm_qos));
|
||||
|
||||
if (sscanf(buff, "del %hhu.%hhu.%hhu.%hhu",
|
||||
ip, ip+1, ip+2, ip+3) == 4) {
|
||||
ipaddr = *(__be32 *)ip;
|
||||
return atm_mpoa_delete_qos(atm_mpoa_search_qos(ipaddr));
|
||||
}
|
||||
|
||||
if (sscanf(buff, "add %hhu.%hhu.%hhu.%hhu tx=%d,%d rx=tx",
|
||||
ip, ip+1, ip+2, ip+3, &tx_pcr, &tx_sdu) == 6) {
|
||||
rx_pcr = tx_pcr;
|
||||
rx_sdu = tx_sdu;
|
||||
} else if (sscanf(buff, "add %hhu.%hhu.%hhu.%hhu tx=%d,%d rx=%d,%d",
|
||||
ip, ip+1, ip+2, ip+3, &tx_pcr, &tx_sdu, &rx_pcr, &rx_sdu) != 8)
|
||||
return 0;
|
||||
|
||||
ipaddr = *(__be32 *)ip;
|
||||
qos.txtp.traffic_class = ATM_CBR;
|
||||
qos.txtp.max_pcr = tx_pcr;
|
||||
qos.txtp.max_sdu = tx_sdu;
|
||||
qos.rxtp.traffic_class = ATM_CBR;
|
||||
qos.rxtp.max_pcr = rx_pcr;
|
||||
qos.rxtp.max_sdu = rx_sdu;
|
||||
qos.aal = ATM_AAL5;
|
||||
dprintk("parse_qos(): setting qos paramameters to tx=%d,%d rx=%d,%d\n",
|
||||
qos.txtp.max_pcr, qos.txtp.max_sdu,
|
||||
qos.rxtp.max_pcr, qos.rxtp.max_sdu);
|
||||
|
||||
atm_mpoa_add_qos(ipaddr, &qos);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* INITIALIZATION function - called when module is initialized/loaded.
|
||||
*/
|
||||
int mpc_proc_init(void)
|
||||
{
|
||||
struct proc_dir_entry *p;
|
||||
|
||||
p = proc_create(STAT_FILE_NAME, 0, atm_proc_root, &mpc_file_operations);
|
||||
if (!p) {
|
||||
pr_err("Unable to initialize /proc/atm/%s\n", STAT_FILE_NAME);
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* DELETING function - called when module is removed.
|
||||
*/
|
||||
void mpc_proc_clean(void)
|
||||
{
|
||||
remove_proc_entry(STAT_FILE_NAME, atm_proc_root);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
500
net/atm/pppoatm.c
Normal file
500
net/atm/pppoatm.c
Normal file
|
@ -0,0 +1,500 @@
|
|||
/* net/atm/pppoatm.c - RFC2364 PPP over ATM/AAL5 */
|
||||
|
||||
/* Copyright 1999-2000 by Mitchell Blank Jr */
|
||||
/* Based on clip.c; 1995-1999 by Werner Almesberger, EPFL LRC/ICA */
|
||||
/* And on ppp_async.c; Copyright 1999 Paul Mackerras */
|
||||
/* And help from Jens Axboe */
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This driver provides the encapsulation and framing for sending
|
||||
* and receiving PPP frames in ATM AAL5 PDUs.
|
||||
*/
|
||||
|
||||
/*
|
||||
* One shortcoming of this driver is that it does not comply with
|
||||
* section 8 of RFC2364 - we are supposed to detect a change
|
||||
* in encapsulation and immediately abort the connection (in order
|
||||
* to avoid a black-hole being created if our peer loses state
|
||||
* and changes encapsulation unilaterally. However, since the
|
||||
* ppp_generic layer actually does the decapsulation, we need
|
||||
* a way of notifying it when we _think_ there might be a problem)
|
||||
* There's two cases:
|
||||
* 1. LLC-encapsulation was missing when it was enabled. In
|
||||
* this case, we should tell the upper layer "tear down
|
||||
* this session if this skb looks ok to you"
|
||||
* 2. LLC-encapsulation was present when it was disabled. Then
|
||||
* we need to tell the upper layer "this packet may be
|
||||
* ok, but if its in error tear down the session"
|
||||
* These hooks are not yet available in ppp_generic
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/ppp_defs.h>
|
||||
#include <linux/ppp-ioctl.h>
|
||||
#include <linux/ppp_channel.h>
|
||||
#include <linux/atmppp.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
enum pppoatm_encaps {
|
||||
e_autodetect = PPPOATM_ENCAPS_AUTODETECT,
|
||||
e_vc = PPPOATM_ENCAPS_VC,
|
||||
e_llc = PPPOATM_ENCAPS_LLC,
|
||||
};
|
||||
|
||||
struct pppoatm_vcc {
|
||||
struct atm_vcc *atmvcc; /* VCC descriptor */
|
||||
void (*old_push)(struct atm_vcc *, struct sk_buff *);
|
||||
void (*old_pop)(struct atm_vcc *, struct sk_buff *);
|
||||
void (*old_release_cb)(struct atm_vcc *);
|
||||
struct module *old_owner;
|
||||
/* keep old push/pop for detaching */
|
||||
enum pppoatm_encaps encaps;
|
||||
atomic_t inflight;
|
||||
unsigned long blocked;
|
||||
int flags; /* SC_COMP_PROT - compress protocol */
|
||||
struct ppp_channel chan; /* interface to generic ppp layer */
|
||||
struct tasklet_struct wakeup_tasklet;
|
||||
};
|
||||
|
||||
/*
|
||||
* We want to allow two packets in the queue. The one that's currently in
|
||||
* flight, and *one* queued up ready for the ATM device to send immediately
|
||||
* from its TX done IRQ. We want to be able to use atomic_inc_not_zero(), so
|
||||
* inflight == -2 represents an empty queue, -1 one packet, and zero means
|
||||
* there are two packets in the queue.
|
||||
*/
|
||||
#define NONE_INFLIGHT -2
|
||||
|
||||
#define BLOCKED 0
|
||||
|
||||
/*
|
||||
* Header used for LLC Encapsulated PPP (4 bytes) followed by the LCP protocol
|
||||
* ID (0xC021) used in autodetection
|
||||
*/
|
||||
static const unsigned char pppllc[6] = { 0xFE, 0xFE, 0x03, 0xCF, 0xC0, 0x21 };
|
||||
#define LLC_LEN (4)
|
||||
|
||||
static inline struct pppoatm_vcc *atmvcc_to_pvcc(const struct atm_vcc *atmvcc)
|
||||
{
|
||||
return (struct pppoatm_vcc *) (atmvcc->user_back);
|
||||
}
|
||||
|
||||
static inline struct pppoatm_vcc *chan_to_pvcc(const struct ppp_channel *chan)
|
||||
{
|
||||
return (struct pppoatm_vcc *) (chan->private);
|
||||
}
|
||||
|
||||
/*
|
||||
* We can't do this directly from our _pop handler, since the ppp code
|
||||
* doesn't want to be called in interrupt context, so we do it from
|
||||
* a tasklet
|
||||
*/
|
||||
static void pppoatm_wakeup_sender(unsigned long arg)
|
||||
{
|
||||
ppp_output_wakeup((struct ppp_channel *) arg);
|
||||
}
|
||||
|
||||
static void pppoatm_release_cb(struct atm_vcc *atmvcc)
|
||||
{
|
||||
struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc);
|
||||
|
||||
/*
|
||||
* As in pppoatm_pop(), it's safe to clear the BLOCKED bit here because
|
||||
* the wakeup *can't* race with pppoatm_send(). They both hold the PPP
|
||||
* channel's ->downl lock. And the potential race with *setting* it,
|
||||
* which leads to the double-check dance in pppoatm_may_send(), doesn't
|
||||
* exist here. In the sock_owned_by_user() case in pppoatm_send(), we
|
||||
* set the BLOCKED bit while the socket is still locked. We know that
|
||||
* ->release_cb() can't be called until that's done.
|
||||
*/
|
||||
if (test_and_clear_bit(BLOCKED, &pvcc->blocked))
|
||||
tasklet_schedule(&pvcc->wakeup_tasklet);
|
||||
if (pvcc->old_release_cb)
|
||||
pvcc->old_release_cb(atmvcc);
|
||||
}
|
||||
/*
|
||||
* This gets called every time the ATM card has finished sending our
|
||||
* skb. The ->old_pop will take care up normal atm flow control,
|
||||
* but we also need to wake up the device if we blocked it
|
||||
*/
|
||||
static void pppoatm_pop(struct atm_vcc *atmvcc, struct sk_buff *skb)
|
||||
{
|
||||
struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc);
|
||||
|
||||
pvcc->old_pop(atmvcc, skb);
|
||||
atomic_dec(&pvcc->inflight);
|
||||
|
||||
/*
|
||||
* We always used to run the wakeup tasklet unconditionally here, for
|
||||
* fear of race conditions where we clear the BLOCKED flag just as we
|
||||
* refuse another packet in pppoatm_send(). This was quite inefficient.
|
||||
*
|
||||
* In fact it's OK. The PPP core will only ever call pppoatm_send()
|
||||
* while holding the channel->downl lock. And ppp_output_wakeup() as
|
||||
* called by the tasklet will *also* grab that lock. So even if another
|
||||
* CPU is in pppoatm_send() right now, the tasklet isn't going to race
|
||||
* with it. The wakeup *will* happen after the other CPU is safely out
|
||||
* of pppoatm_send() again.
|
||||
*
|
||||
* So if the CPU in pppoatm_send() has already set the BLOCKED bit and
|
||||
* it about to return, that's fine. We trigger a wakeup which will
|
||||
* happen later. And if the CPU in pppoatm_send() *hasn't* set the
|
||||
* BLOCKED bit yet, that's fine too because of the double check in
|
||||
* pppoatm_may_send() which is commented there.
|
||||
*/
|
||||
if (test_and_clear_bit(BLOCKED, &pvcc->blocked))
|
||||
tasklet_schedule(&pvcc->wakeup_tasklet);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unbind from PPP - currently we only do this when closing the socket,
|
||||
* but we could put this into an ioctl if need be
|
||||
*/
|
||||
static void pppoatm_unassign_vcc(struct atm_vcc *atmvcc)
|
||||
{
|
||||
struct pppoatm_vcc *pvcc;
|
||||
pvcc = atmvcc_to_pvcc(atmvcc);
|
||||
atmvcc->push = pvcc->old_push;
|
||||
atmvcc->pop = pvcc->old_pop;
|
||||
atmvcc->release_cb = pvcc->old_release_cb;
|
||||
tasklet_kill(&pvcc->wakeup_tasklet);
|
||||
ppp_unregister_channel(&pvcc->chan);
|
||||
atmvcc->user_back = NULL;
|
||||
kfree(pvcc);
|
||||
}
|
||||
|
||||
/* Called when an AAL5 PDU comes in */
|
||||
static void pppoatm_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
|
||||
{
|
||||
struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc);
|
||||
pr_debug("\n");
|
||||
if (skb == NULL) { /* VCC was closed */
|
||||
struct module *module;
|
||||
|
||||
pr_debug("removing ATMPPP VCC %p\n", pvcc);
|
||||
module = pvcc->old_owner;
|
||||
pppoatm_unassign_vcc(atmvcc);
|
||||
atmvcc->push(atmvcc, NULL); /* Pass along bad news */
|
||||
module_put(module);
|
||||
return;
|
||||
}
|
||||
atm_return(atmvcc, skb->truesize);
|
||||
switch (pvcc->encaps) {
|
||||
case e_llc:
|
||||
if (skb->len < LLC_LEN ||
|
||||
memcmp(skb->data, pppllc, LLC_LEN))
|
||||
goto error;
|
||||
skb_pull(skb, LLC_LEN);
|
||||
break;
|
||||
case e_autodetect:
|
||||
if (pvcc->chan.ppp == NULL) { /* Not bound yet! */
|
||||
kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
if (skb->len >= sizeof(pppllc) &&
|
||||
!memcmp(skb->data, pppllc, sizeof(pppllc))) {
|
||||
pvcc->encaps = e_llc;
|
||||
skb_pull(skb, LLC_LEN);
|
||||
break;
|
||||
}
|
||||
if (skb->len >= (sizeof(pppllc) - LLC_LEN) &&
|
||||
!memcmp(skb->data, &pppllc[LLC_LEN],
|
||||
sizeof(pppllc) - LLC_LEN)) {
|
||||
pvcc->encaps = e_vc;
|
||||
pvcc->chan.mtu += LLC_LEN;
|
||||
break;
|
||||
}
|
||||
pr_debug("Couldn't autodetect yet (skb: %02X %02X %02X %02X %02X %02X)\n",
|
||||
skb->data[0], skb->data[1], skb->data[2],
|
||||
skb->data[3], skb->data[4], skb->data[5]);
|
||||
goto error;
|
||||
case e_vc:
|
||||
break;
|
||||
}
|
||||
ppp_input(&pvcc->chan, skb);
|
||||
return;
|
||||
|
||||
error:
|
||||
kfree_skb(skb);
|
||||
ppp_input_error(&pvcc->chan, 0);
|
||||
}
|
||||
|
||||
static int pppoatm_may_send(struct pppoatm_vcc *pvcc, int size)
|
||||
{
|
||||
/*
|
||||
* It's not clear that we need to bother with using atm_may_send()
|
||||
* to check we don't exceed sk->sk_sndbuf. If userspace sets a
|
||||
* value of sk_sndbuf which is lower than the MTU, we're going to
|
||||
* block for ever. But the code always did that before we introduced
|
||||
* the packet count limit, so...
|
||||
*/
|
||||
if (atm_may_send(pvcc->atmvcc, size) &&
|
||||
atomic_inc_not_zero_hint(&pvcc->inflight, NONE_INFLIGHT))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* We use test_and_set_bit() rather than set_bit() here because
|
||||
* we need to ensure there's a memory barrier after it. The bit
|
||||
* *must* be set before we do the atomic_inc() on pvcc->inflight.
|
||||
* There's no smp_mb__after_set_bit(), so it's this or abuse
|
||||
* smp_mb__after_clear_bit().
|
||||
*/
|
||||
test_and_set_bit(BLOCKED, &pvcc->blocked);
|
||||
|
||||
/*
|
||||
* We may have raced with pppoatm_pop(). If it ran for the
|
||||
* last packet in the queue, *just* before we set the BLOCKED
|
||||
* bit, then it might never run again and the channel could
|
||||
* remain permanently blocked. Cope with that race by checking
|
||||
* *again*. If it did run in that window, we'll have space on
|
||||
* the queue now and can return success. It's harmless to leave
|
||||
* the BLOCKED flag set, since it's only used as a trigger to
|
||||
* run the wakeup tasklet. Another wakeup will never hurt.
|
||||
* If pppoatm_pop() is running but hasn't got as far as making
|
||||
* space on the queue yet, then it hasn't checked the BLOCKED
|
||||
* flag yet either, so we're safe in that case too. It'll issue
|
||||
* an "immediate" wakeup... where "immediate" actually involves
|
||||
* taking the PPP channel's ->downl lock, which is held by the
|
||||
* code path that calls pppoatm_send(), and is thus going to
|
||||
* wait for us to finish.
|
||||
*/
|
||||
if (atm_may_send(pvcc->atmvcc, size) &&
|
||||
atomic_inc_not_zero(&pvcc->inflight))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* Called by the ppp_generic.c to send a packet - returns true if packet
|
||||
* was accepted. If we return false, then it's our job to call
|
||||
* ppp_output_wakeup(chan) when we're feeling more up to it.
|
||||
* Note that in the ENOMEM case (as opposed to the !atm_may_send case)
|
||||
* we should really drop the packet, but the generic layer doesn't
|
||||
* support this yet. We just return 'DROP_PACKET' which we actually define
|
||||
* as success, just to be clear what we're really doing.
|
||||
*/
|
||||
#define DROP_PACKET 1
|
||||
static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb)
|
||||
{
|
||||
struct pppoatm_vcc *pvcc = chan_to_pvcc(chan);
|
||||
struct atm_vcc *vcc;
|
||||
int ret;
|
||||
|
||||
ATM_SKB(skb)->vcc = pvcc->atmvcc;
|
||||
pr_debug("(skb=0x%p, vcc=0x%p)\n", skb, pvcc->atmvcc);
|
||||
if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT))
|
||||
(void) skb_pull(skb, 1);
|
||||
|
||||
vcc = ATM_SKB(skb)->vcc;
|
||||
bh_lock_sock(sk_atm(vcc));
|
||||
if (sock_owned_by_user(sk_atm(vcc))) {
|
||||
/*
|
||||
* Needs to happen (and be flushed, hence test_and_) before we unlock
|
||||
* the socket. It needs to be seen by the time our ->release_cb gets
|
||||
* called.
|
||||
*/
|
||||
test_and_set_bit(BLOCKED, &pvcc->blocked);
|
||||
goto nospace;
|
||||
}
|
||||
if (test_bit(ATM_VF_RELEASED, &vcc->flags) ||
|
||||
test_bit(ATM_VF_CLOSE, &vcc->flags) ||
|
||||
!test_bit(ATM_VF_READY, &vcc->flags)) {
|
||||
bh_unlock_sock(sk_atm(vcc));
|
||||
kfree_skb(skb);
|
||||
return DROP_PACKET;
|
||||
}
|
||||
|
||||
switch (pvcc->encaps) { /* LLC encapsulation needed */
|
||||
case e_llc:
|
||||
if (skb_headroom(skb) < LLC_LEN) {
|
||||
struct sk_buff *n;
|
||||
n = skb_realloc_headroom(skb, LLC_LEN);
|
||||
if (n != NULL &&
|
||||
!pppoatm_may_send(pvcc, n->truesize)) {
|
||||
kfree_skb(n);
|
||||
goto nospace;
|
||||
}
|
||||
consume_skb(skb);
|
||||
skb = n;
|
||||
if (skb == NULL) {
|
||||
bh_unlock_sock(sk_atm(vcc));
|
||||
return DROP_PACKET;
|
||||
}
|
||||
} else if (!pppoatm_may_send(pvcc, skb->truesize))
|
||||
goto nospace;
|
||||
memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN);
|
||||
break;
|
||||
case e_vc:
|
||||
if (!pppoatm_may_send(pvcc, skb->truesize))
|
||||
goto nospace;
|
||||
break;
|
||||
case e_autodetect:
|
||||
bh_unlock_sock(sk_atm(vcc));
|
||||
pr_debug("Trying to send without setting encaps!\n");
|
||||
kfree_skb(skb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
atomic_add(skb->truesize, &sk_atm(ATM_SKB(skb)->vcc)->sk_wmem_alloc);
|
||||
ATM_SKB(skb)->atm_options = ATM_SKB(skb)->vcc->atm_options;
|
||||
pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n",
|
||||
skb, ATM_SKB(skb)->vcc, ATM_SKB(skb)->vcc->dev);
|
||||
ret = ATM_SKB(skb)->vcc->send(ATM_SKB(skb)->vcc, skb)
|
||||
? DROP_PACKET : 1;
|
||||
bh_unlock_sock(sk_atm(vcc));
|
||||
return ret;
|
||||
nospace:
|
||||
bh_unlock_sock(sk_atm(vcc));
|
||||
/*
|
||||
* We don't have space to send this SKB now, but we might have
|
||||
* already applied SC_COMP_PROT compression, so may need to undo
|
||||
*/
|
||||
if ((pvcc->flags & SC_COMP_PROT) && skb_headroom(skb) > 0 &&
|
||||
skb->data[-1] == '\0')
|
||||
(void) skb_push(skb, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This handles ioctls sent to the /dev/ppp interface */
|
||||
static int pppoatm_devppp_ioctl(struct ppp_channel *chan, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case PPPIOCGFLAGS:
|
||||
return put_user(chan_to_pvcc(chan)->flags, (int __user *) arg)
|
||||
? -EFAULT : 0;
|
||||
case PPPIOCSFLAGS:
|
||||
return get_user(chan_to_pvcc(chan)->flags, (int __user *) arg)
|
||||
? -EFAULT : 0;
|
||||
}
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
static const struct ppp_channel_ops pppoatm_ops = {
|
||||
.start_xmit = pppoatm_send,
|
||||
.ioctl = pppoatm_devppp_ioctl,
|
||||
};
|
||||
|
||||
static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, void __user *arg)
|
||||
{
|
||||
struct atm_backend_ppp be;
|
||||
struct pppoatm_vcc *pvcc;
|
||||
int err;
|
||||
/*
|
||||
* Each PPPoATM instance has its own tasklet - this is just a
|
||||
* prototypical one used to initialize them
|
||||
*/
|
||||
static const DECLARE_TASKLET(tasklet_proto, pppoatm_wakeup_sender, 0);
|
||||
if (copy_from_user(&be, arg, sizeof be))
|
||||
return -EFAULT;
|
||||
if (be.encaps != PPPOATM_ENCAPS_AUTODETECT &&
|
||||
be.encaps != PPPOATM_ENCAPS_VC && be.encaps != PPPOATM_ENCAPS_LLC)
|
||||
return -EINVAL;
|
||||
pvcc = kzalloc(sizeof(*pvcc), GFP_KERNEL);
|
||||
if (pvcc == NULL)
|
||||
return -ENOMEM;
|
||||
pvcc->atmvcc = atmvcc;
|
||||
|
||||
/* Maximum is zero, so that we can use atomic_inc_not_zero() */
|
||||
atomic_set(&pvcc->inflight, NONE_INFLIGHT);
|
||||
pvcc->old_push = atmvcc->push;
|
||||
pvcc->old_pop = atmvcc->pop;
|
||||
pvcc->old_owner = atmvcc->owner;
|
||||
pvcc->old_release_cb = atmvcc->release_cb;
|
||||
pvcc->encaps = (enum pppoatm_encaps) be.encaps;
|
||||
pvcc->chan.private = pvcc;
|
||||
pvcc->chan.ops = &pppoatm_ops;
|
||||
pvcc->chan.mtu = atmvcc->qos.txtp.max_sdu - PPP_HDRLEN -
|
||||
(be.encaps == e_vc ? 0 : LLC_LEN);
|
||||
pvcc->wakeup_tasklet = tasklet_proto;
|
||||
pvcc->wakeup_tasklet.data = (unsigned long) &pvcc->chan;
|
||||
err = ppp_register_channel(&pvcc->chan);
|
||||
if (err != 0) {
|
||||
kfree(pvcc);
|
||||
return err;
|
||||
}
|
||||
atmvcc->user_back = pvcc;
|
||||
atmvcc->push = pppoatm_push;
|
||||
atmvcc->pop = pppoatm_pop;
|
||||
atmvcc->release_cb = pppoatm_release_cb;
|
||||
__module_get(THIS_MODULE);
|
||||
atmvcc->owner = THIS_MODULE;
|
||||
|
||||
/* re-process everything received between connection setup and
|
||||
backend setup */
|
||||
vcc_process_recv_queue(atmvcc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This handles ioctls actually performed on our vcc - we must return
|
||||
* -ENOIOCTLCMD for any unrecognized ioctl
|
||||
*/
|
||||
static int pppoatm_ioctl(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct atm_vcc *atmvcc = ATM_SD(sock);
|
||||
void __user *argp = (void __user *)arg;
|
||||
|
||||
if (cmd != ATM_SETBACKEND && atmvcc->push != pppoatm_push)
|
||||
return -ENOIOCTLCMD;
|
||||
switch (cmd) {
|
||||
case ATM_SETBACKEND: {
|
||||
atm_backend_t b;
|
||||
if (get_user(b, (atm_backend_t __user *) argp))
|
||||
return -EFAULT;
|
||||
if (b != ATM_BACKEND_PPP)
|
||||
return -ENOIOCTLCMD;
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
if (sock->state != SS_CONNECTED)
|
||||
return -EINVAL;
|
||||
return pppoatm_assign_vcc(atmvcc, argp);
|
||||
}
|
||||
case PPPIOCGCHAN:
|
||||
return put_user(ppp_channel_index(&atmvcc_to_pvcc(atmvcc)->
|
||||
chan), (int __user *) argp) ? -EFAULT : 0;
|
||||
case PPPIOCGUNIT:
|
||||
return put_user(ppp_unit_number(&atmvcc_to_pvcc(atmvcc)->
|
||||
chan), (int __user *) argp) ? -EFAULT : 0;
|
||||
}
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
static struct atm_ioctl pppoatm_ioctl_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.ioctl = pppoatm_ioctl,
|
||||
};
|
||||
|
||||
static int __init pppoatm_init(void)
|
||||
{
|
||||
register_atm_ioctl(&pppoatm_ioctl_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit pppoatm_exit(void)
|
||||
{
|
||||
deregister_atm_ioctl(&pppoatm_ioctl_ops);
|
||||
}
|
||||
|
||||
module_init(pppoatm_init);
|
||||
module_exit(pppoatm_exit);
|
||||
|
||||
MODULE_AUTHOR("Mitchell Blank Jr <mitch@sfgoth.com>");
|
||||
MODULE_DESCRIPTION("RFC2364 PPP over ATM/AAL5");
|
||||
MODULE_LICENSE("GPL");
|
497
net/atm/proc.c
Normal file
497
net/atm/proc.c
Normal file
|
@ -0,0 +1,497 @@
|
|||
/* net/atm/proc.c - ATM /proc interface
|
||||
*
|
||||
* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA
|
||||
*
|
||||
* seq_file api usage by romieu@fr.zoreil.com
|
||||
*
|
||||
* Evaluating the efficiency of the whole thing if left as an exercise to
|
||||
* the reader.
|
||||
*/
|
||||
|
||||
#include <linux/module.h> /* for EXPORT_SYMBOL */
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/atmclip.h>
|
||||
#include <linux/init.h> /* for __init */
|
||||
#include <linux/slab.h>
|
||||
#include <net/net_namespace.h>
|
||||
#include <net/atmclip.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/param.h> /* for HZ */
|
||||
#include <linux/atomic.h>
|
||||
#include "resources.h"
|
||||
#include "common.h" /* atm_proc_init prototype */
|
||||
#include "signaling.h" /* to get sigd - ugly too */
|
||||
|
||||
static ssize_t proc_dev_atm_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *pos);
|
||||
|
||||
static const struct file_operations proc_atm_dev_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.read = proc_dev_atm_read,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
static void add_stats(struct seq_file *seq, const char *aal,
|
||||
const struct k_atm_aal_stats *stats)
|
||||
{
|
||||
seq_printf(seq, "%s ( %d %d %d %d %d )", aal,
|
||||
atomic_read(&stats->tx), atomic_read(&stats->tx_err),
|
||||
atomic_read(&stats->rx), atomic_read(&stats->rx_err),
|
||||
atomic_read(&stats->rx_drop));
|
||||
}
|
||||
|
||||
static void atm_dev_info(struct seq_file *seq, const struct atm_dev *dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
seq_printf(seq, "%3d %-8s", dev->number, dev->type);
|
||||
for (i = 0; i < ESI_LEN; i++)
|
||||
seq_printf(seq, "%02x", dev->esi[i]);
|
||||
seq_puts(seq, " ");
|
||||
add_stats(seq, "0", &dev->stats.aal0);
|
||||
seq_puts(seq, " ");
|
||||
add_stats(seq, "5", &dev->stats.aal5);
|
||||
seq_printf(seq, "\t[%d]", atomic_read(&dev->refcnt));
|
||||
seq_putc(seq, '\n');
|
||||
}
|
||||
|
||||
struct vcc_state {
|
||||
int bucket;
|
||||
struct sock *sk;
|
||||
int family;
|
||||
};
|
||||
|
||||
static inline int compare_family(struct sock *sk, int family)
|
||||
{
|
||||
return !family || (sk->sk_family == family);
|
||||
}
|
||||
|
||||
static int __vcc_walk(struct sock **sock, int family, int *bucket, loff_t l)
|
||||
{
|
||||
struct sock *sk = *sock;
|
||||
|
||||
if (sk == SEQ_START_TOKEN) {
|
||||
for (*bucket = 0; *bucket < VCC_HTABLE_SIZE; ++*bucket) {
|
||||
struct hlist_head *head = &vcc_hash[*bucket];
|
||||
|
||||
sk = hlist_empty(head) ? NULL : __sk_head(head);
|
||||
if (sk)
|
||||
break;
|
||||
}
|
||||
l--;
|
||||
}
|
||||
try_again:
|
||||
for (; sk; sk = sk_next(sk)) {
|
||||
l -= compare_family(sk, family);
|
||||
if (l < 0)
|
||||
goto out;
|
||||
}
|
||||
if (!sk && ++*bucket < VCC_HTABLE_SIZE) {
|
||||
sk = sk_head(&vcc_hash[*bucket]);
|
||||
goto try_again;
|
||||
}
|
||||
sk = SEQ_START_TOKEN;
|
||||
out:
|
||||
*sock = sk;
|
||||
return (l < 0);
|
||||
}
|
||||
|
||||
static inline void *vcc_walk(struct vcc_state *state, loff_t l)
|
||||
{
|
||||
return __vcc_walk(&state->sk, state->family, &state->bucket, l) ?
|
||||
state : NULL;
|
||||
}
|
||||
|
||||
static int __vcc_seq_open(struct inode *inode, struct file *file,
|
||||
int family, const struct seq_operations *ops)
|
||||
{
|
||||
struct vcc_state *state;
|
||||
|
||||
state = __seq_open_private(file, ops, sizeof(*state));
|
||||
if (state == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
state->family = family;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *vcc_seq_start(struct seq_file *seq, loff_t *pos)
|
||||
__acquires(vcc_sklist_lock)
|
||||
{
|
||||
struct vcc_state *state = seq->private;
|
||||
loff_t left = *pos;
|
||||
|
||||
read_lock(&vcc_sklist_lock);
|
||||
state->sk = SEQ_START_TOKEN;
|
||||
return left ? vcc_walk(state, left) : SEQ_START_TOKEN;
|
||||
}
|
||||
|
||||
static void vcc_seq_stop(struct seq_file *seq, void *v)
|
||||
__releases(vcc_sklist_lock)
|
||||
{
|
||||
read_unlock(&vcc_sklist_lock);
|
||||
}
|
||||
|
||||
static void *vcc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
||||
{
|
||||
struct vcc_state *state = seq->private;
|
||||
|
||||
v = vcc_walk(state, 1);
|
||||
*pos += !!PTR_ERR(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void pvc_info(struct seq_file *seq, struct atm_vcc *vcc)
|
||||
{
|
||||
static const char *const class_name[] = {
|
||||
"off", "UBR", "CBR", "VBR", "ABR"};
|
||||
static const char *const aal_name[] = {
|
||||
"---", "1", "2", "3/4", /* 0- 3 */
|
||||
"???", "5", "???", "???", /* 4- 7 */
|
||||
"???", "???", "???", "???", /* 8-11 */
|
||||
"???", "0", "???", "???"}; /* 12-15 */
|
||||
|
||||
seq_printf(seq, "%3d %3d %5d %-3s %7d %-5s %7d %-6s",
|
||||
vcc->dev->number, vcc->vpi, vcc->vci,
|
||||
vcc->qos.aal >= ARRAY_SIZE(aal_name) ? "err" :
|
||||
aal_name[vcc->qos.aal], vcc->qos.rxtp.min_pcr,
|
||||
class_name[vcc->qos.rxtp.traffic_class],
|
||||
vcc->qos.txtp.min_pcr,
|
||||
class_name[vcc->qos.txtp.traffic_class]);
|
||||
if (test_bit(ATM_VF_IS_CLIP, &vcc->flags)) {
|
||||
struct clip_vcc *clip_vcc = CLIP_VCC(vcc);
|
||||
struct net_device *dev;
|
||||
|
||||
dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : NULL;
|
||||
seq_printf(seq, "CLIP, Itf:%s, Encap:",
|
||||
dev ? dev->name : "none?");
|
||||
seq_printf(seq, "%s", clip_vcc->encap ? "LLC/SNAP" : "None");
|
||||
}
|
||||
seq_putc(seq, '\n');
|
||||
}
|
||||
|
||||
static const char *vcc_state(struct atm_vcc *vcc)
|
||||
{
|
||||
static const char *const map[] = { ATM_VS2TXT_MAP };
|
||||
|
||||
return map[ATM_VF2VS(vcc->flags)];
|
||||
}
|
||||
|
||||
static void vcc_info(struct seq_file *seq, struct atm_vcc *vcc)
|
||||
{
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
|
||||
seq_printf(seq, "%pK ", vcc);
|
||||
if (!vcc->dev)
|
||||
seq_printf(seq, "Unassigned ");
|
||||
else
|
||||
seq_printf(seq, "%3d %3d %5d ", vcc->dev->number, vcc->vpi,
|
||||
vcc->vci);
|
||||
switch (sk->sk_family) {
|
||||
case AF_ATMPVC:
|
||||
seq_printf(seq, "PVC");
|
||||
break;
|
||||
case AF_ATMSVC:
|
||||
seq_printf(seq, "SVC");
|
||||
break;
|
||||
default:
|
||||
seq_printf(seq, "%3d", sk->sk_family);
|
||||
}
|
||||
seq_printf(seq, " %04lx %5d %7d/%7d %7d/%7d [%d]\n",
|
||||
vcc->flags, sk->sk_err,
|
||||
sk_wmem_alloc_get(sk), sk->sk_sndbuf,
|
||||
sk_rmem_alloc_get(sk), sk->sk_rcvbuf,
|
||||
atomic_read(&sk->sk_refcnt));
|
||||
}
|
||||
|
||||
static void svc_info(struct seq_file *seq, struct atm_vcc *vcc)
|
||||
{
|
||||
if (!vcc->dev)
|
||||
seq_printf(seq, sizeof(void *) == 4 ?
|
||||
"N/A@%pK%10s" : "N/A@%pK%2s", vcc, "");
|
||||
else
|
||||
seq_printf(seq, "%3d %3d %5d ",
|
||||
vcc->dev->number, vcc->vpi, vcc->vci);
|
||||
seq_printf(seq, "%-10s ", vcc_state(vcc));
|
||||
seq_printf(seq, "%s%s", vcc->remote.sas_addr.pub,
|
||||
*vcc->remote.sas_addr.pub && *vcc->remote.sas_addr.prv ? "+" : "");
|
||||
if (*vcc->remote.sas_addr.prv) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ATM_ESA_LEN; i++)
|
||||
seq_printf(seq, "%02x", vcc->remote.sas_addr.prv[i]);
|
||||
}
|
||||
seq_putc(seq, '\n');
|
||||
}
|
||||
|
||||
static int atm_dev_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
static char atm_dev_banner[] =
|
||||
"Itf Type ESI/\"MAC\"addr "
|
||||
"AAL(TX,err,RX,err,drop) ... [refcnt]\n";
|
||||
|
||||
if (v == &atm_devs)
|
||||
seq_puts(seq, atm_dev_banner);
|
||||
else {
|
||||
struct atm_dev *dev = list_entry(v, struct atm_dev, dev_list);
|
||||
|
||||
atm_dev_info(seq, dev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations atm_dev_seq_ops = {
|
||||
.start = atm_dev_seq_start,
|
||||
.next = atm_dev_seq_next,
|
||||
.stop = atm_dev_seq_stop,
|
||||
.show = atm_dev_seq_show,
|
||||
};
|
||||
|
||||
static int atm_dev_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_open(file, &atm_dev_seq_ops);
|
||||
}
|
||||
|
||||
static const struct file_operations devices_seq_fops = {
|
||||
.open = atm_dev_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
static int pvc_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
static char atm_pvc_banner[] =
|
||||
"Itf VPI VCI AAL RX(PCR,Class) TX(PCR,Class)\n";
|
||||
|
||||
if (v == SEQ_START_TOKEN)
|
||||
seq_puts(seq, atm_pvc_banner);
|
||||
else {
|
||||
struct vcc_state *state = seq->private;
|
||||
struct atm_vcc *vcc = atm_sk(state->sk);
|
||||
|
||||
pvc_info(seq, vcc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations pvc_seq_ops = {
|
||||
.start = vcc_seq_start,
|
||||
.next = vcc_seq_next,
|
||||
.stop = vcc_seq_stop,
|
||||
.show = pvc_seq_show,
|
||||
};
|
||||
|
||||
static int pvc_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return __vcc_seq_open(inode, file, PF_ATMPVC, &pvc_seq_ops);
|
||||
}
|
||||
|
||||
static const struct file_operations pvc_seq_fops = {
|
||||
.open = pvc_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release_private,
|
||||
};
|
||||
|
||||
static int vcc_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
if (v == SEQ_START_TOKEN) {
|
||||
seq_printf(seq, sizeof(void *) == 4 ? "%-8s%s" : "%-16s%s",
|
||||
"Address ", "Itf VPI VCI Fam Flags Reply "
|
||||
"Send buffer Recv buffer [refcnt]\n");
|
||||
} else {
|
||||
struct vcc_state *state = seq->private;
|
||||
struct atm_vcc *vcc = atm_sk(state->sk);
|
||||
|
||||
vcc_info(seq, vcc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations vcc_seq_ops = {
|
||||
.start = vcc_seq_start,
|
||||
.next = vcc_seq_next,
|
||||
.stop = vcc_seq_stop,
|
||||
.show = vcc_seq_show,
|
||||
};
|
||||
|
||||
static int vcc_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return __vcc_seq_open(inode, file, 0, &vcc_seq_ops);
|
||||
}
|
||||
|
||||
static const struct file_operations vcc_seq_fops = {
|
||||
.open = vcc_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release_private,
|
||||
};
|
||||
|
||||
static int svc_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
static const char atm_svc_banner[] =
|
||||
"Itf VPI VCI State Remote\n";
|
||||
|
||||
if (v == SEQ_START_TOKEN)
|
||||
seq_puts(seq, atm_svc_banner);
|
||||
else {
|
||||
struct vcc_state *state = seq->private;
|
||||
struct atm_vcc *vcc = atm_sk(state->sk);
|
||||
|
||||
svc_info(seq, vcc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations svc_seq_ops = {
|
||||
.start = vcc_seq_start,
|
||||
.next = vcc_seq_next,
|
||||
.stop = vcc_seq_stop,
|
||||
.show = svc_seq_show,
|
||||
};
|
||||
|
||||
static int svc_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return __vcc_seq_open(inode, file, PF_ATMSVC, &svc_seq_ops);
|
||||
}
|
||||
|
||||
static const struct file_operations svc_seq_fops = {
|
||||
.open = svc_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release_private,
|
||||
};
|
||||
|
||||
static ssize_t proc_dev_atm_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
struct atm_dev *dev;
|
||||
unsigned long page;
|
||||
int length;
|
||||
|
||||
if (count == 0)
|
||||
return 0;
|
||||
page = get_zeroed_page(GFP_KERNEL);
|
||||
if (!page)
|
||||
return -ENOMEM;
|
||||
dev = PDE_DATA(file_inode(file));
|
||||
if (!dev->ops->proc_read)
|
||||
length = -EINVAL;
|
||||
else {
|
||||
length = dev->ops->proc_read(dev, pos, (char *)page);
|
||||
if (length > count)
|
||||
length = -EINVAL;
|
||||
}
|
||||
if (length >= 0) {
|
||||
if (copy_to_user(buf, (char *)page, length))
|
||||
length = -EFAULT;
|
||||
(*pos)++;
|
||||
}
|
||||
free_page(page);
|
||||
return length;
|
||||
}
|
||||
|
||||
struct proc_dir_entry *atm_proc_root;
|
||||
EXPORT_SYMBOL(atm_proc_root);
|
||||
|
||||
|
||||
int atm_proc_dev_register(struct atm_dev *dev)
|
||||
{
|
||||
int error;
|
||||
|
||||
/* No proc info */
|
||||
if (!dev->ops->proc_read)
|
||||
return 0;
|
||||
|
||||
error = -ENOMEM;
|
||||
dev->proc_name = kasprintf(GFP_KERNEL, "%s:%d", dev->type, dev->number);
|
||||
if (!dev->proc_name)
|
||||
goto err_out;
|
||||
|
||||
dev->proc_entry = proc_create_data(dev->proc_name, 0, atm_proc_root,
|
||||
&proc_atm_dev_ops, dev);
|
||||
if (!dev->proc_entry)
|
||||
goto err_free_name;
|
||||
return 0;
|
||||
|
||||
err_free_name:
|
||||
kfree(dev->proc_name);
|
||||
err_out:
|
||||
return error;
|
||||
}
|
||||
|
||||
void atm_proc_dev_deregister(struct atm_dev *dev)
|
||||
{
|
||||
if (!dev->ops->proc_read)
|
||||
return;
|
||||
|
||||
remove_proc_entry(dev->proc_name, atm_proc_root);
|
||||
kfree(dev->proc_name);
|
||||
}
|
||||
|
||||
static struct atm_proc_entry {
|
||||
char *name;
|
||||
const struct file_operations *proc_fops;
|
||||
struct proc_dir_entry *dirent;
|
||||
} atm_proc_ents[] = {
|
||||
{ .name = "devices", .proc_fops = &devices_seq_fops },
|
||||
{ .name = "pvc", .proc_fops = &pvc_seq_fops },
|
||||
{ .name = "svc", .proc_fops = &svc_seq_fops },
|
||||
{ .name = "vc", .proc_fops = &vcc_seq_fops },
|
||||
{ .name = NULL, .proc_fops = NULL }
|
||||
};
|
||||
|
||||
static void atm_proc_dirs_remove(void)
|
||||
{
|
||||
static struct atm_proc_entry *e;
|
||||
|
||||
for (e = atm_proc_ents; e->name; e++) {
|
||||
if (e->dirent)
|
||||
remove_proc_entry(e->name, atm_proc_root);
|
||||
}
|
||||
remove_proc_entry("atm", init_net.proc_net);
|
||||
}
|
||||
|
||||
int __init atm_proc_init(void)
|
||||
{
|
||||
static struct atm_proc_entry *e;
|
||||
int ret;
|
||||
|
||||
atm_proc_root = proc_net_mkdir(&init_net, "atm", init_net.proc_net);
|
||||
if (!atm_proc_root)
|
||||
goto err_out;
|
||||
for (e = atm_proc_ents; e->name; e++) {
|
||||
struct proc_dir_entry *dirent;
|
||||
|
||||
dirent = proc_create(e->name, S_IRUGO,
|
||||
atm_proc_root, e->proc_fops);
|
||||
if (!dirent)
|
||||
goto err_out_remove;
|
||||
e->dirent = dirent;
|
||||
}
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
|
||||
err_out_remove:
|
||||
atm_proc_dirs_remove();
|
||||
err_out:
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
void atm_proc_exit(void)
|
||||
{
|
||||
atm_proc_dirs_remove();
|
||||
}
|
13
net/atm/protocols.h
Normal file
13
net/atm/protocols.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
/* net/atm/protocols.h - ATM protocol handler entry points */
|
||||
|
||||
/* Written 1995-1997 by Werner Almesberger, EPFL LRC */
|
||||
|
||||
|
||||
#ifndef NET_ATM_PROTOCOLS_H
|
||||
#define NET_ATM_PROTOCOLS_H
|
||||
|
||||
int atm_init_aal0(struct atm_vcc *vcc); /* "raw" AAL0 */
|
||||
int atm_init_aal34(struct atm_vcc *vcc);/* "raw" AAL3/4 transport */
|
||||
int atm_init_aal5(struct atm_vcc *vcc); /* "raw" AAL5 transport */
|
||||
|
||||
#endif
|
162
net/atm/pvc.c
Normal file
162
net/atm/pvc.c
Normal file
|
@ -0,0 +1,162 @@
|
|||
/* net/atm/pvc.c - ATM PVC sockets */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#include <linux/net.h> /* struct socket, struct proto_ops */
|
||||
#include <linux/atm.h> /* ATM stuff */
|
||||
#include <linux/atmdev.h> /* ATM devices */
|
||||
#include <linux/errno.h> /* error codes */
|
||||
#include <linux/kernel.h> /* printk */
|
||||
#include <linux/init.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/export.h>
|
||||
#include <net/sock.h> /* for sock_no_* */
|
||||
|
||||
#include "resources.h" /* devs and vccs */
|
||||
#include "common.h" /* common for PVCs and SVCs */
|
||||
|
||||
|
||||
static int pvc_shutdown(struct socket *sock, int how)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pvc_bind(struct socket *sock, struct sockaddr *sockaddr,
|
||||
int sockaddr_len)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct sockaddr_atmpvc *addr;
|
||||
struct atm_vcc *vcc;
|
||||
int error;
|
||||
|
||||
if (sockaddr_len != sizeof(struct sockaddr_atmpvc))
|
||||
return -EINVAL;
|
||||
addr = (struct sockaddr_atmpvc *)sockaddr;
|
||||
if (addr->sap_family != AF_ATMPVC)
|
||||
return -EAFNOSUPPORT;
|
||||
lock_sock(sk);
|
||||
vcc = ATM_SD(sock);
|
||||
if (!test_bit(ATM_VF_HASQOS, &vcc->flags)) {
|
||||
error = -EBADFD;
|
||||
goto out;
|
||||
}
|
||||
if (test_bit(ATM_VF_PARTIAL, &vcc->flags)) {
|
||||
if (vcc->vpi != ATM_VPI_UNSPEC)
|
||||
addr->sap_addr.vpi = vcc->vpi;
|
||||
if (vcc->vci != ATM_VCI_UNSPEC)
|
||||
addr->sap_addr.vci = vcc->vci;
|
||||
}
|
||||
error = vcc_connect(sock, addr->sap_addr.itf, addr->sap_addr.vpi,
|
||||
addr->sap_addr.vci);
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int pvc_connect(struct socket *sock, struct sockaddr *sockaddr,
|
||||
int sockaddr_len, int flags)
|
||||
{
|
||||
return pvc_bind(sock, sockaddr, sockaddr_len);
|
||||
}
|
||||
|
||||
static int pvc_setsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, unsigned int optlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
int error;
|
||||
|
||||
lock_sock(sk);
|
||||
error = vcc_setsockopt(sock, level, optname, optval, optlen);
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int pvc_getsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
int error;
|
||||
|
||||
lock_sock(sk);
|
||||
error = vcc_getsockopt(sock, level, optname, optval, optlen);
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int pvc_getname(struct socket *sock, struct sockaddr *sockaddr,
|
||||
int *sockaddr_len, int peer)
|
||||
{
|
||||
struct sockaddr_atmpvc *addr;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
|
||||
if (!vcc->dev || !test_bit(ATM_VF_ADDR, &vcc->flags))
|
||||
return -ENOTCONN;
|
||||
*sockaddr_len = sizeof(struct sockaddr_atmpvc);
|
||||
addr = (struct sockaddr_atmpvc *)sockaddr;
|
||||
memset(addr, 0, sizeof(*addr));
|
||||
addr->sap_family = AF_ATMPVC;
|
||||
addr->sap_addr.itf = vcc->dev->number;
|
||||
addr->sap_addr.vpi = vcc->vpi;
|
||||
addr->sap_addr.vci = vcc->vci;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct proto_ops pvc_proto_ops = {
|
||||
.family = PF_ATMPVC,
|
||||
.owner = THIS_MODULE,
|
||||
|
||||
.release = vcc_release,
|
||||
.bind = pvc_bind,
|
||||
.connect = pvc_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = pvc_getname,
|
||||
.poll = vcc_poll,
|
||||
.ioctl = vcc_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = vcc_compat_ioctl,
|
||||
#endif
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = pvc_shutdown,
|
||||
.setsockopt = pvc_setsockopt,
|
||||
.getsockopt = pvc_getsockopt,
|
||||
.sendmsg = vcc_sendmsg,
|
||||
.recvmsg = vcc_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
.sendpage = sock_no_sendpage,
|
||||
};
|
||||
|
||||
|
||||
static int pvc_create(struct net *net, struct socket *sock, int protocol,
|
||||
int kern)
|
||||
{
|
||||
if (net != &init_net)
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
sock->ops = &pvc_proto_ops;
|
||||
return vcc_create(net, sock, protocol, PF_ATMPVC);
|
||||
}
|
||||
|
||||
static const struct net_proto_family pvc_family_ops = {
|
||||
.family = PF_ATMPVC,
|
||||
.create = pvc_create,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the ATM PVC protocol family
|
||||
*/
|
||||
|
||||
|
||||
int __init atmpvc_init(void)
|
||||
{
|
||||
return sock_register(&pvc_family_ops);
|
||||
}
|
||||
|
||||
void atmpvc_exit(void)
|
||||
{
|
||||
sock_unregister(PF_ATMPVC);
|
||||
}
|
85
net/atm/raw.c
Normal file
85
net/atm/raw.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
/* net/atm/raw.c - Raw AAL0 and AAL5 transports */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "protocols.h"
|
||||
|
||||
/*
|
||||
* SKB == NULL indicates that the link is being closed
|
||||
*/
|
||||
|
||||
static void atm_push_raw(struct atm_vcc *vcc, struct sk_buff *skb)
|
||||
{
|
||||
if (skb) {
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
|
||||
skb_queue_tail(&sk->sk_receive_queue, skb);
|
||||
sk->sk_data_ready(sk, skb->len);
|
||||
}
|
||||
}
|
||||
|
||||
static void atm_pop_raw(struct atm_vcc *vcc, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
|
||||
pr_debug("(%d) %d -= %d\n",
|
||||
vcc->vci, sk_wmem_alloc_get(sk), skb->truesize);
|
||||
atomic_sub(skb->truesize, &sk->sk_wmem_alloc);
|
||||
dev_kfree_skb_any(skb);
|
||||
sk->sk_write_space(sk);
|
||||
}
|
||||
|
||||
static int atm_send_aal0(struct atm_vcc *vcc, struct sk_buff *skb)
|
||||
{
|
||||
/*
|
||||
* Note that if vpi/vci are _ANY or _UNSPEC the below will
|
||||
* still work
|
||||
*/
|
||||
if (!capable(CAP_NET_ADMIN) &&
|
||||
(((u32 *)skb->data)[0] & (ATM_HDR_VPI_MASK | ATM_HDR_VCI_MASK)) !=
|
||||
((vcc->vpi << ATM_HDR_VPI_SHIFT) |
|
||||
(vcc->vci << ATM_HDR_VCI_SHIFT))) {
|
||||
kfree_skb(skb);
|
||||
return -EADDRNOTAVAIL;
|
||||
}
|
||||
return vcc->dev->ops->send(vcc, skb);
|
||||
}
|
||||
|
||||
int atm_init_aal0(struct atm_vcc *vcc)
|
||||
{
|
||||
vcc->push = atm_push_raw;
|
||||
vcc->pop = atm_pop_raw;
|
||||
vcc->push_oam = NULL;
|
||||
vcc->send = atm_send_aal0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atm_init_aal34(struct atm_vcc *vcc)
|
||||
{
|
||||
vcc->push = atm_push_raw;
|
||||
vcc->pop = atm_pop_raw;
|
||||
vcc->push_oam = NULL;
|
||||
vcc->send = vcc->dev->ops->send;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atm_init_aal5(struct atm_vcc *vcc)
|
||||
{
|
||||
vcc->push = atm_push_raw;
|
||||
vcc->pop = atm_pop_raw;
|
||||
vcc->push_oam = NULL;
|
||||
vcc->send = vcc->dev->ops->send;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(atm_init_aal5);
|
463
net/atm/resources.c
Normal file
463
net/atm/resources.c
Normal file
|
@ -0,0 +1,463 @@
|
|||
/* net/atm/resources.c - Statically allocated resources */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
/* Fixes
|
||||
* Arnaldo Carvalho de Melo <acme@conectiva.com.br>
|
||||
* 2002/01 - don't free the whole struct sock on sk->destruct time,
|
||||
* use the default destruct function initialized by sock_init_data */
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/sonet.h>
|
||||
#include <linux/kernel.h> /* for barrier */
|
||||
#include <linux/module.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <net/sock.h> /* for struct sock */
|
||||
|
||||
#include "common.h"
|
||||
#include "resources.h"
|
||||
#include "addr.h"
|
||||
|
||||
|
||||
LIST_HEAD(atm_devs);
|
||||
DEFINE_MUTEX(atm_dev_mutex);
|
||||
|
||||
static struct atm_dev *__alloc_atm_dev(const char *type)
|
||||
{
|
||||
struct atm_dev *dev;
|
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
dev->type = type;
|
||||
dev->signal = ATM_PHY_SIG_UNKNOWN;
|
||||
dev->link_rate = ATM_OC3_PCR;
|
||||
spin_lock_init(&dev->lock);
|
||||
INIT_LIST_HEAD(&dev->local);
|
||||
INIT_LIST_HEAD(&dev->lecs);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static struct atm_dev *__atm_dev_lookup(int number)
|
||||
{
|
||||
struct atm_dev *dev;
|
||||
struct list_head *p;
|
||||
|
||||
list_for_each(p, &atm_devs) {
|
||||
dev = list_entry(p, struct atm_dev, dev_list);
|
||||
if (dev->number == number) {
|
||||
atm_dev_hold(dev);
|
||||
return dev;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct atm_dev *atm_dev_lookup(int number)
|
||||
{
|
||||
struct atm_dev *dev;
|
||||
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
dev = __atm_dev_lookup(number);
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
return dev;
|
||||
}
|
||||
EXPORT_SYMBOL(atm_dev_lookup);
|
||||
|
||||
struct atm_dev *atm_dev_register(const char *type, struct device *parent,
|
||||
const struct atmdev_ops *ops, int number,
|
||||
unsigned long *flags)
|
||||
{
|
||||
struct atm_dev *dev, *inuse;
|
||||
|
||||
dev = __alloc_atm_dev(type);
|
||||
if (!dev) {
|
||||
pr_err("no space for dev %s\n", type);
|
||||
return NULL;
|
||||
}
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
if (number != -1) {
|
||||
inuse = __atm_dev_lookup(number);
|
||||
if (inuse) {
|
||||
atm_dev_put(inuse);
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
kfree(dev);
|
||||
return NULL;
|
||||
}
|
||||
dev->number = number;
|
||||
} else {
|
||||
dev->number = 0;
|
||||
while ((inuse = __atm_dev_lookup(dev->number))) {
|
||||
atm_dev_put(inuse);
|
||||
dev->number++;
|
||||
}
|
||||
}
|
||||
|
||||
dev->ops = ops;
|
||||
if (flags)
|
||||
dev->flags = *flags;
|
||||
else
|
||||
memset(&dev->flags, 0, sizeof(dev->flags));
|
||||
memset(&dev->stats, 0, sizeof(dev->stats));
|
||||
atomic_set(&dev->refcnt, 1);
|
||||
|
||||
if (atm_proc_dev_register(dev) < 0) {
|
||||
pr_err("atm_proc_dev_register failed for dev %s\n", type);
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
if (atm_register_sysfs(dev, parent) < 0) {
|
||||
pr_err("atm_register_sysfs failed for dev %s\n", type);
|
||||
atm_proc_dev_deregister(dev);
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
list_add_tail(&dev->dev_list, &atm_devs);
|
||||
|
||||
out:
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
return dev;
|
||||
|
||||
out_fail:
|
||||
kfree(dev);
|
||||
dev = NULL;
|
||||
goto out;
|
||||
}
|
||||
EXPORT_SYMBOL(atm_dev_register);
|
||||
|
||||
void atm_dev_deregister(struct atm_dev *dev)
|
||||
{
|
||||
BUG_ON(test_bit(ATM_DF_REMOVED, &dev->flags));
|
||||
set_bit(ATM_DF_REMOVED, &dev->flags);
|
||||
|
||||
/*
|
||||
* if we remove current device from atm_devs list, new device
|
||||
* with same number can appear, such we need deregister proc,
|
||||
* release async all vccs and remove them from vccs list too
|
||||
*/
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
list_del(&dev->dev_list);
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
|
||||
atm_dev_release_vccs(dev);
|
||||
atm_unregister_sysfs(dev);
|
||||
atm_proc_dev_deregister(dev);
|
||||
|
||||
atm_dev_put(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(atm_dev_deregister);
|
||||
|
||||
static void copy_aal_stats(struct k_atm_aal_stats *from,
|
||||
struct atm_aal_stats *to)
|
||||
{
|
||||
#define __HANDLE_ITEM(i) to->i = atomic_read(&from->i)
|
||||
__AAL_STAT_ITEMS
|
||||
#undef __HANDLE_ITEM
|
||||
}
|
||||
|
||||
static void subtract_aal_stats(struct k_atm_aal_stats *from,
|
||||
struct atm_aal_stats *to)
|
||||
{
|
||||
#define __HANDLE_ITEM(i) atomic_sub(to->i, &from->i)
|
||||
__AAL_STAT_ITEMS
|
||||
#undef __HANDLE_ITEM
|
||||
}
|
||||
|
||||
static int fetch_stats(struct atm_dev *dev, struct atm_dev_stats __user *arg,
|
||||
int zero)
|
||||
{
|
||||
struct atm_dev_stats tmp;
|
||||
int error = 0;
|
||||
|
||||
copy_aal_stats(&dev->stats.aal0, &tmp.aal0);
|
||||
copy_aal_stats(&dev->stats.aal34, &tmp.aal34);
|
||||
copy_aal_stats(&dev->stats.aal5, &tmp.aal5);
|
||||
if (arg)
|
||||
error = copy_to_user(arg, &tmp, sizeof(tmp));
|
||||
if (zero && !error) {
|
||||
subtract_aal_stats(&dev->stats.aal0, &tmp.aal0);
|
||||
subtract_aal_stats(&dev->stats.aal34, &tmp.aal34);
|
||||
subtract_aal_stats(&dev->stats.aal5, &tmp.aal5);
|
||||
}
|
||||
return error ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
int atm_dev_ioctl(unsigned int cmd, void __user *arg, int compat)
|
||||
{
|
||||
void __user *buf;
|
||||
int error, len, number, size = 0;
|
||||
struct atm_dev *dev;
|
||||
struct list_head *p;
|
||||
int *tmp_buf, *tmp_p;
|
||||
int __user *sioc_len;
|
||||
int __user *iobuf_len;
|
||||
|
||||
#ifndef CONFIG_COMPAT
|
||||
compat = 0; /* Just so the compiler _knows_ */
|
||||
#endif
|
||||
|
||||
switch (cmd) {
|
||||
case ATM_GETNAMES:
|
||||
if (compat) {
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct compat_atm_iobuf __user *ciobuf = arg;
|
||||
compat_uptr_t cbuf;
|
||||
iobuf_len = &ciobuf->length;
|
||||
if (get_user(cbuf, &ciobuf->buffer))
|
||||
return -EFAULT;
|
||||
buf = compat_ptr(cbuf);
|
||||
#endif
|
||||
} else {
|
||||
struct atm_iobuf __user *iobuf = arg;
|
||||
iobuf_len = &iobuf->length;
|
||||
if (get_user(buf, &iobuf->buffer))
|
||||
return -EFAULT;
|
||||
}
|
||||
if (get_user(len, iobuf_len))
|
||||
return -EFAULT;
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
list_for_each(p, &atm_devs)
|
||||
size += sizeof(int);
|
||||
if (size > len) {
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
return -E2BIG;
|
||||
}
|
||||
tmp_buf = kmalloc(size, GFP_ATOMIC);
|
||||
if (!tmp_buf) {
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
return -ENOMEM;
|
||||
}
|
||||
tmp_p = tmp_buf;
|
||||
list_for_each(p, &atm_devs) {
|
||||
dev = list_entry(p, struct atm_dev, dev_list);
|
||||
*tmp_p++ = dev->number;
|
||||
}
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
error = ((copy_to_user(buf, tmp_buf, size)) ||
|
||||
put_user(size, iobuf_len))
|
||||
? -EFAULT : 0;
|
||||
kfree(tmp_buf);
|
||||
return error;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (compat) {
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct compat_atmif_sioc __user *csioc = arg;
|
||||
compat_uptr_t carg;
|
||||
|
||||
sioc_len = &csioc->length;
|
||||
if (get_user(carg, &csioc->arg))
|
||||
return -EFAULT;
|
||||
buf = compat_ptr(carg);
|
||||
|
||||
if (get_user(len, &csioc->length))
|
||||
return -EFAULT;
|
||||
if (get_user(number, &csioc->number))
|
||||
return -EFAULT;
|
||||
#endif
|
||||
} else {
|
||||
struct atmif_sioc __user *sioc = arg;
|
||||
|
||||
sioc_len = &sioc->length;
|
||||
if (get_user(buf, &sioc->arg))
|
||||
return -EFAULT;
|
||||
if (get_user(len, &sioc->length))
|
||||
return -EFAULT;
|
||||
if (get_user(number, &sioc->number))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
dev = try_then_request_module(atm_dev_lookup(number), "atm-device-%d",
|
||||
number);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
switch (cmd) {
|
||||
case ATM_GETTYPE:
|
||||
size = strlen(dev->type) + 1;
|
||||
if (copy_to_user(buf, dev->type, size)) {
|
||||
error = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case ATM_GETESI:
|
||||
size = ESI_LEN;
|
||||
if (copy_to_user(buf, dev->esi, size)) {
|
||||
error = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case ATM_SETESI:
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ESI_LEN; i++)
|
||||
if (dev->esi[i]) {
|
||||
error = -EEXIST;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* fall through */
|
||||
case ATM_SETESIF:
|
||||
{
|
||||
unsigned char esi[ESI_LEN];
|
||||
|
||||
if (!capable(CAP_NET_ADMIN)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
if (copy_from_user(esi, buf, ESI_LEN)) {
|
||||
error = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
memcpy(dev->esi, esi, ESI_LEN);
|
||||
error = ESI_LEN;
|
||||
goto done;
|
||||
}
|
||||
case ATM_GETSTATZ:
|
||||
if (!capable(CAP_NET_ADMIN)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
/* fall through */
|
||||
case ATM_GETSTAT:
|
||||
size = sizeof(struct atm_dev_stats);
|
||||
error = fetch_stats(dev, buf, cmd == ATM_GETSTATZ);
|
||||
if (error)
|
||||
goto done;
|
||||
break;
|
||||
case ATM_GETCIRANGE:
|
||||
size = sizeof(struct atm_cirange);
|
||||
if (copy_to_user(buf, &dev->ci_range, size)) {
|
||||
error = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case ATM_GETLINKRATE:
|
||||
size = sizeof(int);
|
||||
if (copy_to_user(buf, &dev->link_rate, size)) {
|
||||
error = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case ATM_RSTADDR:
|
||||
if (!capable(CAP_NET_ADMIN)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
atm_reset_addr(dev, ATM_ADDR_LOCAL);
|
||||
break;
|
||||
case ATM_ADDADDR:
|
||||
case ATM_DELADDR:
|
||||
case ATM_ADDLECSADDR:
|
||||
case ATM_DELLECSADDR:
|
||||
{
|
||||
struct sockaddr_atmsvc addr;
|
||||
|
||||
if (!capable(CAP_NET_ADMIN)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (copy_from_user(&addr, buf, sizeof(addr))) {
|
||||
error = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
if (cmd == ATM_ADDADDR || cmd == ATM_ADDLECSADDR)
|
||||
error = atm_add_addr(dev, &addr,
|
||||
(cmd == ATM_ADDADDR ?
|
||||
ATM_ADDR_LOCAL : ATM_ADDR_LECS));
|
||||
else
|
||||
error = atm_del_addr(dev, &addr,
|
||||
(cmd == ATM_DELADDR ?
|
||||
ATM_ADDR_LOCAL : ATM_ADDR_LECS));
|
||||
goto done;
|
||||
}
|
||||
case ATM_GETADDR:
|
||||
case ATM_GETLECSADDR:
|
||||
error = atm_get_addr(dev, buf, len,
|
||||
(cmd == ATM_GETADDR ?
|
||||
ATM_ADDR_LOCAL : ATM_ADDR_LECS));
|
||||
if (error < 0)
|
||||
goto done;
|
||||
size = error;
|
||||
/* may return 0, but later on size == 0 means "don't
|
||||
write the length" */
|
||||
error = put_user(size, sioc_len) ? -EFAULT : 0;
|
||||
goto done;
|
||||
case ATM_SETLOOP:
|
||||
if (__ATM_LM_XTRMT((int) (unsigned long) buf) &&
|
||||
__ATM_LM_XTLOC((int) (unsigned long) buf) >
|
||||
__ATM_LM_XTRMT((int) (unsigned long) buf)) {
|
||||
error = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
/* fall through */
|
||||
case ATM_SETCIRANGE:
|
||||
case SONET_GETSTATZ:
|
||||
case SONET_SETDIAG:
|
||||
case SONET_CLRDIAG:
|
||||
case SONET_SETFRAMING:
|
||||
if (!capable(CAP_NET_ADMIN)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
if (compat) {
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (!dev->ops->compat_ioctl) {
|
||||
error = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
size = dev->ops->compat_ioctl(dev, cmd, buf);
|
||||
#endif
|
||||
} else {
|
||||
if (!dev->ops->ioctl) {
|
||||
error = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
size = dev->ops->ioctl(dev, cmd, buf);
|
||||
}
|
||||
if (size < 0) {
|
||||
error = (size == -ENOIOCTLCMD ? -ENOTTY : size);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (size)
|
||||
error = put_user(size, sioc_len) ? -EFAULT : 0;
|
||||
else
|
||||
error = 0;
|
||||
done:
|
||||
atm_dev_put(dev);
|
||||
return error;
|
||||
}
|
||||
|
||||
void *atm_dev_seq_start(struct seq_file *seq, loff_t *pos)
|
||||
{
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
return seq_list_start_head(&atm_devs, *pos);
|
||||
}
|
||||
|
||||
void atm_dev_seq_stop(struct seq_file *seq, void *v)
|
||||
{
|
||||
mutex_unlock(&atm_dev_mutex);
|
||||
}
|
||||
|
||||
void *atm_dev_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
||||
{
|
||||
return seq_list_next(v, &atm_devs, pos);
|
||||
}
|
47
net/atm/resources.h
Normal file
47
net/atm/resources.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* net/atm/resources.h - ATM-related resources */
|
||||
|
||||
/* Written 1995-1998 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#ifndef NET_ATM_RESOURCES_H
|
||||
#define NET_ATM_RESOURCES_H
|
||||
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
|
||||
extern struct list_head atm_devs;
|
||||
extern struct mutex atm_dev_mutex;
|
||||
|
||||
int atm_dev_ioctl(unsigned int cmd, void __user *arg, int compat);
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
#include <linux/proc_fs.h>
|
||||
|
||||
void *atm_dev_seq_start(struct seq_file *seq, loff_t *pos);
|
||||
void atm_dev_seq_stop(struct seq_file *seq, void *v);
|
||||
void *atm_dev_seq_next(struct seq_file *seq, void *v, loff_t *pos);
|
||||
|
||||
|
||||
int atm_proc_dev_register(struct atm_dev *dev);
|
||||
void atm_proc_dev_deregister(struct atm_dev *dev);
|
||||
|
||||
#else
|
||||
|
||||
static inline int atm_proc_dev_register(struct atm_dev *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void atm_proc_dev_deregister(struct atm_dev *dev)
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
int atm_register_sysfs(struct atm_dev *adev, struct device *parent);
|
||||
void atm_unregister_sysfs(struct atm_dev *adev);
|
||||
#endif
|
268
net/atm/signaling.c
Normal file
268
net/atm/signaling.c
Normal file
|
@ -0,0 +1,268 @@
|
|||
/* net/atm/signaling.c - ATM signaling */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/errno.h> /* error codes */
|
||||
#include <linux/kernel.h> /* printk */
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h> /* jiffies and HZ */
|
||||
#include <linux/atm.h> /* ATM stuff */
|
||||
#include <linux/atmsap.h>
|
||||
#include <linux/atmsvc.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "resources.h"
|
||||
#include "signaling.h"
|
||||
|
||||
#undef WAIT_FOR_DEMON /* #define this if system calls on SVC sockets
|
||||
should block until the demon runs.
|
||||
Danger: may cause nasty hangs if the demon
|
||||
crashes. */
|
||||
|
||||
struct atm_vcc *sigd = NULL;
|
||||
#ifdef WAIT_FOR_DEMON
|
||||
static DECLARE_WAIT_QUEUE_HEAD(sigd_sleep);
|
||||
#endif
|
||||
|
||||
static void sigd_put_skb(struct sk_buff *skb)
|
||||
{
|
||||
#ifdef WAIT_FOR_DEMON
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
add_wait_queue(&sigd_sleep, &wait);
|
||||
while (!sigd) {
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
pr_debug("atmsvc: waiting for signaling daemon...\n");
|
||||
schedule();
|
||||
}
|
||||
current->state = TASK_RUNNING;
|
||||
remove_wait_queue(&sigd_sleep, &wait);
|
||||
#else
|
||||
if (!sigd) {
|
||||
pr_debug("atmsvc: no signaling daemon\n");
|
||||
kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
atm_force_charge(sigd, skb->truesize);
|
||||
skb_queue_tail(&sk_atm(sigd)->sk_receive_queue, skb);
|
||||
sk_atm(sigd)->sk_data_ready(sk_atm(sigd), skb->len);
|
||||
}
|
||||
|
||||
static void modify_qos(struct atm_vcc *vcc, struct atmsvc_msg *msg)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
|
||||
if (test_bit(ATM_VF_RELEASED, &vcc->flags) ||
|
||||
!test_bit(ATM_VF_READY, &vcc->flags))
|
||||
return;
|
||||
msg->type = as_error;
|
||||
if (!vcc->dev->ops->change_qos)
|
||||
msg->reply = -EOPNOTSUPP;
|
||||
else {
|
||||
/* should lock VCC */
|
||||
msg->reply = vcc->dev->ops->change_qos(vcc, &msg->qos,
|
||||
msg->reply);
|
||||
if (!msg->reply)
|
||||
msg->type = as_okay;
|
||||
}
|
||||
/*
|
||||
* Should probably just turn around the old skb. But the, the buffer
|
||||
* space accounting needs to follow the change too. Maybe later.
|
||||
*/
|
||||
while (!(skb = alloc_skb(sizeof(struct atmsvc_msg), GFP_KERNEL)))
|
||||
schedule();
|
||||
*(struct atmsvc_msg *)skb_put(skb, sizeof(struct atmsvc_msg)) = *msg;
|
||||
sigd_put_skb(skb);
|
||||
}
|
||||
|
||||
static int sigd_send(struct atm_vcc *vcc, struct sk_buff *skb)
|
||||
{
|
||||
struct atmsvc_msg *msg;
|
||||
struct atm_vcc *session_vcc;
|
||||
struct sock *sk;
|
||||
|
||||
msg = (struct atmsvc_msg *) skb->data;
|
||||
atomic_sub(skb->truesize, &sk_atm(vcc)->sk_wmem_alloc);
|
||||
vcc = *(struct atm_vcc **) &msg->vcc;
|
||||
pr_debug("%d (0x%lx)\n", (int)msg->type, (unsigned long)vcc);
|
||||
sk = sk_atm(vcc);
|
||||
|
||||
switch (msg->type) {
|
||||
case as_okay:
|
||||
sk->sk_err = -msg->reply;
|
||||
clear_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
if (!*vcc->local.sas_addr.prv && !*vcc->local.sas_addr.pub) {
|
||||
vcc->local.sas_family = AF_ATMSVC;
|
||||
memcpy(vcc->local.sas_addr.prv,
|
||||
msg->local.sas_addr.prv, ATM_ESA_LEN);
|
||||
memcpy(vcc->local.sas_addr.pub,
|
||||
msg->local.sas_addr.pub, ATM_E164_LEN + 1);
|
||||
}
|
||||
session_vcc = vcc->session ? vcc->session : vcc;
|
||||
if (session_vcc->vpi || session_vcc->vci)
|
||||
break;
|
||||
session_vcc->itf = msg->pvc.sap_addr.itf;
|
||||
session_vcc->vpi = msg->pvc.sap_addr.vpi;
|
||||
session_vcc->vci = msg->pvc.sap_addr.vci;
|
||||
if (session_vcc->vpi || session_vcc->vci)
|
||||
session_vcc->qos = msg->qos;
|
||||
break;
|
||||
case as_error:
|
||||
clear_bit(ATM_VF_REGIS, &vcc->flags);
|
||||
clear_bit(ATM_VF_READY, &vcc->flags);
|
||||
sk->sk_err = -msg->reply;
|
||||
clear_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
break;
|
||||
case as_indicate:
|
||||
vcc = *(struct atm_vcc **)&msg->listen_vcc;
|
||||
sk = sk_atm(vcc);
|
||||
pr_debug("as_indicate!!!\n");
|
||||
lock_sock(sk);
|
||||
if (sk_acceptq_is_full(sk)) {
|
||||
sigd_enq(NULL, as_reject, vcc, NULL, NULL);
|
||||
dev_kfree_skb(skb);
|
||||
goto as_indicate_complete;
|
||||
}
|
||||
sk->sk_ack_backlog++;
|
||||
skb_queue_tail(&sk->sk_receive_queue, skb);
|
||||
pr_debug("waking sk_sleep(sk) 0x%p\n", sk_sleep(sk));
|
||||
sk->sk_state_change(sk);
|
||||
as_indicate_complete:
|
||||
release_sock(sk);
|
||||
return 0;
|
||||
case as_close:
|
||||
set_bit(ATM_VF_RELEASED, &vcc->flags);
|
||||
vcc_release_async(vcc, msg->reply);
|
||||
goto out;
|
||||
case as_modify:
|
||||
modify_qos(vcc, msg);
|
||||
break;
|
||||
case as_addparty:
|
||||
case as_dropparty:
|
||||
sk->sk_err_soft = msg->reply;
|
||||
/* < 0 failure, otherwise ep_ref */
|
||||
clear_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
break;
|
||||
default:
|
||||
pr_alert("bad message type %d\n", (int)msg->type);
|
||||
return -EINVAL;
|
||||
}
|
||||
sk->sk_state_change(sk);
|
||||
out:
|
||||
dev_kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sigd_enq2(struct atm_vcc *vcc, enum atmsvc_msg_type type,
|
||||
struct atm_vcc *listen_vcc, const struct sockaddr_atmpvc *pvc,
|
||||
const struct sockaddr_atmsvc *svc, const struct atm_qos *qos,
|
||||
int reply)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct atmsvc_msg *msg;
|
||||
static unsigned int session = 0;
|
||||
|
||||
pr_debug("%d (0x%p)\n", (int)type, vcc);
|
||||
while (!(skb = alloc_skb(sizeof(struct atmsvc_msg), GFP_KERNEL)))
|
||||
schedule();
|
||||
msg = (struct atmsvc_msg *)skb_put(skb, sizeof(struct atmsvc_msg));
|
||||
memset(msg, 0, sizeof(*msg));
|
||||
msg->type = type;
|
||||
*(struct atm_vcc **) &msg->vcc = vcc;
|
||||
*(struct atm_vcc **) &msg->listen_vcc = listen_vcc;
|
||||
msg->reply = reply;
|
||||
if (qos)
|
||||
msg->qos = *qos;
|
||||
if (vcc)
|
||||
msg->sap = vcc->sap;
|
||||
if (svc)
|
||||
msg->svc = *svc;
|
||||
if (vcc)
|
||||
msg->local = vcc->local;
|
||||
if (pvc)
|
||||
msg->pvc = *pvc;
|
||||
if (vcc) {
|
||||
if (type == as_connect && test_bit(ATM_VF_SESSION, &vcc->flags))
|
||||
msg->session = ++session;
|
||||
/* every new pmp connect gets the next session number */
|
||||
}
|
||||
sigd_put_skb(skb);
|
||||
if (vcc)
|
||||
set_bit(ATM_VF_REGIS, &vcc->flags);
|
||||
}
|
||||
|
||||
void sigd_enq(struct atm_vcc *vcc, enum atmsvc_msg_type type,
|
||||
struct atm_vcc *listen_vcc, const struct sockaddr_atmpvc *pvc,
|
||||
const struct sockaddr_atmsvc *svc)
|
||||
{
|
||||
sigd_enq2(vcc, type, listen_vcc, pvc, svc, vcc ? &vcc->qos : NULL, 0);
|
||||
/* other ISP applications may use "reply" */
|
||||
}
|
||||
|
||||
static void purge_vcc(struct atm_vcc *vcc)
|
||||
{
|
||||
if (sk_atm(vcc)->sk_family == PF_ATMSVC &&
|
||||
!test_bit(ATM_VF_META, &vcc->flags)) {
|
||||
set_bit(ATM_VF_RELEASED, &vcc->flags);
|
||||
clear_bit(ATM_VF_REGIS, &vcc->flags);
|
||||
vcc_release_async(vcc, -EUNATCH);
|
||||
}
|
||||
}
|
||||
|
||||
static void sigd_close(struct atm_vcc *vcc)
|
||||
{
|
||||
struct sock *s;
|
||||
int i;
|
||||
|
||||
pr_debug("\n");
|
||||
sigd = NULL;
|
||||
if (skb_peek(&sk_atm(vcc)->sk_receive_queue))
|
||||
pr_err("closing with requests pending\n");
|
||||
skb_queue_purge(&sk_atm(vcc)->sk_receive_queue);
|
||||
|
||||
read_lock(&vcc_sklist_lock);
|
||||
for (i = 0; i < VCC_HTABLE_SIZE; ++i) {
|
||||
struct hlist_head *head = &vcc_hash[i];
|
||||
|
||||
sk_for_each(s, head) {
|
||||
vcc = atm_sk(s);
|
||||
|
||||
purge_vcc(vcc);
|
||||
}
|
||||
}
|
||||
read_unlock(&vcc_sklist_lock);
|
||||
}
|
||||
|
||||
static struct atmdev_ops sigd_dev_ops = {
|
||||
.close = sigd_close,
|
||||
.send = sigd_send
|
||||
};
|
||||
|
||||
static struct atm_dev sigd_dev = {
|
||||
.ops = &sigd_dev_ops,
|
||||
.type = "sig",
|
||||
.number = 999,
|
||||
.lock = __SPIN_LOCK_UNLOCKED(sigd_dev.lock)
|
||||
};
|
||||
|
||||
int sigd_attach(struct atm_vcc *vcc)
|
||||
{
|
||||
if (sigd)
|
||||
return -EADDRINUSE;
|
||||
pr_debug("\n");
|
||||
sigd = vcc;
|
||||
vcc->dev = &sigd_dev;
|
||||
vcc_insert_socket(sk_atm(vcc));
|
||||
set_bit(ATM_VF_META, &vcc->flags);
|
||||
set_bit(ATM_VF_READY, &vcc->flags);
|
||||
#ifdef WAIT_FOR_DEMON
|
||||
wake_up(&sigd_sleep);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
30
net/atm/signaling.h
Normal file
30
net/atm/signaling.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* net/atm/signaling.h - ATM signaling */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#ifndef NET_ATM_SIGNALING_H
|
||||
#define NET_ATM_SIGNALING_H
|
||||
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/atmsvc.h>
|
||||
|
||||
|
||||
extern struct atm_vcc *sigd; /* needed in svc_release */
|
||||
|
||||
|
||||
/*
|
||||
* sigd_enq is a wrapper for sigd_enq2, covering the more common cases, and
|
||||
* avoiding huge lists of null values.
|
||||
*/
|
||||
|
||||
void sigd_enq2(struct atm_vcc *vcc,enum atmsvc_msg_type type,
|
||||
struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc,
|
||||
const struct sockaddr_atmsvc *svc,const struct atm_qos *qos,int reply);
|
||||
void sigd_enq(struct atm_vcc *vcc,enum atmsvc_msg_type type,
|
||||
struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc,
|
||||
const struct sockaddr_atmsvc *svc);
|
||||
int sigd_attach(struct atm_vcc *vcc);
|
||||
|
||||
#endif
|
692
net/atm/svc.c
Normal file
692
net/atm/svc.c
Normal file
|
@ -0,0 +1,692 @@
|
|||
/* net/atm/svc.c - ATM SVC sockets */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
||||
|
||||
#include <linux/string.h>
|
||||
#include <linux/net.h> /* struct socket, struct proto_ops */
|
||||
#include <linux/errno.h> /* error codes */
|
||||
#include <linux/kernel.h> /* printk */
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h> /* jiffies and HZ */
|
||||
#include <linux/fcntl.h> /* O_NONBLOCK */
|
||||
#include <linux/init.h>
|
||||
#include <linux/atm.h> /* ATM stuff */
|
||||
#include <linux/atmsap.h>
|
||||
#include <linux/atmsvc.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <net/sock.h> /* for sock_no_* */
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#include "resources.h"
|
||||
#include "common.h" /* common for PVCs and SVCs */
|
||||
#include "signaling.h"
|
||||
#include "addr.h"
|
||||
|
||||
static int svc_create(struct net *net, struct socket *sock, int protocol,
|
||||
int kern);
|
||||
|
||||
/*
|
||||
* Note: since all this is still nicely synchronized with the signaling demon,
|
||||
* there's no need to protect sleep loops with clis. If signaling is
|
||||
* moved into the kernel, that would change.
|
||||
*/
|
||||
|
||||
|
||||
static int svc_shutdown(struct socket *sock, int how)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void svc_disconnect(struct atm_vcc *vcc)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
struct sk_buff *skb;
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
|
||||
pr_debug("%p\n", vcc);
|
||||
if (test_bit(ATM_VF_REGIS, &vcc->flags)) {
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
|
||||
sigd_enq(vcc, as_close, NULL, NULL, NULL);
|
||||
while (!test_bit(ATM_VF_RELEASED, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
prepare_to_wait(sk_sleep(sk), &wait,
|
||||
TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
}
|
||||
/* beware - socket is still in use by atmsigd until the last
|
||||
as_indicate has been answered */
|
||||
while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
|
||||
atm_return(vcc, skb->truesize);
|
||||
pr_debug("LISTEN REL\n");
|
||||
sigd_enq2(NULL, as_reject, vcc, NULL, NULL, &vcc->qos, 0);
|
||||
dev_kfree_skb(skb);
|
||||
}
|
||||
clear_bit(ATM_VF_REGIS, &vcc->flags);
|
||||
/* ... may retry later */
|
||||
}
|
||||
|
||||
static int svc_release(struct socket *sock)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc;
|
||||
|
||||
if (sk) {
|
||||
vcc = ATM_SD(sock);
|
||||
pr_debug("%p\n", vcc);
|
||||
clear_bit(ATM_VF_READY, &vcc->flags);
|
||||
/*
|
||||
* VCC pointer is used as a reference,
|
||||
* so we must not free it (thereby subjecting it to re-use)
|
||||
* before all pending connections are closed
|
||||
*/
|
||||
svc_disconnect(vcc);
|
||||
vcc_release(sock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int svc_bind(struct socket *sock, struct sockaddr *sockaddr,
|
||||
int sockaddr_len)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
struct sock *sk = sock->sk;
|
||||
struct sockaddr_atmsvc *addr;
|
||||
struct atm_vcc *vcc;
|
||||
int error;
|
||||
|
||||
if (sockaddr_len != sizeof(struct sockaddr_atmsvc))
|
||||
return -EINVAL;
|
||||
lock_sock(sk);
|
||||
if (sock->state == SS_CONNECTED) {
|
||||
error = -EISCONN;
|
||||
goto out;
|
||||
}
|
||||
if (sock->state != SS_UNCONNECTED) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
vcc = ATM_SD(sock);
|
||||
addr = (struct sockaddr_atmsvc *) sockaddr;
|
||||
if (addr->sas_family != AF_ATMSVC) {
|
||||
error = -EAFNOSUPPORT;
|
||||
goto out;
|
||||
}
|
||||
clear_bit(ATM_VF_BOUND, &vcc->flags);
|
||||
/* failing rebind will kill old binding */
|
||||
/* @@@ check memory (de)allocation on rebind */
|
||||
if (!test_bit(ATM_VF_HASQOS, &vcc->flags)) {
|
||||
error = -EBADFD;
|
||||
goto out;
|
||||
}
|
||||
vcc->local = *addr;
|
||||
set_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
|
||||
sigd_enq(vcc, as_bind, NULL, NULL, &vcc->local);
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
clear_bit(ATM_VF_REGIS, &vcc->flags); /* doesn't count */
|
||||
if (!sigd) {
|
||||
error = -EUNATCH;
|
||||
goto out;
|
||||
}
|
||||
if (!sk->sk_err)
|
||||
set_bit(ATM_VF_BOUND, &vcc->flags);
|
||||
error = -sk->sk_err;
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_connect(struct socket *sock, struct sockaddr *sockaddr,
|
||||
int sockaddr_len, int flags)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
struct sock *sk = sock->sk;
|
||||
struct sockaddr_atmsvc *addr;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
int error;
|
||||
|
||||
pr_debug("%p\n", vcc);
|
||||
lock_sock(sk);
|
||||
if (sockaddr_len != sizeof(struct sockaddr_atmsvc)) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (sock->state) {
|
||||
default:
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
case SS_CONNECTED:
|
||||
error = -EISCONN;
|
||||
goto out;
|
||||
case SS_CONNECTING:
|
||||
if (test_bit(ATM_VF_WAITING, &vcc->flags)) {
|
||||
error = -EALREADY;
|
||||
goto out;
|
||||
}
|
||||
sock->state = SS_UNCONNECTED;
|
||||
if (sk->sk_err) {
|
||||
error = -sk->sk_err;
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
case SS_UNCONNECTED:
|
||||
addr = (struct sockaddr_atmsvc *) sockaddr;
|
||||
if (addr->sas_family != AF_ATMSVC) {
|
||||
error = -EAFNOSUPPORT;
|
||||
goto out;
|
||||
}
|
||||
if (!test_bit(ATM_VF_HASQOS, &vcc->flags)) {
|
||||
error = -EBADFD;
|
||||
goto out;
|
||||
}
|
||||
if (vcc->qos.txtp.traffic_class == ATM_ANYCLASS ||
|
||||
vcc->qos.rxtp.traffic_class == ATM_ANYCLASS) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (!vcc->qos.txtp.traffic_class &&
|
||||
!vcc->qos.rxtp.traffic_class) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
vcc->remote = *addr;
|
||||
set_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
sigd_enq(vcc, as_connect, NULL, NULL, &vcc->remote);
|
||||
if (flags & O_NONBLOCK) {
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
sock->state = SS_CONNECTING;
|
||||
error = -EINPROGRESS;
|
||||
goto out;
|
||||
}
|
||||
error = 0;
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
if (!signal_pending(current)) {
|
||||
prepare_to_wait(sk_sleep(sk), &wait,
|
||||
TASK_INTERRUPTIBLE);
|
||||
continue;
|
||||
}
|
||||
pr_debug("*ABORT*\n");
|
||||
/*
|
||||
* This is tricky:
|
||||
* Kernel ---close--> Demon
|
||||
* Kernel <--close--- Demon
|
||||
* or
|
||||
* Kernel ---close--> Demon
|
||||
* Kernel <--error--- Demon
|
||||
* or
|
||||
* Kernel ---close--> Demon
|
||||
* Kernel <--okay---- Demon
|
||||
* Kernel <--close--- Demon
|
||||
*/
|
||||
sigd_enq(vcc, as_close, NULL, NULL, NULL);
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) {
|
||||
prepare_to_wait(sk_sleep(sk), &wait,
|
||||
TASK_INTERRUPTIBLE);
|
||||
schedule();
|
||||
}
|
||||
if (!sk->sk_err)
|
||||
while (!test_bit(ATM_VF_RELEASED, &vcc->flags) &&
|
||||
sigd) {
|
||||
prepare_to_wait(sk_sleep(sk), &wait,
|
||||
TASK_INTERRUPTIBLE);
|
||||
schedule();
|
||||
}
|
||||
clear_bit(ATM_VF_REGIS, &vcc->flags);
|
||||
clear_bit(ATM_VF_RELEASED, &vcc->flags);
|
||||
clear_bit(ATM_VF_CLOSE, &vcc->flags);
|
||||
/* we're gone now but may connect later */
|
||||
error = -EINTR;
|
||||
break;
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
if (error)
|
||||
goto out;
|
||||
if (!sigd) {
|
||||
error = -EUNATCH;
|
||||
goto out;
|
||||
}
|
||||
if (sk->sk_err) {
|
||||
error = -sk->sk_err;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Not supported yet
|
||||
*
|
||||
* #ifndef CONFIG_SINGLE_SIGITF
|
||||
*/
|
||||
vcc->qos.txtp.max_pcr = SELECT_TOP_PCR(vcc->qos.txtp);
|
||||
vcc->qos.txtp.pcr = 0;
|
||||
vcc->qos.txtp.min_pcr = 0;
|
||||
/*
|
||||
* #endif
|
||||
*/
|
||||
error = vcc_connect(sock, vcc->itf, vcc->vpi, vcc->vci);
|
||||
if (!error)
|
||||
sock->state = SS_CONNECTED;
|
||||
else
|
||||
(void)svc_disconnect(vcc);
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_listen(struct socket *sock, int backlog)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
int error;
|
||||
|
||||
pr_debug("%p\n", vcc);
|
||||
lock_sock(sk);
|
||||
/* let server handle listen on unbound sockets */
|
||||
if (test_bit(ATM_VF_SESSION, &vcc->flags)) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (test_bit(ATM_VF_LISTEN, &vcc->flags)) {
|
||||
error = -EADDRINUSE;
|
||||
goto out;
|
||||
}
|
||||
set_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
|
||||
sigd_enq(vcc, as_listen, NULL, NULL, &vcc->local);
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
if (!sigd) {
|
||||
error = -EUNATCH;
|
||||
goto out;
|
||||
}
|
||||
set_bit(ATM_VF_LISTEN, &vcc->flags);
|
||||
vcc_insert_socket(sk);
|
||||
sk->sk_max_ack_backlog = backlog > 0 ? backlog : ATM_BACKLOG_DEFAULT;
|
||||
error = -sk->sk_err;
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_accept(struct socket *sock, struct socket *newsock, int flags)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct sk_buff *skb;
|
||||
struct atmsvc_msg *msg;
|
||||
struct atm_vcc *old_vcc = ATM_SD(sock);
|
||||
struct atm_vcc *new_vcc;
|
||||
int error;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
error = svc_create(sock_net(sk), newsock, 0, 0);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
new_vcc = ATM_SD(newsock);
|
||||
|
||||
pr_debug("%p -> %p\n", old_vcc, new_vcc);
|
||||
while (1) {
|
||||
DEFINE_WAIT(wait);
|
||||
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
while (!(skb = skb_dequeue(&sk->sk_receive_queue)) &&
|
||||
sigd) {
|
||||
if (test_bit(ATM_VF_RELEASED, &old_vcc->flags))
|
||||
break;
|
||||
if (test_bit(ATM_VF_CLOSE, &old_vcc->flags)) {
|
||||
error = -sk->sk_err;
|
||||
break;
|
||||
}
|
||||
if (flags & O_NONBLOCK) {
|
||||
error = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
release_sock(sk);
|
||||
schedule();
|
||||
lock_sock(sk);
|
||||
if (signal_pending(current)) {
|
||||
error = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
prepare_to_wait(sk_sleep(sk), &wait,
|
||||
TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
if (error)
|
||||
goto out;
|
||||
if (!skb) {
|
||||
error = -EUNATCH;
|
||||
goto out;
|
||||
}
|
||||
msg = (struct atmsvc_msg *)skb->data;
|
||||
new_vcc->qos = msg->qos;
|
||||
set_bit(ATM_VF_HASQOS, &new_vcc->flags);
|
||||
new_vcc->remote = msg->svc;
|
||||
new_vcc->local = msg->local;
|
||||
new_vcc->sap = msg->sap;
|
||||
error = vcc_connect(newsock, msg->pvc.sap_addr.itf,
|
||||
msg->pvc.sap_addr.vpi,
|
||||
msg->pvc.sap_addr.vci);
|
||||
dev_kfree_skb(skb);
|
||||
sk->sk_ack_backlog--;
|
||||
if (error) {
|
||||
sigd_enq2(NULL, as_reject, old_vcc, NULL, NULL,
|
||||
&old_vcc->qos, error);
|
||||
error = error == -EAGAIN ? -EBUSY : error;
|
||||
goto out;
|
||||
}
|
||||
/* wait should be short, so we ignore the non-blocking flag */
|
||||
set_bit(ATM_VF_WAITING, &new_vcc->flags);
|
||||
prepare_to_wait(sk_sleep(sk_atm(new_vcc)), &wait,
|
||||
TASK_UNINTERRUPTIBLE);
|
||||
sigd_enq(new_vcc, as_accept, old_vcc, NULL, NULL);
|
||||
while (test_bit(ATM_VF_WAITING, &new_vcc->flags) && sigd) {
|
||||
release_sock(sk);
|
||||
schedule();
|
||||
lock_sock(sk);
|
||||
prepare_to_wait(sk_sleep(sk_atm(new_vcc)), &wait,
|
||||
TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk_atm(new_vcc)), &wait);
|
||||
if (!sigd) {
|
||||
error = -EUNATCH;
|
||||
goto out;
|
||||
}
|
||||
if (!sk_atm(new_vcc)->sk_err)
|
||||
break;
|
||||
if (sk_atm(new_vcc)->sk_err != ERESTARTSYS) {
|
||||
error = -sk_atm(new_vcc)->sk_err;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
newsock->state = SS_CONNECTED;
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_getname(struct socket *sock, struct sockaddr *sockaddr,
|
||||
int *sockaddr_len, int peer)
|
||||
{
|
||||
struct sockaddr_atmsvc *addr;
|
||||
|
||||
*sockaddr_len = sizeof(struct sockaddr_atmsvc);
|
||||
addr = (struct sockaddr_atmsvc *) sockaddr;
|
||||
memcpy(addr, peer ? &ATM_SD(sock)->remote : &ATM_SD(sock)->local,
|
||||
sizeof(struct sockaddr_atmsvc));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int svc_change_qos(struct atm_vcc *vcc, struct atm_qos *qos)
|
||||
{
|
||||
struct sock *sk = sk_atm(vcc);
|
||||
DEFINE_WAIT(wait);
|
||||
|
||||
set_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
|
||||
sigd_enq2(vcc, as_modify, NULL, NULL, &vcc->local, qos, 0);
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) &&
|
||||
!test_bit(ATM_VF_RELEASED, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
if (!sigd)
|
||||
return -EUNATCH;
|
||||
return -sk->sk_err;
|
||||
}
|
||||
|
||||
static int svc_setsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, unsigned int optlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
int value, error = 0;
|
||||
|
||||
lock_sock(sk);
|
||||
switch (optname) {
|
||||
case SO_ATMSAP:
|
||||
if (level != SOL_ATM || optlen != sizeof(struct atm_sap)) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (copy_from_user(&vcc->sap, optval, optlen)) {
|
||||
error = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
set_bit(ATM_VF_HASSAP, &vcc->flags);
|
||||
break;
|
||||
case SO_MULTIPOINT:
|
||||
if (level != SOL_ATM || optlen != sizeof(int)) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (get_user(value, (int __user *)optval)) {
|
||||
error = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
if (value == 1)
|
||||
set_bit(ATM_VF_SESSION, &vcc->flags);
|
||||
else if (value == 0)
|
||||
clear_bit(ATM_VF_SESSION, &vcc->flags);
|
||||
else
|
||||
error = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
error = vcc_setsockopt(sock, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_getsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
int error = 0, len;
|
||||
|
||||
lock_sock(sk);
|
||||
if (!__SO_LEVEL_MATCH(optname, level) || optname != SO_ATMSAP) {
|
||||
error = vcc_getsockopt(sock, level, optname, optval, optlen);
|
||||
goto out;
|
||||
}
|
||||
if (get_user(len, optlen)) {
|
||||
error = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
if (len != sizeof(struct atm_sap)) {
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (copy_to_user(optval, &ATM_SD(sock)->sap, sizeof(struct atm_sap))) {
|
||||
error = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_addparty(struct socket *sock, struct sockaddr *sockaddr,
|
||||
int sockaddr_len, int flags)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
int error;
|
||||
|
||||
lock_sock(sk);
|
||||
set_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
sigd_enq(vcc, as_addparty, NULL, NULL,
|
||||
(struct sockaddr_atmsvc *) sockaddr);
|
||||
if (flags & O_NONBLOCK) {
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
error = -EINPROGRESS;
|
||||
goto out;
|
||||
}
|
||||
pr_debug("added wait queue\n");
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
error = xchg(&sk->sk_err_soft, 0);
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_dropparty(struct socket *sock, int ep_ref)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
struct sock *sk = sock->sk;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
int error;
|
||||
|
||||
lock_sock(sk);
|
||||
set_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
sigd_enq2(vcc, as_dropparty, NULL, NULL, NULL, NULL, ep_ref);
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_sleep(sk), &wait);
|
||||
if (!sigd) {
|
||||
error = -EUNATCH;
|
||||
goto out;
|
||||
}
|
||||
error = xchg(&sk->sk_err_soft, 0);
|
||||
out:
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int svc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int error, ep_ref;
|
||||
struct sockaddr_atmsvc sa;
|
||||
struct atm_vcc *vcc = ATM_SD(sock);
|
||||
|
||||
switch (cmd) {
|
||||
case ATM_ADDPARTY:
|
||||
if (!test_bit(ATM_VF_SESSION, &vcc->flags))
|
||||
return -EINVAL;
|
||||
if (copy_from_user(&sa, (void __user *) arg, sizeof(sa)))
|
||||
return -EFAULT;
|
||||
error = svc_addparty(sock, (struct sockaddr *)&sa, sizeof(sa),
|
||||
0);
|
||||
break;
|
||||
case ATM_DROPPARTY:
|
||||
if (!test_bit(ATM_VF_SESSION, &vcc->flags))
|
||||
return -EINVAL;
|
||||
if (copy_from_user(&ep_ref, (void __user *) arg, sizeof(int)))
|
||||
return -EFAULT;
|
||||
error = svc_dropparty(sock, ep_ref);
|
||||
break;
|
||||
default:
|
||||
error = vcc_ioctl(sock, cmd, arg);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static int svc_compat_ioctl(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
/* The definition of ATM_ADDPARTY uses the size of struct atm_iobuf.
|
||||
But actually it takes a struct sockaddr_atmsvc, which doesn't need
|
||||
compat handling. So all we have to do is fix up cmd... */
|
||||
if (cmd == COMPAT_ATM_ADDPARTY)
|
||||
cmd = ATM_ADDPARTY;
|
||||
|
||||
if (cmd == ATM_ADDPARTY || cmd == ATM_DROPPARTY)
|
||||
return svc_ioctl(sock, cmd, arg);
|
||||
else
|
||||
return vcc_compat_ioctl(sock, cmd, arg);
|
||||
}
|
||||
#endif /* CONFIG_COMPAT */
|
||||
|
||||
static const struct proto_ops svc_proto_ops = {
|
||||
.family = PF_ATMSVC,
|
||||
.owner = THIS_MODULE,
|
||||
|
||||
.release = svc_release,
|
||||
.bind = svc_bind,
|
||||
.connect = svc_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = svc_accept,
|
||||
.getname = svc_getname,
|
||||
.poll = vcc_poll,
|
||||
.ioctl = svc_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = svc_compat_ioctl,
|
||||
#endif
|
||||
.listen = svc_listen,
|
||||
.shutdown = svc_shutdown,
|
||||
.setsockopt = svc_setsockopt,
|
||||
.getsockopt = svc_getsockopt,
|
||||
.sendmsg = vcc_sendmsg,
|
||||
.recvmsg = vcc_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
.sendpage = sock_no_sendpage,
|
||||
};
|
||||
|
||||
|
||||
static int svc_create(struct net *net, struct socket *sock, int protocol,
|
||||
int kern)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!net_eq(net, &init_net))
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
sock->ops = &svc_proto_ops;
|
||||
error = vcc_create(net, sock, protocol, AF_ATMSVC);
|
||||
if (error)
|
||||
return error;
|
||||
ATM_SD(sock)->local.sas_family = AF_ATMSVC;
|
||||
ATM_SD(sock)->remote.sas_family = AF_ATMSVC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct net_proto_family svc_family_ops = {
|
||||
.family = PF_ATMSVC,
|
||||
.create = svc_create,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the ATM SVC protocol family
|
||||
*/
|
||||
|
||||
int __init atmsvc_init(void)
|
||||
{
|
||||
return sock_register(&svc_family_ops);
|
||||
}
|
||||
|
||||
void atmsvc_exit(void)
|
||||
{
|
||||
sock_unregister(PF_ATMSVC);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue