Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
74
net/atm/Kconfig
Normal file
74
net/atm/Kconfig
Normal file
@@ -0,0 +1,74 @@
|
||||
#
|
||||
# Asynchronous Transfer Mode (ATM) (EXPERIMENTAL)
|
||||
#
|
||||
|
||||
config ATM
|
||||
tristate "Asynchronous Transfer Mode (ATM) (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
---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 (EXPERIMENTAL)"
|
||||
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 (EXPERIMENTAL)"
|
||||
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 (EXPERIMENTAL)"
|
||||
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 (EXPERIMENTAL)"
|
||||
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
|
||||
160
net/atm/addr.c
Normal file
160
net/atm/addr.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/* 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 <asm/uaccess.h>
|
||||
|
||||
#include "signaling.h"
|
||||
#include "addr.h"
|
||||
|
||||
static int check_addr(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(struct sockaddr_atmsvc *a, 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(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, 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, 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, struct sockaddr_atmsvc *addr,
|
||||
enum atm_addr_type_t type);
|
||||
int atm_del_addr(struct atm_dev *dev, 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
|
||||
109
net/atm/atm_misc.c
Normal file
109
net/atm/atm_misc.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/* 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 <asm/atomic.h>
|
||||
#include <asm/errno.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;
|
||||
}
|
||||
|
||||
|
||||
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 = atm_guess_pdu2truesize(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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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(atm_charge);
|
||||
EXPORT_SYMBOL(atm_alloc_charge);
|
||||
EXPORT_SYMBOL(atm_pcr_goal);
|
||||
EXPORT_SYMBOL(sonet_copy_stats);
|
||||
EXPORT_SYMBOL(sonet_subtract_stats);
|
||||
184
net/atm/atm_sysfs.c
Normal file
184
net/atm/atm_sysfs.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/* ATM driver model support. */
|
||||
|
||||
#include <linux/kernel.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 class_device *cdev, char *buf)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
return sprintf(buf, "%s\n", adev->type);
|
||||
}
|
||||
|
||||
static ssize_t show_address(struct class_device *cdev, char *buf)
|
||||
{
|
||||
char *pos = buf;
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (ESI_LEN - 1); i++)
|
||||
pos += sprintf(pos, "%02x:", adev->esi[i]);
|
||||
pos += sprintf(pos, "%02x\n", adev->esi[i]);
|
||||
|
||||
return pos - buf;
|
||||
}
|
||||
|
||||
static ssize_t show_atmaddress(struct class_device *cdev, char *buf)
|
||||
{
|
||||
unsigned long flags;
|
||||
char *pos = buf;
|
||||
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;
|
||||
|
||||
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) {
|
||||
pos += sprintf(pos, ".");
|
||||
++fmt;
|
||||
j = 0;
|
||||
}
|
||||
pos += sprintf(pos, "%02x", aaddr->addr.sas_addr.prv[i]);
|
||||
}
|
||||
pos += sprintf(pos, "\n");
|
||||
}
|
||||
spin_unlock_irqrestore(&adev->lock, flags);
|
||||
|
||||
return pos - buf;
|
||||
}
|
||||
|
||||
static ssize_t show_carrier(struct class_device *cdev, char *buf)
|
||||
{
|
||||
char *pos = buf;
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
|
||||
pos += sprintf(pos, "%d\n",
|
||||
adev->signal == ATM_PHY_SIG_LOST ? 0 : 1);
|
||||
|
||||
return pos - buf;
|
||||
}
|
||||
|
||||
static ssize_t show_link_rate(struct class_device *cdev, char *buf)
|
||||
{
|
||||
char *pos = 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;
|
||||
}
|
||||
pos += sprintf(pos, "%d\n", link_rate);
|
||||
|
||||
return pos - buf;
|
||||
}
|
||||
|
||||
static CLASS_DEVICE_ATTR(address, S_IRUGO, show_address, NULL);
|
||||
static CLASS_DEVICE_ATTR(atmaddress, S_IRUGO, show_atmaddress, NULL);
|
||||
static CLASS_DEVICE_ATTR(carrier, S_IRUGO, show_carrier, NULL);
|
||||
static CLASS_DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
|
||||
static CLASS_DEVICE_ATTR(link_rate, S_IRUGO, show_link_rate, NULL);
|
||||
|
||||
static struct class_device_attribute *atm_attrs[] = {
|
||||
&class_device_attr_atmaddress,
|
||||
&class_device_attr_address,
|
||||
&class_device_attr_carrier,
|
||||
&class_device_attr_type,
|
||||
&class_device_attr_link_rate,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int atm_uevent(struct class_device *cdev, char **envp, int num_envp, char *buf, int size)
|
||||
{
|
||||
struct atm_dev *adev;
|
||||
int i = 0, len = 0;
|
||||
|
||||
if (!cdev)
|
||||
return -ENODEV;
|
||||
|
||||
adev = to_atm_dev(cdev);
|
||||
if (!adev)
|
||||
return -ENODEV;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buf, size, &len,
|
||||
"NAME=%s%d", adev->type, adev->number))
|
||||
return -ENOMEM;
|
||||
|
||||
envp[i] = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atm_release(struct class_device *cdev)
|
||||
{
|
||||
struct atm_dev *adev = to_atm_dev(cdev);
|
||||
|
||||
kfree(adev);
|
||||
}
|
||||
|
||||
static struct class atm_class = {
|
||||
.name = "atm",
|
||||
.release = atm_release,
|
||||
.uevent = atm_uevent,
|
||||
};
|
||||
|
||||
int atm_register_sysfs(struct atm_dev *adev)
|
||||
{
|
||||
struct class_device *cdev = &adev->class_dev;
|
||||
int i, j, err;
|
||||
|
||||
cdev->class = &atm_class;
|
||||
class_set_devdata(cdev, adev);
|
||||
|
||||
snprintf(cdev->class_id, BUS_ID_SIZE, "%s%d", adev->type, adev->number);
|
||||
err = class_device_register(cdev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; atm_attrs[i]; i++) {
|
||||
err = class_device_create_file(cdev, atm_attrs[i]);
|
||||
if (err)
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
for (j = 0; j < i; j++)
|
||||
class_device_remove_file(cdev, atm_attrs[j]);
|
||||
class_device_del(cdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
void atm_unregister_sysfs(struct atm_dev *adev)
|
||||
{
|
||||
struct class_device *cdev = &adev->class_dev;
|
||||
|
||||
class_device_del(cdev);
|
||||
}
|
||||
|
||||
int __init atm_sysfs_init(void)
|
||||
{
|
||||
return class_register(&atm_class);
|
||||
}
|
||||
|
||||
void __exit atm_sysfs_exit(void)
|
||||
{
|
||||
class_unregister(&atm_class);
|
||||
}
|
||||
840
net/atm/br2684.c
Normal file
840
net/atm/br2684.c
Normal file
@@ -0,0 +1,840 @@
|
||||
/*
|
||||
Experimental ethernet netdevice using ATM AAL5 as underlying carrier
|
||||
(RFC1483 obsoleted by RFC2684) for Linux 2.4
|
||||
Author: Marcell GAL, 2000, XDSL Ltd, Hungary
|
||||
*/
|
||||
|
||||
#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 <asm/uaccess.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"
|
||||
|
||||
/*
|
||||
* Define this to use a version of the code which interacts with the higher
|
||||
* layers in a more intellegent way, by always reserving enough space for
|
||||
* our header at the begining of the packet. However, there may still be
|
||||
* some problems with programs like tcpdump. In 2.5 we'll sort out what
|
||||
* we need to do to get this perfect. For now we just will copy the packet
|
||||
* if we need space for the header
|
||||
*/
|
||||
/* #define FASTER_VERSION */
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DPRINTK(format, args...) printk(KERN_DEBUG "br2684: " format, ##args)
|
||||
#else
|
||||
#define DPRINTK(format, args...)
|
||||
#endif
|
||||
|
||||
#ifdef SKB_DEBUG
|
||||
static void skb_debug(const struct sk_buff *skb)
|
||||
{
|
||||
#define NUM2PRINT 50
|
||||
char buf[NUM2PRINT * 3 + 1]; /* 3 chars per byte */
|
||||
int i = 0;
|
||||
for (i = 0; i < skb->len && i < NUM2PRINT; i++) {
|
||||
sprintf(buf + i * 3, "%2.2x ", 0xff & skb->data[i]);
|
||||
}
|
||||
printk(KERN_DEBUG "br2684: skb: %s\n", buf);
|
||||
}
|
||||
#else
|
||||
#define skb_debug(skb) do {} while (0)
|
||||
#endif
|
||||
|
||||
static unsigned char llc_oui_pid_pad[] =
|
||||
{ 0xAA, 0xAA, 0x03, 0x00, 0x80, 0xC2, 0x00, 0x07, 0x00, 0x00 };
|
||||
#define PADLEN (2)
|
||||
|
||||
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); */
|
||||
enum br2684_encaps encaps;
|
||||
struct list_head brvccs;
|
||||
#ifdef CONFIG_ATM_BR2684_IPFILTER
|
||||
struct br2684_filter filter;
|
||||
#endif /* CONFIG_ATM_BR2684_IPFILTER */
|
||||
#ifndef FASTER_VERSION
|
||||
unsigned copies_needed, copies_failed;
|
||||
#endif /* FASTER_VERSION */
|
||||
};
|
||||
|
||||
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) */
|
||||
struct net_device_stats stats;
|
||||
int mac_was_set;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 (struct br2684_dev *) net_dev->priv;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 br2684_dev *brdev,
|
||||
struct br2684_vcc *brvcc)
|
||||
{
|
||||
struct atm_vcc *atmvcc;
|
||||
#ifdef FASTER_VERSION
|
||||
if (brvcc->encaps == e_llc)
|
||||
memcpy(skb_push(skb, 8), llc_oui_pid_pad, 8);
|
||||
/* last 2 bytes of llc_oui_pid_pad are managed by header routines;
|
||||
yes, you got it: 8 + 2 = sizeof(llc_oui_pid_pad)
|
||||
*/
|
||||
#else
|
||||
int minheadroom = (brvcc->encaps == e_llc) ? 10 : 2;
|
||||
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;
|
||||
}
|
||||
skb_push(skb, minheadroom);
|
||||
if (brvcc->encaps == e_llc)
|
||||
memcpy(skb->data, llc_oui_pid_pad, 10);
|
||||
else
|
||||
memset(skb->data, 0, 2);
|
||||
#endif /* FASTER_VERSION */
|
||||
skb_debug(skb);
|
||||
|
||||
ATM_SKB(skb)->vcc = atmvcc = brvcc->atmvcc;
|
||||
DPRINTK("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, atmvcc, atmvcc->dev);
|
||||
if (!atm_may_send(atmvcc, skb->truesize)) {
|
||||
/* we free this here for now, because we cannot know in a higher
|
||||
layer whether the skb point it supplied wasn't freed yet.
|
||||
now, it always is.
|
||||
*/
|
||||
dev_kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
atomic_add(skb->truesize, &sk_atm(atmvcc)->sk_wmem_alloc);
|
||||
ATM_SKB(skb)->atm_options = atmvcc->atm_options;
|
||||
brdev->stats.tx_packets++;
|
||||
brdev->stats.tx_bytes += skb->len;
|
||||
atmvcc->send(atmvcc, skb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline struct br2684_vcc *pick_outgoing_vcc(struct sk_buff *skb,
|
||||
struct br2684_dev *brdev)
|
||||
{
|
||||
return list_empty(&brdev->brvccs) ? NULL :
|
||||
list_entry_brvcc(brdev->brvccs.next); /* 1 vcc/dev right now */
|
||||
}
|
||||
|
||||
static int br2684_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct br2684_dev *brdev = BRPRIV(dev);
|
||||
struct br2684_vcc *brvcc;
|
||||
|
||||
DPRINTK("br2684_start_xmit, skb->dst=%p\n", skb->dst);
|
||||
read_lock(&devs_lock);
|
||||
brvcc = pick_outgoing_vcc(skb, brdev);
|
||||
if (brvcc == NULL) {
|
||||
DPRINTK("no vcc attached to dev %s\n", dev->name);
|
||||
brdev->stats.tx_errors++;
|
||||
brdev->stats.tx_carrier_errors++;
|
||||
/* netif_stop_queue(dev); */
|
||||
dev_kfree_skb(skb);
|
||||
read_unlock(&devs_lock);
|
||||
return 0;
|
||||
}
|
||||
if (!br2684_xmit_vcc(skb, brdev, 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_kfree_skb(skb);
|
||||
*/
|
||||
brdev->stats.tx_errors++;
|
||||
brdev->stats.tx_fifo_errors++;
|
||||
}
|
||||
read_unlock(&devs_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct net_device_stats *br2684_get_stats(struct net_device *dev)
|
||||
{
|
||||
DPRINTK("br2684_get_stats\n");
|
||||
return &BRPRIV(dev)->stats;
|
||||
}
|
||||
|
||||
#ifdef FASTER_VERSION
|
||||
/*
|
||||
* These mirror eth_header and eth_header_cache. They are not usually
|
||||
* exported for use in modules, so we grab them from net_device
|
||||
* after ether_setup() is done with it. Bit of a hack.
|
||||
*/
|
||||
static int (*my_eth_header)(struct sk_buff *, struct net_device *,
|
||||
unsigned short, void *, void *, unsigned);
|
||||
static int (*my_eth_header_cache)(struct neighbour *, struct hh_cache *);
|
||||
|
||||
static int
|
||||
br2684_header(struct sk_buff *skb, struct net_device *dev,
|
||||
unsigned short type, void *daddr, void *saddr, unsigned len)
|
||||
{
|
||||
u16 *pad_before_eth;
|
||||
int t = my_eth_header(skb, dev, type, daddr, saddr, len);
|
||||
if (t > 0) {
|
||||
pad_before_eth = (u16 *) skb_push(skb, 2);
|
||||
*pad_before_eth = 0;
|
||||
return dev->hard_header_len; /* or return 16; ? */
|
||||
} else
|
||||
return t;
|
||||
}
|
||||
|
||||
static int
|
||||
br2684_header_cache(struct neighbour *neigh, struct hh_cache *hh)
|
||||
{
|
||||
/* hh_data is 16 bytes long. if encaps is ether-llc we need 24, so
|
||||
xmit will add the additional header part in that case */
|
||||
u16 *pad_before_eth = (u16 *)(hh->hh_data);
|
||||
int t = my_eth_header_cache(neigh, hh);
|
||||
DPRINTK("br2684_header_cache, neigh=%p, hh_cache=%p\n", neigh, hh);
|
||||
if (t < 0)
|
||||
return t;
|
||||
else {
|
||||
*pad_before_eth = 0;
|
||||
hh->hh_len = PADLEN + ETH_HLEN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is similar to eth_type_trans, which cannot be used because of
|
||||
* our dev->hard_header_len
|
||||
*/
|
||||
static inline __be16 br_type_trans(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct ethhdr *eth;
|
||||
unsigned char *rawp;
|
||||
eth = eth_hdr(skb);
|
||||
|
||||
if (is_multicast_ether_addr(eth->h_dest)) {
|
||||
if (!compare_ether_addr(eth->h_dest, dev->broadcast))
|
||||
skb->pkt_type = PACKET_BROADCAST;
|
||||
else
|
||||
skb->pkt_type = PACKET_MULTICAST;
|
||||
}
|
||||
|
||||
else if (compare_ether_addr(eth->h_dest, dev->dev_addr))
|
||||
skb->pkt_type = PACKET_OTHERHOST;
|
||||
|
||||
if (ntohs(eth->h_proto) >= 1536)
|
||||
return eth->h_proto;
|
||||
|
||||
rawp = skb->data;
|
||||
|
||||
/*
|
||||
* This is a magic hack to spot IPX packets. Older Novell breaks
|
||||
* the protocol design and runs IPX over 802.3 without an 802.2 LLC
|
||||
* layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
|
||||
* won't work for fault tolerant netware but does for the rest.
|
||||
*/
|
||||
if (*(unsigned short *) rawp == 0xFFFF)
|
||||
return htons(ETH_P_802_3);
|
||||
|
||||
/*
|
||||
* Real 802.2 LLC
|
||||
*/
|
||||
return htons(ETH_P_802_2);
|
||||
}
|
||||
#endif /* FASTER_VERSION */
|
||||
|
||||
/*
|
||||
* 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 (*my_eth_mac_addr)(struct net_device *, void *);
|
||||
static int br2684_mac_addr(struct net_device *dev, void *p)
|
||||
{
|
||||
int err = my_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 == __constant_htons(ETH_P_IP) &&
|
||||
(((struct iphdr *) (skb->data))->daddr & brvcc->filter.
|
||||
netmask) == brvcc->filter.prefix)
|
||||
return 0;
|
||||
if (type == __constant_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)
|
||||
{
|
||||
DPRINTK("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->old_push(brvcc->atmvcc, NULL); /* pass on the bad news */
|
||||
kfree(brvcc);
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
int plen = sizeof(llc_oui_pid_pad) + ETH_HLEN;
|
||||
|
||||
DPRINTK("br2684_push\n");
|
||||
|
||||
if (unlikely(skb == NULL)) {
|
||||
/* skb==NULL means VCC is being destroyed */
|
||||
br2684_close_vcc(brvcc);
|
||||
if (list_empty(&brdev->brvccs)) {
|
||||
read_lock(&devs_lock);
|
||||
list_del(&brdev->br2684_devs);
|
||||
read_unlock(&devs_lock);
|
||||
unregister_netdev(net_dev);
|
||||
free_netdev(net_dev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
skb_debug(skb);
|
||||
atm_return(atmvcc, skb->truesize);
|
||||
DPRINTK("skb from brdev %p\n", brdev);
|
||||
if (brvcc->encaps == e_llc) {
|
||||
/* 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) */
|
||||
if (memcmp(skb->data, llc_oui_pid_pad, 7)) {
|
||||
brdev->stats.rx_errors++;
|
||||
dev_kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Strip FCS if present */
|
||||
if (skb->len > 7 && skb->data[7] == 0x01)
|
||||
__skb_trim(skb, skb->len - 4);
|
||||
} else {
|
||||
plen = PADLEN + ETH_HLEN; /* pad, dstmac,srcmac, ethtype */
|
||||
/* first 2 chars should be 0 */
|
||||
if (*((u16 *) (skb->data)) != 0) {
|
||||
brdev->stats.rx_errors++;
|
||||
dev_kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (skb->len < plen) {
|
||||
brdev->stats.rx_errors++;
|
||||
dev_kfree_skb(skb); /* dev_ not needed? */
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef FASTER_VERSION
|
||||
/* FIXME: tcpdump shows that pointer to mac header is 2 bytes earlier,
|
||||
than should be. What else should I set? */
|
||||
skb_pull(skb, plen);
|
||||
skb->mac.raw = ((char *) (skb->data)) - ETH_HLEN;
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
#ifdef CONFIG_BR2684_FAST_TRANS
|
||||
skb->protocol = ((u16 *) skb->data)[-1];
|
||||
#else /* some protocols might require this: */
|
||||
skb->protocol = br_type_trans(skb, net_dev);
|
||||
#endif /* CONFIG_BR2684_FAST_TRANS */
|
||||
#else
|
||||
skb_pull(skb, plen - ETH_HLEN);
|
||||
skb->protocol = eth_type_trans(skb, net_dev);
|
||||
#endif /* FASTER_VERSION */
|
||||
#ifdef CONFIG_ATM_BR2684_IPFILTER
|
||||
if (unlikely(packet_fails_filter(skb->protocol, brvcc, skb))) {
|
||||
brdev->stats.rx_dropped++;
|
||||
dev_kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
#endif /* CONFIG_ATM_BR2684_IPFILTER */
|
||||
skb->dev = net_dev;
|
||||
ATM_SKB(skb)->vcc = atmvcc; /* needed ? */
|
||||
DPRINTK("received packet's protocol: %x\n", ntohs(skb->protocol));
|
||||
skb_debug(skb);
|
||||
if (unlikely(!(net_dev->flags & IFF_UP))) {
|
||||
/* sigh, interface is down */
|
||||
brdev->stats.rx_dropped++;
|
||||
dev_kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
brdev->stats.rx_packets++;
|
||||
brdev->stats.rx_bytes += skb->len;
|
||||
memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data));
|
||||
netif_rx(skb);
|
||||
}
|
||||
|
||||
static int br2684_regvcc(struct atm_vcc *atmvcc, void __user *arg)
|
||||
{
|
||||
/* assign a vcc to a dev
|
||||
Note: we do not have explicit unassign, but look at _push()
|
||||
*/
|
||||
int err;
|
||||
struct br2684_vcc *brvcc;
|
||||
struct sk_buff *skb;
|
||||
struct sk_buff_head *rq;
|
||||
struct br2684_dev *brdev;
|
||||
struct net_device *net_dev;
|
||||
struct atm_backend_br2684 be;
|
||||
unsigned long flags;
|
||||
|
||||
if (copy_from_user(&be, arg, sizeof be))
|
||||
return -EFAULT;
|
||||
brvcc = kzalloc(sizeof(struct br2684_vcc), GFP_KERNEL);
|
||||
if (!brvcc)
|
||||
return -ENOMEM;
|
||||
write_lock_irq(&devs_lock);
|
||||
net_dev = br2684_find_dev(&be.ifspec);
|
||||
if (net_dev == NULL) {
|
||||
printk(KERN_ERR
|
||||
"br2684: tried to attach to non-existant 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;
|
||||
}
|
||||
DPRINTK("br2684_regvcc 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;
|
||||
barrier();
|
||||
atmvcc->push = br2684_push;
|
||||
|
||||
rq = &sk_atm(atmvcc)->sk_receive_queue;
|
||||
|
||||
spin_lock_irqsave(&rq->lock, flags);
|
||||
if (skb_queue_empty(rq)) {
|
||||
skb = NULL;
|
||||
} else {
|
||||
/* NULL terminate the list. */
|
||||
rq->prev->next = NULL;
|
||||
skb = rq->next;
|
||||
}
|
||||
rq->prev = rq->next = (struct sk_buff *)rq;
|
||||
rq->qlen = 0;
|
||||
spin_unlock_irqrestore(&rq->lock, flags);
|
||||
|
||||
while (skb) {
|
||||
struct sk_buff *next = skb->next;
|
||||
|
||||
skb->next = skb->prev = NULL;
|
||||
BRPRIV(skb->dev)->stats.rx_bytes -= skb->len;
|
||||
BRPRIV(skb->dev)->stats.rx_packets--;
|
||||
br2684_push(atmvcc, skb);
|
||||
|
||||
skb = next;
|
||||
}
|
||||
__module_get(THIS_MODULE);
|
||||
return 0;
|
||||
error:
|
||||
write_unlock_irq(&devs_lock);
|
||||
kfree(brvcc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void br2684_setup(struct net_device *netdev)
|
||||
{
|
||||
struct br2684_dev *brdev = BRPRIV(netdev);
|
||||
|
||||
ether_setup(netdev);
|
||||
brdev->net_dev = netdev;
|
||||
|
||||
#ifdef FASTER_VERSION
|
||||
my_eth_header = netdev->hard_header;
|
||||
netdev->hard_header = br2684_header;
|
||||
my_eth_header_cache = netdev->hard_header_cache;
|
||||
netdev->hard_header_cache = br2684_header_cache;
|
||||
netdev->hard_header_len = sizeof(llc_oui_pid_pad) + ETH_HLEN; /* 10 + 14 */
|
||||
#endif
|
||||
my_eth_mac_addr = netdev->set_mac_address;
|
||||
netdev->set_mac_address = br2684_mac_addr;
|
||||
netdev->hard_start_xmit = br2684_start_xmit;
|
||||
netdev->get_stats = br2684_get_stats;
|
||||
|
||||
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;
|
||||
|
||||
DPRINTK("br2684_create\n");
|
||||
|
||||
if (copy_from_user(&ni, arg, sizeof ni)) {
|
||||
return -EFAULT;
|
||||
}
|
||||
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",
|
||||
br2684_setup);
|
||||
if (!netdev)
|
||||
return -ENOMEM;
|
||||
|
||||
brdev = BRPRIV(netdev);
|
||||
|
||||
DPRINTK("registered netdev %s\n", netdev->name);
|
||||
/* open, stop, do_ioctl ? */
|
||||
err = register_netdev(netdev);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "br2684_create: register_netdev failed\n");
|
||||
free_netdev(netdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
write_lock_irq(&devs_lock);
|
||||
brdev->number = list_empty(&br2684_devs) ? 1 :
|
||||
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;
|
||||
|
||||
int err;
|
||||
switch(cmd) {
|
||||
case ATM_SETBACKEND:
|
||||
case ATM_NEWBACKENDIF: {
|
||||
atm_backend_t b;
|
||||
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)
|
||||
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)
|
||||
{
|
||||
loff_t offs = 0;
|
||||
struct br2684_dev *brd;
|
||||
|
||||
read_lock(&devs_lock);
|
||||
|
||||
list_for_each_entry(brd, &br2684_devs, br2684_devs) {
|
||||
if (offs == *pos)
|
||||
return brd;
|
||||
++offs;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *br2684_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
||||
{
|
||||
struct br2684_dev *brd = v;
|
||||
|
||||
++*pos;
|
||||
|
||||
brd = list_entry(brd->br2684_devs.next,
|
||||
struct br2684_dev, br2684_devs);
|
||||
return (&brd->br2684_devs != &br2684_devs) ? brd : NULL;
|
||||
}
|
||||
|
||||
static void br2684_seq_stop(struct seq_file *seq, void *v)
|
||||
{
|
||||
read_unlock(&devs_lock);
|
||||
}
|
||||
|
||||
static int br2684_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
const struct br2684_dev *brdev = v;
|
||||
const struct net_device *net_dev = brdev->net_dev;
|
||||
const struct br2684_vcc *brvcc;
|
||||
|
||||
seq_printf(seq, "dev %.16s: num=%d, mac=%02X:%02X:"
|
||||
"%02X:%02X:%02X:%02X (%s)\n", net_dev->name,
|
||||
brdev->number,
|
||||
net_dev->dev_addr[0],
|
||||
net_dev->dev_addr[1],
|
||||
net_dev->dev_addr[2],
|
||||
net_dev->dev_addr[3],
|
||||
net_dev->dev_addr[4],
|
||||
net_dev->dev_addr[5],
|
||||
brdev->mac_was_set ? "set" : "auto");
|
||||
|
||||
list_for_each_entry(brvcc, &brdev->brvccs, brvccs) {
|
||||
seq_printf(seq, " vcc %d.%d.%d: encaps=%s"
|
||||
#ifndef FASTER_VERSION
|
||||
", failed copies %u/%u"
|
||||
#endif /* FASTER_VERSION */
|
||||
"\n", brvcc->atmvcc->dev->number,
|
||||
brvcc->atmvcc->vpi, brvcc->atmvcc->vci,
|
||||
(brvcc->encaps == e_llc) ? "LLC" : "VC"
|
||||
#ifndef FASTER_VERSION
|
||||
, brvcc->copies_failed
|
||||
, brvcc->copies_needed
|
||||
#endif /* FASTER_VERSION */
|
||||
);
|
||||
#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 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
|
||||
|
||||
static int __init br2684_init(void)
|
||||
{
|
||||
#ifdef CONFIG_PROC_FS
|
||||
struct proc_dir_entry *p;
|
||||
if ((p = create_proc_entry("br2684", 0, atm_proc_root)) == NULL)
|
||||
return -ENOMEM;
|
||||
p->proc_fops = &br2684_proc_ops;
|
||||
#endif
|
||||
register_atm_ioctl(&br2684_ioctl_ops);
|
||||
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
|
||||
|
||||
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");
|
||||
1038
net/atm/clip.c
Normal file
1038
net/atm/clip.c
Normal file
File diff suppressed because it is too large
Load Diff
825
net/atm/common.c
Normal file
825
net/atm/common.c
Normal file
@@ -0,0 +1,825 @@
|
||||
/* net/atm/common.c - ATM sockets (common part for PVC and SVC) */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#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 <net/sock.h> /* struct sock */
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/atomic.h>
|
||||
#include <asm/poll.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 */
|
||||
|
||||
|
||||
#if 0
|
||||
#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
|
||||
#else
|
||||
#define DPRINTK(format,args...)
|
||||
#endif
|
||||
|
||||
struct hlist_head vcc_hash[VCC_HTABLE_SIZE];
|
||||
DEFINE_RWLOCK(vcc_sklist_lock);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 (atomic_read(&sk->sk_wmem_alloc) && !atm_may_send(vcc, size)) {
|
||||
DPRINTK("Sorry: wmem_alloc = %d, size = %d, sndbuf = %d\n",
|
||||
atomic_read(&sk->sk_wmem_alloc), size,
|
||||
sk->sk_sndbuf);
|
||||
return NULL;
|
||||
}
|
||||
while (!(skb = alloc_skb(size,GFP_KERNEL))) schedule();
|
||||
DPRINTK("AlTx %d += %d\n", atomic_read(&sk->sk_wmem_alloc),
|
||||
skb->truesize);
|
||||
atomic_add(skb->truesize, &sk->sk_wmem_alloc);
|
||||
return skb;
|
||||
}
|
||||
|
||||
|
||||
EXPORT_SYMBOL(vcc_hash);
|
||||
EXPORT_SYMBOL(vcc_sklist_lock);
|
||||
EXPORT_SYMBOL(vcc_insert_socket);
|
||||
|
||||
static void vcc_sock_destruct(struct sock *sk)
|
||||
{
|
||||
if (atomic_read(&sk->sk_rmem_alloc))
|
||||
printk(KERN_DEBUG "vcc_sock_destruct: rmem leakage (%d bytes) detected.\n", atomic_read(&sk->sk_rmem_alloc));
|
||||
|
||||
if (atomic_read(&sk->sk_wmem_alloc))
|
||||
printk(KERN_DEBUG "vcc_sock_destruct: wmem leakage (%d bytes) detected.\n", atomic_read(&sk->sk_wmem_alloc));
|
||||
}
|
||||
|
||||
static void vcc_def_wakeup(struct sock *sk)
|
||||
{
|
||||
read_lock(&sk->sk_callback_lock);
|
||||
if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
|
||||
wake_up(sk->sk_sleep);
|
||||
read_unlock(&sk->sk_callback_lock);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
read_lock(&sk->sk_callback_lock);
|
||||
|
||||
if (vcc_writable(sk)) {
|
||||
if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
|
||||
wake_up_interruptible(sk->sk_sleep);
|
||||
|
||||
sk_wake_async(sk, 2, POLL_OUT);
|
||||
}
|
||||
|
||||
read_unlock(&sk->sk_callback_lock);
|
||||
}
|
||||
|
||||
static struct proto vcc_proto = {
|
||||
.name = "VCC",
|
||||
.owner = THIS_MODULE,
|
||||
.obj_size = sizeof(struct atm_vcc),
|
||||
};
|
||||
|
||||
int vcc_create(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(family, GFP_KERNEL, &vcc_proto, 1);
|
||||
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, 0);
|
||||
atomic_set(&sk->sk_rmem_alloc, 0);
|
||||
vcc->push = NULL;
|
||||
vcc->pop = NULL;
|
||||
vcc->push_oam = 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 */
|
||||
|
||||
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 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 *node, *tmp;
|
||||
struct sock *s;
|
||||
struct atm_vcc *vcc;
|
||||
|
||||
sk_for_each_safe(s, node, 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);
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
printk(KERN_WARNING "ATM: 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(struct atm_vcc *vcc, short vpi, int vci)
|
||||
{
|
||||
struct hlist_head *head = &vcc_hash[vci &
|
||||
(VCC_HTABLE_SIZE - 1)];
|
||||
struct hlist_node *node;
|
||||
struct sock *s;
|
||||
struct atm_vcc *walk;
|
||||
|
||||
sk_for_each(s, node, 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(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;
|
||||
DPRINTK("VCC %d.%d, AAL %d\n",vpi,vci,vcc->qos.aal);
|
||||
DPRINTK(" 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);
|
||||
DPRINTK(" 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) {
|
||||
if ((error = dev->ops->open(vcc)))
|
||||
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;
|
||||
|
||||
DPRINTK("vcc_connect (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;
|
||||
DPRINTK("vcc_connect (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;
|
||||
if (flags & ~MSG_DONTWAIT) /* only handle MSG_DONTWAIT */
|
||||
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_timestamp(msg, sk, skb);
|
||||
DPRINTK("RcvM %d -= %d\n", atomic_read(&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->sk_sleep, &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->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &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;
|
||||
|
||||
poll_wait(file, sk->sk_sleep, 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(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(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, 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;
|
||||
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);
|
||||
}
|
||||
|
||||
static int __init atm_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if ((error = proto_register(&vcc_proto, 0)) < 0)
|
||||
goto out;
|
||||
|
||||
if ((error = atmpvc_init()) < 0) {
|
||||
printk(KERN_ERR "atmpvc_init() failed with %d\n", error);
|
||||
goto out_unregister_vcc_proto;
|
||||
}
|
||||
if ((error = atmsvc_init()) < 0) {
|
||||
printk(KERN_ERR "atmsvc_init() failed with %d\n", error);
|
||||
goto out_atmpvc_exit;
|
||||
}
|
||||
if ((error = atm_proc_init()) < 0) {
|
||||
printk(KERN_ERR "atm_proc_init() failed with %d\n",error);
|
||||
goto out_atmsvc_exit;
|
||||
}
|
||||
if ((error = atm_sysfs_init()) < 0) {
|
||||
printk(KERN_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);
|
||||
54
net/atm/common.h
Normal file
54
net/atm/common.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/* 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 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_setsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int optlen);
|
||||
int vcc_getsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, int __user *optlen);
|
||||
|
||||
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
|
||||
159
net/atm/ioctl.c
Normal file
159
net/atm/ioctl.c
Normal file
@@ -0,0 +1,159 @@
|
||||
/* ATM ioctl handling */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
/* 2003 John Levon <levon@movementarian.org> */
|
||||
|
||||
|
||||
#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 "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);
|
||||
}
|
||||
|
||||
void deregister_atm_ioctl(struct atm_ioctl *ioctl)
|
||||
{
|
||||
mutex_lock(&ioctl_mutex);
|
||||
list_del(&ioctl->list);
|
||||
mutex_unlock(&ioctl_mutex);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(register_atm_ioctl);
|
||||
EXPORT_SYMBOL(deregister_atm_ioctl);
|
||||
|
||||
int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
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 -
|
||||
atomic_read(&sk->sk_wmem_alloc),
|
||||
(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 */
|
||||
error = sock_get_timestamp(sk, argp);
|
||||
goto done;
|
||||
case ATM_SETSC:
|
||||
printk(KERN_WARNING "ATM_SETSC is obsolete\n");
|
||||
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 privledges that /proc/kcore needs
|
||||
*/
|
||||
if (!capable(CAP_SYS_RAWIO)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
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);
|
||||
|
||||
done:
|
||||
return error;
|
||||
}
|
||||
2520
net/atm/lec.c
Normal file
2520
net/atm/lec.c
Normal file
File diff suppressed because it is too large
Load Diff
156
net/atm/lec.h
Normal file
156
net/atm/lec.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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 intented 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, u8 *dst_mac, int force,
|
||||
u8 **tlvs, u32 *sizeoftlvs);
|
||||
int (*associate_req) (struct net_device *dev, u8 *lan_dst,
|
||||
u8 *tlvs, u32 sizeoftlvs);
|
||||
void (*associate_indicator) (struct net_device *dev, u8 *mac_addr,
|
||||
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 immediatelly 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 1536(0x0600) MUST be encoded by placing that length
|
||||
* in the h_type field. The LLC field follows header immediatelly.
|
||||
* 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 {
|
||||
struct net_device_stats stats;
|
||||
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 */
|
||||
int is_trdev; /* Device type, 0 = Ethernet, 1 = TokenRing */
|
||||
};
|
||||
|
||||
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_ */
|
||||
1488
net/atm/mpc.c
Normal file
1488
net/atm/mpc.c
Normal file
File diff suppressed because it is too large
Load Diff
61
net/atm/mpc.h
Normal file
61
net/atm/mpc.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#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 */
|
||||
int (*old_hard_start_xmit)(struct sk_buff *skb, struct net_device *dev);
|
||||
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 */
|
||||
};
|
||||
|
||||
|
||||
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_ */
|
||||
564
net/atm/mpoa_caches.c
Normal file
564
net/atm/mpoa_caches.c
Normal file
@@ -0,0 +1,564 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/atmmpc.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 printk /* debug */
|
||||
#else
|
||||
#define dprintk(format,args...)
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define ddprintk printk /* more debug */
|
||||
#else
|
||||
#define ddprintk(format,args...)
|
||||
#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) {
|
||||
printk("mpoa: mpoa_caches.c: new_in_cache_entry: out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dprintk("mpoa: mpoa_caches.c: adding an ingress entry, ip = %u.%u.%u.%u\n", NIPQUAD(dst_ip));
|
||||
|
||||
atomic_set(&entry->use, 1);
|
||||
dprintk("mpoa: mpoa_caches.c: 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("mpoa: mpoa_caches.c: 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("mpoa: (%s) mpoa_caches.c: threshold exceeded for ip %u.%u.%u.%u, sending MPOA res req\n", mpc->dev->name, NIPQUAD(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);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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("mpoa: mpoa_caches.c: removing an ingress entry, ip = %u.%u.%u.%u\n",NIPQUAD(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);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* 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("mpoa: mpoa_caches.c: holding time expired, ip = %u.%u.%u.%u\n", NIPQUAD(entry->ctrl_info.in_dst_ip));
|
||||
client->in_ops->remove_entry(entry, client);
|
||||
}
|
||||
entry = next_entry;
|
||||
}
|
||||
write_unlock_bh(&client->ingress_lock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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 );
|
||||
if(entry->retry_time > client->parameters.mpc_p5){
|
||||
/* Retry time maximum exceeded, put entry in hold down. */
|
||||
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("mpoa: mpoa_caches.c: 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("mpoa: mpoa_caches.c: 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);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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("mpoa: mpoa_caches.c: 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);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
printk("mpoa: mpoa_caches.c: new_eg_cache_entry: out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dprintk("mpoa: mpoa_caches.c: adding an egress entry, ip = %u.%u.%u.%u, this should be our IP\n", NIPQUAD(msg->content.eg_info.eg_dst_ip));
|
||||
|
||||
atomic_set(&entry->use, 1);
|
||||
dprintk("mpoa: mpoa_caches.c: 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("mpoa: mpoa_caches.c: new_eg_cache_entry cache_id %lu\n", ntohl(entry->ctrl_info.cache_id));
|
||||
dprintk("mpoa: mpoa_caches.c: mps_ip = %u.%u.%u.%u\n",
|
||||
NIPQUAD(entry->ctrl_info.mps_ip));
|
||||
atomic_inc(&entry->use);
|
||||
|
||||
write_unlock_irq(&client->egress_lock);
|
||||
dprintk("mpoa: mpoa_caches.c: 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;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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("mpoa: mpoa_caches.c: egress_cache: holding time expired, cache_id = %lu.\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);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
|
||||
return;
|
||||
}
|
||||
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
|
||||
304
net/atm/mpoa_proc.c
Normal file
304
net/atm/mpoa_proc.c
Normal file
@@ -0,0 +1,304 @@
|
||||
|
||||
#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 <asm/uaccess.h>
|
||||
#include <linux/atmmpc.h>
|
||||
#include <linux/atm.h>
|
||||
#include "mpc.h"
|
||||
#include "mpoa_caches.h"
|
||||
|
||||
/*
|
||||
* mpoa_proc.c: Implementation MPOA client's proc
|
||||
* file system statistics
|
||||
*/
|
||||
|
||||
#if 1
|
||||
#define dprintk printk /* debug */
|
||||
#else
|
||||
#define dprintk(format,args...)
|
||||
#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 ";
|
||||
break;
|
||||
case INGRESS_RESOLVED:
|
||||
return "resolved ";
|
||||
break;
|
||||
case INGRESS_INVALID:
|
||||
return "invalid ";
|
||||
break;
|
||||
case INGRESS_REFRESHING:
|
||||
return "refreshing ";
|
||||
break;
|
||||
default:
|
||||
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 ";
|
||||
break;
|
||||
case EGRESS_PURGE:
|
||||
return "purge ";
|
||||
break;
|
||||
case EGRESS_INVALID:
|
||||
return "invalid ";
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
unsigned char *temp;
|
||||
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) {
|
||||
temp = (unsigned char *)&in_entry->ctrl_info.in_dst_ip;
|
||||
sprintf(ip_string,"%d.%d.%d.%d", temp[0], temp[1], temp[2], temp[3]);
|
||||
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 */
|
||||
temp = (unsigned char *)&eg_entry->latest_ip_addr;
|
||||
sprintf(ip_string, "%d.%d.%d.%d", temp[0], temp[1], temp[2], temp[3]);
|
||||
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 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 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("mpoa: mpoa_proc.c: 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 = create_proc_entry(STAT_FILE_NAME, 0, atm_proc_root);
|
||||
if (!p) {
|
||||
printk(KERN_ERR "Unable to initialize /proc/atm/%s\n", STAT_FILE_NAME);
|
||||
return -ENOMEM;
|
||||
}
|
||||
p->proc_fops = &mpc_file_operations;
|
||||
p->owner = THIS_MODULE;
|
||||
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 */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
368
net/atm/pppoatm.c
Normal file
368
net/atm/pppoatm.c
Normal file
@@ -0,0 +1,368 @@
|
||||
/* 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
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/atm.h>
|
||||
#include <linux/atmdev.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/ppp_defs.h>
|
||||
#include <linux/if_ppp.h>
|
||||
#include <linux/ppp_channel.h>
|
||||
#include <linux/atmppp.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#if 0
|
||||
#define DPRINTK(format, args...) \
|
||||
printk(KERN_DEBUG "pppoatm: " format, ##args)
|
||||
#else
|
||||
#define DPRINTK(format, args...)
|
||||
#endif
|
||||
|
||||
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 *);
|
||||
/* keep old push/pop for detaching */
|
||||
enum pppoatm_encaps encaps;
|
||||
int flags; /* SC_COMP_PROT - compress protocol */
|
||||
struct ppp_channel chan; /* interface to generic ppp layer */
|
||||
struct tasklet_struct wakeup_tasklet;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
/*
|
||||
* We don't really always want to do this since it's
|
||||
* really inefficient - it would be much better if we could
|
||||
* test if we had actually throttled the generic layer.
|
||||
* Unfortunately then there would be a nasty SMP race where
|
||||
* we could clear that flag just as we refuse another packet.
|
||||
* For now we do the safe thing.
|
||||
*/
|
||||
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;
|
||||
tasklet_kill(&pvcc->wakeup_tasklet);
|
||||
ppp_unregister_channel(&pvcc->chan);
|
||||
atmvcc->user_back = NULL;
|
||||
kfree(pvcc);
|
||||
/* Gee, I hope we have the big kernel lock here... */
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
DPRINTK("pppoatm push\n");
|
||||
if (skb == NULL) { /* VCC was closed */
|
||||
DPRINTK("removing ATMPPP VCC %p\n", pvcc);
|
||||
pppoatm_unassign_vcc(atmvcc);
|
||||
atmvcc->push(atmvcc, NULL); /* Pass along bad news */
|
||||
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;
|
||||
}
|
||||
DPRINTK("(unit %d): Couldn't autodetect yet "
|
||||
"(skb: %02X %02X %02X %02X %02X %02X)\n",
|
||||
pvcc->chan.unit,
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
ATM_SKB(skb)->vcc = pvcc->atmvcc;
|
||||
DPRINTK("(unit %d): pppoatm_send (skb=0x%p, vcc=0x%p)\n",
|
||||
pvcc->chan.unit, skb, pvcc->atmvcc);
|
||||
if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT))
|
||||
(void) skb_pull(skb, 1);
|
||||
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 &&
|
||||
!atm_may_send(pvcc->atmvcc, n->truesize)) {
|
||||
kfree_skb(n);
|
||||
goto nospace;
|
||||
}
|
||||
kfree_skb(skb);
|
||||
if ((skb = n) == NULL)
|
||||
return DROP_PACKET;
|
||||
} else if (!atm_may_send(pvcc->atmvcc, skb->truesize))
|
||||
goto nospace;
|
||||
memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN);
|
||||
break;
|
||||
case e_vc:
|
||||
if (!atm_may_send(pvcc->atmvcc, skb->truesize))
|
||||
goto nospace;
|
||||
break;
|
||||
case e_autodetect:
|
||||
DPRINTK("(unit %d): Trying to send without setting encaps!\n",
|
||||
pvcc->chan.unit);
|
||||
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;
|
||||
DPRINTK("(unit %d): atm_skb(%p)->vcc(%p)->dev(%p)\n",
|
||||
pvcc->chan.unit, skb, ATM_SKB(skb)->vcc,
|
||||
ATM_SKB(skb)->vcc->dev);
|
||||
return ATM_SKB(skb)->vcc->send(ATM_SKB(skb)->vcc, skb)
|
||||
? DROP_PACKET : 1;
|
||||
nospace:
|
||||
/*
|
||||
* 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;
|
||||
pvcc->old_push = atmvcc->push;
|
||||
pvcc->old_pop = atmvcc->pop;
|
||||
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;
|
||||
if ((err = ppp_register_channel(&pvcc->chan)) != 0) {
|
||||
kfree(pvcc);
|
||||
return err;
|
||||
}
|
||||
atmvcc->user_back = pvcc;
|
||||
atmvcc->push = pppoatm_push;
|
||||
atmvcc->pop = pppoatm_pop;
|
||||
__module_get(THIS_MODULE);
|
||||
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;
|
||||
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");
|
||||
513
net/atm/proc.c
Normal file
513
net/atm/proc.c
Normal file
@@ -0,0 +1,513 @@
|
||||
/* 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 <net/atmclip.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/atomic.h>
|
||||
#include <asm/param.h> /* for HZ */
|
||||
#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,
|
||||
};
|
||||
|
||||
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 == (void *)1) {
|
||||
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 = (void *)1;
|
||||
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, struct seq_operations *ops)
|
||||
{
|
||||
struct vcc_state *state;
|
||||
struct seq_file *seq;
|
||||
int rc = -ENOMEM;
|
||||
|
||||
state = kmalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
goto out;
|
||||
|
||||
rc = seq_open(file, ops);
|
||||
if (rc)
|
||||
goto out_kfree;
|
||||
|
||||
state->family = family;
|
||||
|
||||
seq = file->private_data;
|
||||
seq->private = state;
|
||||
out:
|
||||
return rc;
|
||||
out_kfree:
|
||||
kfree(state);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int vcc_seq_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_release_private(inode, file);
|
||||
}
|
||||
|
||||
static void *vcc_seq_start(struct seq_file *seq, loff_t *pos)
|
||||
{
|
||||
struct vcc_state *state = seq->private;
|
||||
loff_t left = *pos;
|
||||
|
||||
read_lock(&vcc_sklist_lock);
|
||||
state->sk = (void *)1;
|
||||
return left ? vcc_walk(state, left) : (void *)1;
|
||||
}
|
||||
|
||||
static void vcc_seq_stop(struct seq_file *seq, void *v)
|
||||
{
|
||||
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 *class_name[] = { "off","UBR","CBR","VBR","ABR" };
|
||||
static const char *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 >= sizeof(aal_name)/sizeof(aal_name[0]) ? "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 *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, "%p ", 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,
|
||||
atomic_read(&sk->sk_wmem_alloc), sk->sk_sndbuf,
|
||||
atomic_read(&sk->sk_rmem_alloc), 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@%p%10s" : "N/A@%p%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 == (void *)1)
|
||||
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 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 == (void *)1)
|
||||
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 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 = vcc_seq_release,
|
||||
};
|
||||
|
||||
static int vcc_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
if (v == (void *)1) {
|
||||
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 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 = vcc_seq_release,
|
||||
};
|
||||
|
||||
static int svc_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
static char atm_svc_banner[] =
|
||||
"Itf VPI VCI State Remote\n";
|
||||
|
||||
if (v == (void *)1)
|
||||
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 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 = vcc_seq_release,
|
||||
};
|
||||
|
||||
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(file->f_path.dentry->d_inode)->data;
|
||||
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 digits,num;
|
||||
int error;
|
||||
|
||||
/* No proc info */
|
||||
if (!dev->ops->proc_read)
|
||||
return 0;
|
||||
|
||||
error = -ENOMEM;
|
||||
digits = 0;
|
||||
for (num = dev->number; num; num /= 10) digits++;
|
||||
if (!digits) digits++;
|
||||
|
||||
dev->proc_name = kmalloc(strlen(dev->type) + digits + 2, GFP_KERNEL);
|
||||
if (!dev->proc_name)
|
||||
goto err_out;
|
||||
sprintf(dev->proc_name,"%s:%d",dev->type, dev->number);
|
||||
|
||||
dev->proc_entry = create_proc_entry(dev->proc_name, 0, atm_proc_root);
|
||||
if (!dev->proc_entry)
|
||||
goto err_free_name;
|
||||
dev->proc_entry->data = dev;
|
||||
dev->proc_entry->proc_fops = &proc_atm_dev_ops;
|
||||
dev->proc_entry->owner = THIS_MODULE;
|
||||
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("net/atm", NULL);
|
||||
}
|
||||
|
||||
int __init atm_proc_init(void)
|
||||
{
|
||||
static struct atm_proc_entry *e;
|
||||
int ret;
|
||||
|
||||
atm_proc_root = proc_mkdir("net/atm",NULL);
|
||||
if (!atm_proc_root)
|
||||
goto err_out;
|
||||
for (e = atm_proc_ents; e->name; e++) {
|
||||
struct proc_dir_entry *dirent;
|
||||
|
||||
dirent = create_proc_entry(e->name, S_IRUGO, atm_proc_root);
|
||||
if (!dirent)
|
||||
goto err_out_remove;
|
||||
dirent->proc_fops = e->proc_fops;
|
||||
dirent->owner = THIS_MODULE;
|
||||
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
|
||||
154
net/atm/pvc.c
Normal file
154
net/atm/pvc.c
Normal file
@@ -0,0 +1,154 @@
|
||||
/* 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 <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, 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;
|
||||
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,
|
||||
.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 socket *sock,int protocol)
|
||||
{
|
||||
sock->ops = &pvc_proto_ops;
|
||||
return vcc_create(sock, protocol, PF_ATMPVC);
|
||||
}
|
||||
|
||||
|
||||
static 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);
|
||||
}
|
||||
98
net/atm/raw.c
Normal file
98
net/atm/raw.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/* net/atm/raw.c - Raw AAL0 and AAL5 transports */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#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 "common.h"
|
||||
#include "protocols.h"
|
||||
|
||||
|
||||
#if 0
|
||||
#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
|
||||
#else
|
||||
#define DPRINTK(format,args...)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
DPRINTK("APopR (%d) %d -= %d\n", vcc->vci, sk->sk_wmem_alloc,
|
||||
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);
|
||||
436
net/atm/resources.c
Normal file
436
net/atm/resources.c
Normal file
@@ -0,0 +1,436 @@
|
||||
/* 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 */
|
||||
|
||||
|
||||
#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 <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;
|
||||
}
|
||||
|
||||
|
||||
struct atm_dev *atm_dev_register(const char *type, const struct atmdev_ops *ops,
|
||||
int number, unsigned long *flags)
|
||||
{
|
||||
struct atm_dev *dev, *inuse;
|
||||
|
||||
dev = __alloc_atm_dev(type);
|
||||
if (!dev) {
|
||||
printk(KERN_ERR "atm_dev_register: no space for dev %s\n",
|
||||
type);
|
||||
return NULL;
|
||||
}
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
if (number != -1) {
|
||||
if ((inuse = __atm_dev_lookup(number))) {
|
||||
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) {
|
||||
printk(KERN_ERR "atm_dev_register: "
|
||||
"atm_proc_dev_register failed for dev %s\n",
|
||||
type);
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
if (atm_register_sysfs(dev) < 0) {
|
||||
printk(KERN_ERR "atm_dev_register: "
|
||||
"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;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
void __user *buf;
|
||||
int error, len, number, size = 0;
|
||||
struct atm_dev *dev;
|
||||
struct list_head *p;
|
||||
int *tmp_buf, *tmp_p;
|
||||
struct atm_iobuf __user *iobuf = arg;
|
||||
struct atmif_sioc __user *sioc = arg;
|
||||
switch (cmd) {
|
||||
case ATM_GETNAMES:
|
||||
if (get_user(buf, &iobuf->buffer))
|
||||
return -EFAULT;
|
||||
if (get_user(len, &iobuf->length))
|
||||
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->length))
|
||||
? -EFAULT : 0;
|
||||
kfree(tmp_buf);
|
||||
return error;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (get_user(buf, &sioc->arg))
|
||||
return -EFAULT;
|
||||
if (get_user(len, &sioc->length))
|
||||
return -EFAULT;
|
||||
if (get_user(number, &sioc->number))
|
||||
return -EFAULT;
|
||||
|
||||
if (!(dev = try_then_request_module(atm_dev_lookup(number),
|
||||
"atm-device-%d", number)))
|
||||
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:
|
||||
if (!capable(CAP_NET_ADMIN)) {
|
||||
error = -EPERM;
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
struct sockaddr_atmsvc addr;
|
||||
|
||||
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->length)
|
||||
? -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 (!dev->ops->ioctl) {
|
||||
error = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
size = dev->ops->ioctl(dev, cmd, buf);
|
||||
if (size < 0) {
|
||||
error = (size == -ENOIOCTLCMD ? -EINVAL : size);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (size)
|
||||
error = put_user(size, &sioc->length)
|
||||
? -EFAULT : 0;
|
||||
else
|
||||
error = 0;
|
||||
done:
|
||||
atm_dev_put(dev);
|
||||
return error;
|
||||
}
|
||||
|
||||
static __inline__ void *dev_get_idx(loff_t left)
|
||||
{
|
||||
struct list_head *p;
|
||||
|
||||
list_for_each(p, &atm_devs) {
|
||||
if (!--left)
|
||||
break;
|
||||
}
|
||||
return (p != &atm_devs) ? p : NULL;
|
||||
}
|
||||
|
||||
void *atm_dev_seq_start(struct seq_file *seq, loff_t *pos)
|
||||
{
|
||||
mutex_lock(&atm_dev_mutex);
|
||||
return *pos ? dev_get_idx(*pos) : (void *) 1;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
++*pos;
|
||||
v = (v == (void *)1) ? atm_devs.next : ((struct list_head *)v)->next;
|
||||
return (v == &atm_devs) ? NULL : v;
|
||||
}
|
||||
|
||||
|
||||
EXPORT_SYMBOL(atm_dev_register);
|
||||
EXPORT_SYMBOL(atm_dev_deregister);
|
||||
EXPORT_SYMBOL(atm_dev_lookup);
|
||||
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);
|
||||
|
||||
|
||||
#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);
|
||||
void atm_unregister_sysfs(struct atm_dev *adev);
|
||||
#endif
|
||||
276
net/atm/signaling.c
Normal file
276
net/atm/signaling.c
Normal file
@@ -0,0 +1,276 @@
|
||||
/* net/atm/signaling.c - ATM signaling */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#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 "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. */
|
||||
|
||||
#if 0
|
||||
#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
|
||||
#else
|
||||
#define DPRINTK(format,args...)
|
||||
#endif
|
||||
|
||||
|
||||
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);
|
||||
DPRINTK("atmsvc: waiting for signaling demon...\n");
|
||||
schedule();
|
||||
}
|
||||
current->state = TASK_RUNNING;
|
||||
remove_wait_queue(&sigd_sleep,&wait);
|
||||
#else
|
||||
if (!sigd) {
|
||||
DPRINTK("atmsvc: no signaling demon\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);
|
||||
DPRINTK("sigd_send %d (0x%lx)\n",(int) msg->type,
|
||||
(unsigned long) msg->vcc);
|
||||
vcc = *(struct atm_vcc **) &msg->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);
|
||||
DPRINTK("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);
|
||||
DPRINTK("waking sk->sk_sleep 0x%p\n", sk->sk_sleep);
|
||||
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:
|
||||
printk(KERN_ALERT "sigd_send: 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 session = 0;
|
||||
|
||||
DPRINTK("sigd_enq %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 hlist_node *node;
|
||||
struct sock *s;
|
||||
int i;
|
||||
|
||||
DPRINTK("sigd_close\n");
|
||||
sigd = NULL;
|
||||
if (skb_peek(&sk_atm(vcc)->sk_receive_queue))
|
||||
printk(KERN_ERR "sigd_close: 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, node, head) {
|
||||
struct atm_vcc *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
|
||||
};
|
||||
|
||||
|
||||
int sigd_attach(struct atm_vcc *vcc)
|
||||
{
|
||||
if (sigd) return -EADDRINUSE;
|
||||
DPRINTK("sigd_attach\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
|
||||
671
net/atm/svc.c
Normal file
671
net/atm/svc.c
Normal file
@@ -0,0 +1,671 @@
|
||||
/* net/atm/svc.c - ATM SVC sockets */
|
||||
|
||||
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
|
||||
|
||||
|
||||
#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 <asm/uaccess.h>
|
||||
|
||||
#include "resources.h"
|
||||
#include "common.h" /* common for PVCs and SVCs */
|
||||
#include "signaling.h"
|
||||
#include "addr.h"
|
||||
|
||||
|
||||
#if 0
|
||||
#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
|
||||
#else
|
||||
#define DPRINTK(format,args...)
|
||||
#endif
|
||||
|
||||
|
||||
static int svc_create(struct socket *sock,int protocol);
|
||||
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
DPRINTK("svc_disconnect %p\n",vcc);
|
||||
if (test_bit(ATM_VF_REGIS,&vcc->flags)) {
|
||||
prepare_to_wait(sk->sk_sleep, &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->sk_sleep, &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &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);
|
||||
DPRINTK("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);
|
||||
DPRINTK("svc_release %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->sk_sleep, &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->sk_sleep, &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &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;
|
||||
|
||||
DPRINTK("svc_connect %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->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
sigd_enq(vcc,as_connect,NULL,NULL,&vcc->remote);
|
||||
if (flags & O_NONBLOCK) {
|
||||
finish_wait(sk->sk_sleep, &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->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
continue;
|
||||
}
|
||||
DPRINTK("*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->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
schedule();
|
||||
}
|
||||
if (!sk->sk_err)
|
||||
while (!test_bit(ATM_VF_RELEASED,&vcc->flags)
|
||||
&& sigd) {
|
||||
prepare_to_wait(sk->sk_sleep, &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->sk_sleep, &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
|
||||
*/
|
||||
if (!(error = vcc_connect(sock, vcc->itf, vcc->vpi, vcc->vci)))
|
||||
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;
|
||||
|
||||
DPRINTK("svc_listen %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;
|
||||
}
|
||||
vcc_insert_socket(sk);
|
||||
set_bit(ATM_VF_WAITING, &vcc->flags);
|
||||
prepare_to_wait(sk->sk_sleep, &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->sk_sleep, &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &wait);
|
||||
if (!sigd) {
|
||||
error = -EUNATCH;
|
||||
goto out;
|
||||
}
|
||||
set_bit(ATM_VF_LISTEN,&vcc->flags);
|
||||
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(newsock,0);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
new_vcc = ATM_SD(newsock);
|
||||
|
||||
DPRINTK("svc_accept %p -> %p\n",old_vcc,new_vcc);
|
||||
while (1) {
|
||||
DEFINE_WAIT(wait);
|
||||
|
||||
prepare_to_wait(sk->sk_sleep, &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->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &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_atm(new_vcc)->sk_sleep, &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_atm(new_vcc)->sk_sleep, &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk_atm(new_vcc)->sk_sleep, &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->sk_sleep, &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->sk_sleep, &wait, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &wait);
|
||||
if (!sigd) return -EUNATCH;
|
||||
return -sk->sk_err;
|
||||
}
|
||||
|
||||
|
||||
static int svc_setsockopt(struct socket *sock, int level, int optname,
|
||||
char __user *optval, 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->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
sigd_enq(vcc, as_addparty, NULL, NULL,
|
||||
(struct sockaddr_atmsvc *) sockaddr);
|
||||
if (flags & O_NONBLOCK) {
|
||||
finish_wait(sk->sk_sleep, &wait);
|
||||
error = -EINPROGRESS;
|
||||
goto out;
|
||||
}
|
||||
DPRINTK("svc_addparty added wait queue\n");
|
||||
while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) {
|
||||
schedule();
|
||||
prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &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->sk_sleep, &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->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
||||
}
|
||||
finish_wait(sk->sk_sleep, &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;
|
||||
}
|
||||
|
||||
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,
|
||||
.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 socket *sock,int protocol)
|
||||
{
|
||||
int error;
|
||||
|
||||
sock->ops = &svc_proto_ops;
|
||||
error = vcc_create(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 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);
|
||||
}
|
||||
Reference in New Issue
Block a user