URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
Compare Revisions
- This comparison shows the changes necessary to convert path
/or1k/trunk/linux/linux-2.4/net/atm
- from Rev 1275 to Rev 1765
- ↔ Reverse comparison
Rev 1275 → Rev 1765
/mpoa_caches.c
0,0 → 1,576
#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(uint32_t 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(uint32_t dst_ip, |
struct mpoa_client *client, |
uint32_t 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(uint32_t dst_ip, |
struct mpoa_client *client) |
{ |
unsigned char *ip __attribute__ ((unused)) = (unsigned char *)&dst_ip; |
in_cache_entry* entry = kmalloc(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", ip[0], ip[1], ip[2], ip[3]); |
memset(entry,0,sizeof(in_cache_entry)); |
|
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){ |
unsigned char *ip __attribute__ ((unused)) = |
(unsigned char *)&entry->ctrl_info.in_dst_ip; |
|
dprintk("mpoa: (%s) mpoa_caches.c: threshold exceeded for ip %u.%u.%u.%u, sending MPOA res req\n", mpc->dev->name, ip[0], ip[1], ip[2], ip[3]); |
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; |
unsigned char *ip; |
|
vcc = entry->shortcut; |
ip = (unsigned char *)&entry->ctrl_info.in_dst_ip; |
dprintk("mpoa: mpoa_caches.c: removing an ingress entry, ip = %u.%u.%u.%u\n",ip[0], ip[1], ip[2], ip[3]); |
|
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) |
{ |
unsigned char *ip; |
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){ |
ip = (unsigned char*)&entry->ctrl_info.in_dst_ip; |
dprintk("mpoa: mpoa_caches.c: holding time expired, ip = %u.%u.%u.%u\n", NIPQUAD(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(uint32_t 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(uint32_t 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(uint32_t 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) |
{ |
unsigned char *ip; |
eg_cache_entry *entry = kmalloc(sizeof(eg_cache_entry), GFP_KERNEL); |
|
if (entry == NULL) { |
printk("mpoa: mpoa_caches.c: new_eg_cache_entry: out of memory\n"); |
return NULL; |
} |
|
ip = (unsigned char *)&msg->content.eg_info.eg_dst_ip; |
dprintk("mpoa: mpoa_caches.c: adding an egress entry, ip = %u.%u.%u.%u, this should be our IP\n", NIPQUAD(ip)); |
memset(entry, 0, sizeof(eg_cache_entry)); |
|
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)); |
ip = (unsigned char *)&entry->ctrl_info.mps_ip; |
dprintk("mpoa: mpoa_caches.c: mps_ip = %u.%u.%u.%u\n", NIPQUAD(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; |
} |
/lec.c
0,0 → 1,2194
/* |
* lec.c: Lan Emulation driver |
* Marko Kiiskila carnil@cs.tut.fi |
* |
*/ |
|
#include <linux/config.h> |
#include <linux/kernel.h> |
#include <linux/bitops.h> |
|
/* We are ethernet device */ |
#include <linux/if_ether.h> |
#include <linux/netdevice.h> |
#include <linux/rtnetlink.h> |
#include <linux/etherdevice.h> |
#include <net/sock.h> |
#include <linux/skbuff.h> |
#include <linux/ip.h> |
#include <asm/byteorder.h> |
#include <asm/uaccess.h> |
#include <net/arp.h> |
#include <net/dst.h> |
#include <linux/proc_fs.h> |
#include <linux/spinlock.h> |
|
/* TokenRing if needed */ |
#ifdef CONFIG_TR |
#include <linux/trdevice.h> |
#endif |
|
/* And atm device */ |
#include <linux/atmdev.h> |
#include <linux/atmlec.h> |
|
/* Proxy LEC knows about bridging */ |
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
#include <linux/if_bridge.h> |
#include "../bridge/br_private.h" |
static unsigned char bridge_ula_lec[] = {0x01, 0x80, 0xc2, 0x00, 0x00}; |
#endif |
|
/* Modular too */ |
#include <linux/module.h> |
#include <linux/init.h> |
|
#include "lec.h" |
#include "lec_arpc.h" |
#include "resources.h" |
|
#if 0 |
#define DPRINTK printk |
#else |
#define DPRINTK(format,args...) |
#endif |
|
struct net_bridge; |
extern struct net_bridge_fdb_entry *(*br_fdb_get_hook)(struct net_bridge *br, |
unsigned char *addr); |
extern void (*br_fdb_put_hook)(struct net_bridge_fdb_entry *ent); |
|
#define DUMP_PACKETS 0 /* 0 = None, |
* 1 = 30 first bytes |
* 2 = Whole packet |
*/ |
|
#define LEC_UNRES_QUE_LEN 8 /* number of tx packets to queue for a |
single destination while waiting for SVC */ |
|
static int lec_open(struct net_device *dev); |
static int lec_send_packet(struct sk_buff *skb, struct net_device *dev); |
static int lec_close(struct net_device *dev); |
static struct net_device_stats *lec_get_stats(struct net_device *dev); |
static void lec_init(struct net_device *dev); |
static inline struct lec_arp_table* lec_arp_find(struct lec_priv *priv, |
unsigned char *mac_addr); |
static inline int lec_arp_remove(struct lec_priv *priv, |
struct lec_arp_table *to_remove); |
/* LANE2 functions */ |
static void lane2_associate_ind (struct net_device *dev, u8 *mac_address, |
u8 *tlvs, u32 sizeoftlvs); |
static int lane2_resolve(struct net_device *dev, u8 *dst_mac, int force, |
u8 **tlvs, u32 *sizeoftlvs); |
static int lane2_associate_req (struct net_device *dev, u8 *lan_dst, |
u8 *tlvs, u32 sizeoftlvs); |
|
static struct lane2_ops lane2_ops = { |
lane2_resolve, /* resolve, spec 3.1.3 */ |
lane2_associate_req, /* associate_req, spec 3.1.4 */ |
NULL /* associate indicator, spec 3.1.5 */ |
}; |
|
static unsigned char bus_mac[ETH_ALEN] = {0xff,0xff,0xff,0xff,0xff,0xff}; |
|
/* Device structures */ |
static struct net_device *dev_lec[MAX_LEC_ITF]; |
|
/* This will be called from proc.c via function pointer */ |
struct net_device *get_dev_lec(int itf) |
{ |
struct net_device *dev; |
|
if (itf >= MAX_LEC_ITF) |
return NULL; |
rtnl_lock(); |
dev = dev_lec[itf]; |
if (dev) |
dev_hold(dev); |
rtnl_unlock(); |
return dev; |
} |
|
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
static void lec_handle_bridge(struct sk_buff *skb, struct net_device *dev) |
{ |
struct ethhdr *eth; |
char *buff; |
struct lec_priv *priv; |
|
/* Check if this is a BPDU. If so, ask zeppelin to send |
* LE_TOPOLOGY_REQUEST with the same value of Topology Change bit |
* as the Config BPDU has */ |
eth = (struct ethhdr *)skb->data; |
buff = skb->data + skb->dev->hard_header_len; |
if (*buff++ == 0x42 && *buff++ == 0x42 && *buff++ == 0x03) { |
struct sk_buff *skb2; |
struct atmlec_msg *mesg; |
|
skb2 = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC); |
if (skb2 == NULL) return; |
skb2->len = sizeof(struct atmlec_msg); |
mesg = (struct atmlec_msg *)skb2->data; |
mesg->type = l_topology_change; |
buff += 4; |
mesg->content.normal.flag = *buff & 0x01; /* 0x01 is topology change */ |
|
priv = (struct lec_priv *)dev->priv; |
atm_force_charge(priv->lecd, skb2->truesize); |
skb_queue_tail(&priv->lecd->sk->receive_queue, skb2); |
wake_up(&priv->lecd->sleep); |
} |
|
return; |
} |
#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */ |
|
/* |
* Modelled after tr_type_trans |
* All multicast and ARE or STE frames go to BUS. |
* Non source routed frames go by destination address. |
* Last hop source routed frames go by destination address. |
* Not last hop source routed frames go by _next_ route descriptor. |
* Returns pointer to destination MAC address or fills in rdesc |
* and returns NULL. |
*/ |
#ifdef CONFIG_TR |
unsigned char *get_tr_dst(unsigned char *packet, unsigned char *rdesc) |
{ |
struct trh_hdr *trh; |
int riflen, num_rdsc; |
|
trh = (struct trh_hdr *)packet; |
if (trh->daddr[0] & (uint8_t)0x80) |
return bus_mac; /* multicast */ |
|
if (trh->saddr[0] & TR_RII) { |
riflen = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8; |
if ((ntohs(trh->rcf) >> 13) != 0) |
return bus_mac; /* ARE or STE */ |
} |
else |
return trh->daddr; /* not source routed */ |
|
if (riflen < 6) |
return trh->daddr; /* last hop, source routed */ |
|
/* riflen is 6 or more, packet has more than one route descriptor */ |
num_rdsc = (riflen/2) - 1; |
memset(rdesc, 0, ETH_ALEN); |
/* offset 4 comes from LAN destination field in LE control frames */ |
if (trh->rcf & htons((uint16_t)TR_RCF_DIR_BIT)) |
memcpy(&rdesc[4], &trh->rseg[num_rdsc-2], sizeof(uint16_t)); |
else { |
memcpy(&rdesc[4], &trh->rseg[1], sizeof(uint16_t)); |
rdesc[5] = ((ntohs(trh->rseg[0]) & 0x000f) | (rdesc[5] & 0xf0)); |
} |
|
return NULL; |
} |
#endif /* CONFIG_TR */ |
|
/* |
* Open/initialize the netdevice. This is called (in the current kernel) |
* sometime after booting when the 'ifconfig' program is run. |
* |
* This routine should set everything up anew at each open, even |
* registers that "should" only need to be set once at boot, so that |
* there is non-reboot way to recover if something goes wrong. |
*/ |
|
static int |
lec_open(struct net_device *dev) |
{ |
struct lec_priv *priv = (struct lec_priv *)dev->priv; |
|
netif_start_queue(dev); |
memset(&priv->stats,0,sizeof(struct net_device_stats)); |
|
return 0; |
} |
|
static __inline__ void |
lec_send(struct atm_vcc *vcc, struct sk_buff *skb, struct lec_priv *priv) |
{ |
if (atm_may_send(vcc, skb->len)) { |
atomic_add(skb->truesize, &vcc->sk->wmem_alloc); |
ATM_SKB(skb)->vcc = vcc; |
ATM_SKB(skb)->atm_options = vcc->atm_options; |
priv->stats.tx_packets++; |
priv->stats.tx_bytes += skb->len; |
vcc->send(vcc, skb); |
} else { |
priv->stats.tx_dropped++; |
dev_kfree_skb(skb); |
} |
} |
|
static int |
lec_send_packet(struct sk_buff *skb, struct net_device *dev) |
{ |
struct sk_buff *skb2; |
struct lec_priv *priv = (struct lec_priv *)dev->priv; |
struct lecdatahdr_8023 *lec_h; |
struct atm_vcc *send_vcc; |
struct lec_arp_table *entry; |
unsigned char *dst; |
int min_frame_size; |
#ifdef CONFIG_TR |
unsigned char rdesc[ETH_ALEN]; /* Token Ring route descriptor */ |
#endif |
int is_rdesc; |
#if DUMP_PACKETS > 0 |
char buf[300]; |
int i=0; |
#endif /* DUMP_PACKETS >0 */ |
|
DPRINTK("Lec_send_packet called\n"); |
if (!priv->lecd) { |
printk("%s:No lecd attached\n",dev->name); |
priv->stats.tx_errors++; |
netif_stop_queue(dev); |
return -EUNATCH; |
} |
|
DPRINTK("skbuff head:%lx data:%lx tail:%lx end:%lx\n", |
(long)skb->head, (long)skb->data, (long)skb->tail, |
(long)skb->end); |
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
if (memcmp(skb->data, bridge_ula_lec, sizeof(bridge_ula_lec)) == 0) |
lec_handle_bridge(skb, dev); |
#endif |
|
/* Make sure we have room for lec_id */ |
if (skb_headroom(skb) < 2) { |
|
DPRINTK("lec_send_packet: reallocating skb\n"); |
skb2 = skb_realloc_headroom(skb, LEC_HEADER_LEN); |
kfree_skb(skb); |
if (skb2 == NULL) return 0; |
skb = skb2; |
} |
skb_push(skb, 2); |
|
/* Put le header to place, works for TokenRing too */ |
lec_h = (struct lecdatahdr_8023*)skb->data; |
lec_h->le_header = htons(priv->lecid); |
|
#ifdef CONFIG_TR |
/* Ugly. Use this to realign Token Ring packets for |
* e.g. PCA-200E driver. */ |
if (priv->is_trdev) { |
skb2 = skb_realloc_headroom(skb, LEC_HEADER_LEN); |
kfree_skb(skb); |
if (skb2 == NULL) return 0; |
skb = skb2; |
} |
#endif |
|
#if DUMP_PACKETS > 0 |
printk("%s: send datalen:%ld lecid:%4.4x\n", dev->name, |
skb->len, priv->lecid); |
#if DUMP_PACKETS >= 2 |
for(i=0;i<skb->len && i <99;i++) { |
sprintf(buf+i*3,"%2.2x ",0xff&skb->data[i]); |
} |
#elif DUMP_PACKETS >= 1 |
for(i=0;i<skb->len && i < 30;i++) { |
sprintf(buf+i*3,"%2.2x ", 0xff&skb->data[i]); |
} |
#endif /* DUMP_PACKETS >= 1 */ |
if (i==skb->len) |
printk("%s\n",buf); |
else |
printk("%s...\n",buf); |
#endif /* DUMP_PACKETS > 0 */ |
|
/* Minimum ethernet-frame size */ |
#ifdef CONFIG_TR |
if (priv->is_trdev) |
min_frame_size = LEC_MINIMUM_8025_SIZE; |
else |
#endif |
min_frame_size = LEC_MINIMUM_8023_SIZE; |
if (skb->len < min_frame_size) { |
if ((skb->len + skb_tailroom(skb)) < min_frame_size) { |
skb2 = skb_copy_expand(skb, 0, |
min_frame_size - skb->truesize, GFP_ATOMIC); |
dev_kfree_skb(skb); |
if (skb2 == NULL) { |
priv->stats.tx_dropped++; |
return 0; |
} |
skb = skb2; |
} |
skb_put(skb, min_frame_size - skb->len); |
} |
|
/* Send to right vcc */ |
is_rdesc = 0; |
dst = lec_h->h_dest; |
#ifdef CONFIG_TR |
if (priv->is_trdev) { |
dst = get_tr_dst(skb->data+2, rdesc); |
if (dst == NULL) { |
dst = rdesc; |
is_rdesc = 1; |
} |
} |
#endif |
entry = NULL; |
send_vcc = lec_arp_resolve(priv, dst, is_rdesc, &entry); |
DPRINTK("%s:send_vcc:%p vcc_flags:%x, entry:%p\n", dev->name, |
send_vcc, send_vcc?send_vcc->flags:0, entry); |
if (!send_vcc || !test_bit(ATM_VF_READY,&send_vcc->flags)) { |
if (entry && (entry->tx_wait.qlen < LEC_UNRES_QUE_LEN)) { |
DPRINTK("%s:lec_send_packet: queuing packet, ", dev->name); |
DPRINTK("MAC address 0x%02x:%02x:%02x:%02x:%02x:%02x\n", |
lec_h->h_dest[0], lec_h->h_dest[1], lec_h->h_dest[2], |
lec_h->h_dest[3], lec_h->h_dest[4], lec_h->h_dest[5]); |
skb_queue_tail(&entry->tx_wait, skb); |
} else { |
DPRINTK("%s:lec_send_packet: tx queue full or no arp entry, dropping, ", dev->name); |
DPRINTK("MAC address 0x%02x:%02x:%02x:%02x:%02x:%02x\n", |
lec_h->h_dest[0], lec_h->h_dest[1], lec_h->h_dest[2], |
lec_h->h_dest[3], lec_h->h_dest[4], lec_h->h_dest[5]); |
priv->stats.tx_dropped++; |
dev_kfree_skb(skb); |
} |
return 0; |
} |
|
#if DUMP_PACKETS > 0 |
printk("%s:sending to vpi:%d vci:%d\n", dev->name, |
send_vcc->vpi, send_vcc->vci); |
#endif /* DUMP_PACKETS > 0 */ |
|
while (entry && (skb2 = skb_dequeue(&entry->tx_wait))) { |
DPRINTK("lec.c: emptying tx queue, "); |
DPRINTK("MAC address 0x%02x:%02x:%02x:%02x:%02x:%02x\n", |
lec_h->h_dest[0], lec_h->h_dest[1], lec_h->h_dest[2], |
lec_h->h_dest[3], lec_h->h_dest[4], lec_h->h_dest[5]); |
lec_send(send_vcc, skb2, priv); |
} |
|
lec_send(send_vcc, skb, priv); |
#if 0 |
/* Should we wait for card's device driver to notify us? */ |
dev->tbusy=0; |
#endif |
return 0; |
} |
|
/* The inverse routine to net_open(). */ |
static int |
lec_close(struct net_device *dev) |
{ |
netif_stop_queue(dev); |
return 0; |
} |
|
/* |
* Get the current statistics. |
* This may be called with the card open or closed. |
*/ |
static struct net_device_stats * |
lec_get_stats(struct net_device *dev) |
{ |
return &((struct lec_priv *)dev->priv)->stats; |
} |
|
static int |
lec_atm_send(struct atm_vcc *vcc, struct sk_buff *skb) |
{ |
struct net_device *dev = (struct net_device*)vcc->proto_data; |
struct lec_priv *priv = (struct lec_priv*)dev->priv; |
struct atmlec_msg *mesg; |
struct lec_arp_table *entry; |
int i; |
char *tmp; /* FIXME */ |
|
atomic_sub(skb->truesize, &vcc->sk->wmem_alloc); |
mesg = (struct atmlec_msg *)skb->data; |
tmp = skb->data; |
tmp += sizeof(struct atmlec_msg); |
DPRINTK("%s: msg from zeppelin:%d\n", dev->name, mesg->type); |
switch(mesg->type) { |
case l_set_mac_addr: |
for (i=0;i<6;i++) { |
dev->dev_addr[i] = mesg->content.normal.mac_addr[i]; |
} |
break; |
case l_del_mac_addr: |
for(i=0;i<6;i++) { |
dev->dev_addr[i] = 0; |
} |
break; |
case l_addr_delete: |
lec_addr_delete(priv, mesg->content.normal.atm_addr, |
mesg->content.normal.flag); |
break; |
case l_topology_change: |
priv->topology_change = mesg->content.normal.flag; |
break; |
case l_flush_complete: |
lec_flush_complete(priv, mesg->content.normal.flag); |
break; |
case l_narp_req: /* LANE2: see 7.1.35 in the lane2 spec */ |
entry = lec_arp_find(priv, mesg->content.normal.mac_addr); |
lec_arp_remove(priv, entry); |
|
if (mesg->content.normal.no_source_le_narp) |
break; |
/* FALL THROUGH */ |
case l_arp_update: |
lec_arp_update(priv, mesg->content.normal.mac_addr, |
mesg->content.normal.atm_addr, |
mesg->content.normal.flag, |
mesg->content.normal.targetless_le_arp); |
DPRINTK("lec: in l_arp_update\n"); |
if (mesg->sizeoftlvs != 0) { /* LANE2 3.1.5 */ |
DPRINTK("lec: LANE2 3.1.5, got tlvs, size %d\n", mesg->sizeoftlvs); |
lane2_associate_ind(dev, |
mesg->content.normal.mac_addr, |
tmp, mesg->sizeoftlvs); |
} |
break; |
case l_config: |
priv->maximum_unknown_frame_count = |
mesg->content.config.maximum_unknown_frame_count; |
priv->max_unknown_frame_time = |
(mesg->content.config.max_unknown_frame_time*HZ); |
priv->max_retry_count = |
mesg->content.config.max_retry_count; |
priv->aging_time = (mesg->content.config.aging_time*HZ); |
priv->forward_delay_time = |
(mesg->content.config.forward_delay_time*HZ); |
priv->arp_response_time = |
(mesg->content.config.arp_response_time*HZ); |
priv->flush_timeout = (mesg->content.config.flush_timeout*HZ); |
priv->path_switching_delay = |
(mesg->content.config.path_switching_delay*HZ); |
priv->lane_version = mesg->content.config.lane_version; /* LANE2 */ |
priv->lane2_ops = NULL; |
if (priv->lane_version > 1) |
priv->lane2_ops = &lane2_ops; |
if (dev->change_mtu(dev, mesg->content.config.mtu)) |
printk("%s: change_mtu to %d failed\n", dev->name, |
mesg->content.config.mtu); |
priv->is_proxy = mesg->content.config.is_proxy; |
break; |
case l_flush_tran_id: |
lec_set_flush_tran_id(priv, mesg->content.normal.atm_addr, |
mesg->content.normal.flag); |
break; |
case l_set_lecid: |
priv->lecid=(unsigned short)(0xffff&mesg->content.normal.flag); |
break; |
case l_should_bridge: { |
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
struct net_bridge_fdb_entry *f; |
|
DPRINTK("%s: bridge zeppelin asks about 0x%02x:%02x:%02x:%02x:%02x:%02x\n", |
dev->name, |
mesg->content.proxy.mac_addr[0], mesg->content.proxy.mac_addr[1], |
mesg->content.proxy.mac_addr[2], mesg->content.proxy.mac_addr[3], |
mesg->content.proxy.mac_addr[4], mesg->content.proxy.mac_addr[5]); |
|
if (br_fdb_get_hook == NULL || dev->br_port == NULL) |
break; |
|
f = br_fdb_get_hook(dev->br_port->br, mesg->content.proxy.mac_addr); |
if (f != NULL && |
f->dst->dev != dev && |
f->dst->state == BR_STATE_FORWARDING) { |
/* hit from bridge table, send LE_ARP_RESPONSE */ |
struct sk_buff *skb2; |
|
DPRINTK("%s: entry found, responding to zeppelin\n", dev->name); |
skb2 = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC); |
if (skb2 == NULL) { |
br_fdb_put_hook(f); |
break; |
} |
skb2->len = sizeof(struct atmlec_msg); |
memcpy(skb2->data, mesg, sizeof(struct atmlec_msg)); |
atm_force_charge(priv->lecd, skb2->truesize); |
skb_queue_tail(&priv->lecd->sk->receive_queue, skb2); |
wake_up(&priv->lecd->sleep); |
} |
if (f != NULL) br_fdb_put_hook(f); |
#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */ |
} |
break; |
default: |
printk("%s: Unknown message type %d\n", dev->name, mesg->type); |
dev_kfree_skb(skb); |
return -EINVAL; |
} |
dev_kfree_skb(skb); |
return 0; |
} |
|
static void |
lec_atm_close(struct atm_vcc *vcc) |
{ |
struct sk_buff *skb; |
struct net_device *dev = (struct net_device *)vcc->proto_data; |
struct lec_priv *priv = (struct lec_priv *)dev->priv; |
|
priv->lecd = NULL; |
/* Do something needful? */ |
|
netif_stop_queue(dev); |
lec_arp_destroy(priv); |
|
if (skb_peek(&vcc->sk->receive_queue)) |
printk("%s lec_atm_close: closing with messages pending\n", |
dev->name); |
while ((skb = skb_dequeue(&vcc->sk->receive_queue))) { |
atm_return(vcc, skb->truesize); |
dev_kfree_skb(skb); |
} |
|
printk("%s: Shut down!\n", dev->name); |
MOD_DEC_USE_COUNT; |
} |
|
static struct atmdev_ops lecdev_ops = { |
.close = lec_atm_close, |
.send = lec_atm_send |
}; |
|
static struct atm_dev lecatm_dev = { |
.ops = &lecdev_ops, |
.type = "lec", |
.number = 999, |
.lock = SPIN_LOCK_UNLOCKED |
}; |
|
/* |
* LANE2: new argument struct sk_buff *data contains |
* the LE_ARP based TLVs introduced in the LANE2 spec |
*/ |
int |
send_to_lecd(struct lec_priv *priv, atmlec_msg_type type, |
unsigned char *mac_addr, unsigned char *atm_addr, |
struct sk_buff *data) |
{ |
struct sk_buff *skb; |
struct atmlec_msg *mesg; |
|
if (!priv || !priv->lecd) { |
return -1; |
} |
skb = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC); |
if (!skb) |
return -1; |
skb->len = sizeof(struct atmlec_msg); |
mesg = (struct atmlec_msg *)skb->data; |
memset(mesg, 0, sizeof(struct atmlec_msg)); |
mesg->type = type; |
if (data != NULL) |
mesg->sizeoftlvs = data->len; |
if (mac_addr) |
memcpy(&mesg->content.normal.mac_addr, mac_addr, ETH_ALEN); |
else |
mesg->content.normal.targetless_le_arp = 1; |
if (atm_addr) |
memcpy(&mesg->content.normal.atm_addr, atm_addr, ATM_ESA_LEN); |
|
atm_force_charge(priv->lecd, skb->truesize); |
skb_queue_tail(&priv->lecd->sk->receive_queue, skb); |
wake_up(&priv->lecd->sleep); |
|
if (data != NULL) { |
DPRINTK("lec: about to send %d bytes of data\n", data->len); |
atm_force_charge(priv->lecd, data->truesize); |
skb_queue_tail(&priv->lecd->sk->receive_queue, data); |
wake_up(&priv->lecd->sleep); |
} |
|
return 0; |
} |
|
/* shamelessly stolen from drivers/net/net_init.c */ |
static int lec_change_mtu(struct net_device *dev, int new_mtu) |
{ |
if ((new_mtu < 68) || (new_mtu > 18190)) |
return -EINVAL; |
dev->mtu = new_mtu; |
return 0; |
} |
|
static void lec_set_multicast_list(struct net_device *dev) |
{ |
/* by default, all multicast frames arrive over the bus. |
* eventually support selective multicast service |
*/ |
return; |
} |
|
static void |
lec_init(struct net_device *dev) |
{ |
dev->change_mtu = lec_change_mtu; |
dev->open = lec_open; |
dev->stop = lec_close; |
dev->hard_start_xmit = lec_send_packet; |
|
dev->get_stats = lec_get_stats; |
dev->set_multicast_list = lec_set_multicast_list; |
dev->do_ioctl = NULL; |
printk("%s: Initialized!\n",dev->name); |
return; |
} |
|
static unsigned char lec_ctrl_magic[] = { |
0xff, |
0x00, |
0x01, |
0x01 }; |
|
void |
lec_push(struct atm_vcc *vcc, struct sk_buff *skb) |
{ |
struct net_device *dev = (struct net_device *)vcc->proto_data; |
struct lec_priv *priv = (struct lec_priv *)dev->priv; |
|
#if DUMP_PACKETS >0 |
int i=0; |
char buf[300]; |
|
printk("%s: lec_push vcc vpi:%d vci:%d\n", dev->name, |
vcc->vpi, vcc->vci); |
#endif |
if (!skb) { |
DPRINTK("%s: null skb\n",dev->name); |
lec_vcc_close(priv, vcc); |
return; |
} |
#if DUMP_PACKETS > 0 |
printk("%s: rcv datalen:%ld lecid:%4.4x\n", dev->name, |
skb->len, priv->lecid); |
#if DUMP_PACKETS >= 2 |
for(i=0;i<skb->len && i <99;i++) { |
sprintf(buf+i*3,"%2.2x ",0xff&skb->data[i]); |
} |
#elif DUMP_PACKETS >= 1 |
for(i=0;i<skb->len && i < 30;i++) { |
sprintf(buf+i*3,"%2.2x ", 0xff&skb->data[i]); |
} |
#endif /* DUMP_PACKETS >= 1 */ |
if (i==skb->len) |
printk("%s\n",buf); |
else |
printk("%s...\n",buf); |
#endif /* DUMP_PACKETS > 0 */ |
if (memcmp(skb->data, lec_ctrl_magic, 4) ==0) { /* Control frame, to daemon*/ |
DPRINTK("%s: To daemon\n",dev->name); |
skb_queue_tail(&vcc->sk->receive_queue, skb); |
wake_up(&vcc->sleep); |
} else { /* Data frame, queue to protocol handlers */ |
unsigned char *dst; |
|
atm_return(vcc,skb->truesize); |
if (*(uint16_t *)skb->data == htons(priv->lecid) || |
!priv->lecd) { |
/* Probably looping back, or if lecd is missing, |
lecd has gone down */ |
DPRINTK("Ignoring loopback frame...\n"); |
dev_kfree_skb(skb); |
return; |
} |
#ifdef CONFIG_TR |
if (priv->is_trdev) dst = ((struct lecdatahdr_8025 *)skb->data)->h_dest; |
else |
#endif |
dst = ((struct lecdatahdr_8023 *)skb->data)->h_dest; |
|
if (!(dst[0]&0x01) && /* Never filter Multi/Broadcast */ |
!priv->is_proxy && /* Proxy wants all the packets */ |
memcmp(dst, dev->dev_addr, dev->addr_len)) { |
dev_kfree_skb(skb); |
return; |
} |
if (priv->lec_arp_empty_ones) { |
lec_arp_check_empties(priv, vcc, skb); |
} |
skb->dev = dev; |
skb_pull(skb, 2); /* skip lec_id */ |
#ifdef CONFIG_TR |
if (priv->is_trdev) skb->protocol = tr_type_trans(skb, dev); |
else |
#endif |
skb->protocol = eth_type_trans(skb, dev); |
priv->stats.rx_packets++; |
priv->stats.rx_bytes += skb->len; |
memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data)); |
netif_rx(skb); |
} |
} |
|
int |
lec_vcc_attach(struct atm_vcc *vcc, void *arg) |
{ |
int bytes_left; |
struct atmlec_ioc ioc_data; |
|
/* Lecd must be up in this case */ |
bytes_left = copy_from_user(&ioc_data, arg, sizeof(struct atmlec_ioc)); |
if (bytes_left != 0) { |
printk("lec: lec_vcc_attach, copy from user failed for %d bytes\n", |
bytes_left); |
} |
if (ioc_data.dev_num < 0 || ioc_data.dev_num >= MAX_LEC_ITF || |
!dev_lec[ioc_data.dev_num]) |
return -EINVAL; |
lec_vcc_added(dev_lec[ioc_data.dev_num]->priv, |
&ioc_data, vcc, vcc->push); |
vcc->proto_data = dev_lec[ioc_data.dev_num]; |
vcc->push = lec_push; |
return 0; |
} |
|
int |
lec_mcast_attach(struct atm_vcc *vcc, int arg) |
{ |
if (arg <0 || arg >= MAX_LEC_ITF || !dev_lec[arg]) |
return -EINVAL; |
vcc->proto_data = dev_lec[arg]; |
return (lec_mcast_make((struct lec_priv*)dev_lec[arg]->priv, vcc)); |
} |
|
/* Initialize device. */ |
int |
lecd_attach(struct atm_vcc *vcc, int arg) |
{ |
int i; |
struct lec_priv *priv; |
|
if (arg<0) |
i = 0; |
else |
i = arg; |
#ifdef CONFIG_TR |
if (arg >= MAX_LEC_ITF) |
return -EINVAL; |
#else /* Reserve the top NUM_TR_DEVS for TR */ |
if (arg >= (MAX_LEC_ITF-NUM_TR_DEVS)) |
return -EINVAL; |
#endif |
if (!dev_lec[i]) { |
int is_trdev, size; |
|
is_trdev = 0; |
if (i >= (MAX_LEC_ITF - NUM_TR_DEVS)) |
is_trdev = 1; |
|
size = sizeof(struct lec_priv); |
#ifdef CONFIG_TR |
if (is_trdev) |
dev_lec[i] = alloc_trdev(size); |
else |
#endif |
dev_lec[i] = alloc_etherdev(size); |
if (!dev_lec[i]) |
return -ENOMEM; |
snprintf(dev_lec[i]->name, IFNAMSIZ, "lec%d", i); |
if (register_netdev(dev_lec[i])) { |
kfree(dev_lec[i]); |
return -EINVAL; |
} |
|
priv = dev_lec[i]->priv; |
priv->is_trdev = is_trdev; |
lec_init(dev_lec[i]); |
} else { |
priv = dev_lec[i]->priv; |
if (priv->lecd) |
return -EADDRINUSE; |
} |
lec_arp_init(priv); |
priv->itfnum = i; /* LANE2 addition */ |
priv->lecd = vcc; |
vcc->dev = &lecatm_dev; |
vcc_insert_socket(vcc->sk); |
|
vcc->proto_data = dev_lec[i]; |
set_bit(ATM_VF_META,&vcc->flags); |
set_bit(ATM_VF_READY,&vcc->flags); |
|
/* Set default values to these variables */ |
priv->maximum_unknown_frame_count = 1; |
priv->max_unknown_frame_time = (1*HZ); |
priv->vcc_timeout_period = (1200*HZ); |
priv->max_retry_count = 1; |
priv->aging_time = (300*HZ); |
priv->forward_delay_time = (15*HZ); |
priv->topology_change = 0; |
priv->arp_response_time = (1*HZ); |
priv->flush_timeout = (4*HZ); |
priv->path_switching_delay = (6*HZ); |
|
if (dev_lec[i]->flags & IFF_UP) { |
netif_start_queue(dev_lec[i]); |
} |
MOD_INC_USE_COUNT; |
return i; |
} |
|
static struct atm_lane_ops __atm_lane_ops = |
{ |
.lecd_attach = lecd_attach, |
.mcast_attach = lec_mcast_attach, |
.vcc_attach = lec_vcc_attach, |
.get_lec = get_dev_lec, |
.owner = THIS_MODULE |
}; |
|
static int __init lane_module_init(void) |
{ |
atm_lane_ops_set(&__atm_lane_ops); |
printk("lec.c: " __DATE__ " " __TIME__ " initialized\n"); |
return 0; |
} |
|
static void __exit lane_module_cleanup(void) |
{ |
int i; |
struct lec_priv *priv; |
|
atm_lane_ops_set(NULL); |
|
for (i = 0; i < MAX_LEC_ITF; i++) { |
if (dev_lec[i] != NULL) { |
priv = (struct lec_priv *)dev_lec[i]->priv; |
#if defined(CONFIG_TR) |
if (priv->is_trdev) |
unregister_trdev(dev_lec[i]); |
else |
#endif |
unregister_netdev(dev_lec[i]); |
kfree(dev_lec[i]); |
dev_lec[i] = NULL; |
} |
} |
|
return; |
} |
|
module_init(lane_module_init); |
module_exit(lane_module_cleanup); |
|
/* |
* LANE2: 3.1.3, LE_RESOLVE.request |
* Non force allocates memory and fills in *tlvs, fills in *sizeoftlvs. |
* If sizeoftlvs == NULL the default TLVs associated with with this |
* lec will be used. |
* If dst_mac == NULL, targetless LE_ARP will be sent |
*/ |
static int lane2_resolve(struct net_device *dev, u8 *dst_mac, int force, |
u8 **tlvs, u32 *sizeoftlvs) |
{ |
struct lec_priv *priv = (struct lec_priv *)dev->priv; |
struct lec_arp_table *table; |
struct sk_buff *skb; |
int retval; |
|
if (force == 0) { |
table = lec_arp_find(priv, dst_mac); |
if(table == NULL) |
return -1; |
|
*tlvs = kmalloc(table->sizeoftlvs, GFP_KERNEL); |
if (*tlvs == NULL) |
return -1; |
|
memcpy(*tlvs, table->tlvs, table->sizeoftlvs); |
*sizeoftlvs = table->sizeoftlvs; |
|
return 0; |
} |
|
if (sizeoftlvs == NULL) |
retval = send_to_lecd(priv, l_arp_xmt, dst_mac, NULL, NULL); |
|
else { |
skb = alloc_skb(*sizeoftlvs, GFP_ATOMIC); |
if (skb == NULL) |
return -1; |
skb->len = *sizeoftlvs; |
memcpy(skb->data, *tlvs, *sizeoftlvs); |
retval = send_to_lecd(priv, l_arp_xmt, dst_mac, NULL, skb); |
} |
return retval; |
} |
|
|
/* |
* LANE2: 3.1.4, LE_ASSOCIATE.request |
* Associate the *tlvs with the *lan_dst address. |
* Will overwrite any previous association |
* Returns 1 for success, 0 for failure (out of memory) |
* |
*/ |
static int lane2_associate_req (struct net_device *dev, u8 *lan_dst, |
u8 *tlvs, u32 sizeoftlvs) |
{ |
int retval; |
struct sk_buff *skb; |
struct lec_priv *priv = (struct lec_priv*)dev->priv; |
|
if ( memcmp(lan_dst, dev->dev_addr, ETH_ALEN) != 0 ) |
return (0); /* not our mac address */ |
|
kfree(priv->tlvs); /* NULL if there was no previous association */ |
|
priv->tlvs = kmalloc(sizeoftlvs, GFP_KERNEL); |
if (priv->tlvs == NULL) |
return (0); |
priv->sizeoftlvs = sizeoftlvs; |
memcpy(priv->tlvs, tlvs, sizeoftlvs); |
|
skb = alloc_skb(sizeoftlvs, GFP_ATOMIC); |
if (skb == NULL) |
return 0; |
skb->len = sizeoftlvs; |
memcpy(skb->data, tlvs, sizeoftlvs); |
retval = send_to_lecd(priv, l_associate_req, NULL, NULL, skb); |
if (retval != 0) |
printk("lec.c: lane2_associate_req() failed\n"); |
/* If the previous association has changed we must |
* somehow notify other LANE entities about the change |
*/ |
return (1); |
} |
|
/* |
* LANE2: 3.1.5, LE_ASSOCIATE.indication |
* |
*/ |
static void lane2_associate_ind (struct net_device *dev, u8 *mac_addr, |
u8 *tlvs, u32 sizeoftlvs) |
{ |
#if 0 |
int i = 0; |
#endif |
struct lec_priv *priv = (struct lec_priv *)dev->priv; |
#if 0 /* Why have the TLVs in LE_ARP entries since we do not use them? When you |
uncomment this code, make sure the TLVs get freed when entry is killed */ |
struct lec_arp_table *entry = lec_arp_find(priv, mac_addr); |
|
if (entry == NULL) |
return; /* should not happen */ |
|
kfree(entry->tlvs); |
|
entry->tlvs = kmalloc(sizeoftlvs, GFP_KERNEL); |
if (entry->tlvs == NULL) |
return; |
|
entry->sizeoftlvs = sizeoftlvs; |
memcpy(entry->tlvs, tlvs, sizeoftlvs); |
#endif |
#if 0 |
printk("lec.c: lane2_associate_ind()\n"); |
printk("dump of tlvs, sizeoftlvs=%d\n", sizeoftlvs); |
while (i < sizeoftlvs) |
printk("%02x ", tlvs[i++]); |
|
printk("\n"); |
#endif |
|
/* tell MPOA about the TLVs we saw */ |
if (priv->lane2_ops && priv->lane2_ops->associate_indicator) { |
priv->lane2_ops->associate_indicator(dev, mac_addr, |
tlvs, sizeoftlvs); |
} |
return; |
} |
|
/* |
* Here starts what used to lec_arpc.c |
* |
* lec_arpc.c was added here when making |
* lane client modular. October 1997 |
* |
*/ |
|
#include <linux/types.h> |
#include <linux/sched.h> |
#include <linux/timer.h> |
#include <asm/param.h> |
#include <asm/atomic.h> |
#include <linux/inetdevice.h> |
#include <net/route.h> |
|
|
#if 0 |
#define DPRINTK(format,args...) |
/* |
#define DPRINTK printk |
*/ |
#endif |
#define DEBUG_ARP_TABLE 0 |
|
#define LEC_ARP_REFRESH_INTERVAL (3*HZ) |
|
static void lec_arp_check_expire(unsigned long data); |
static void lec_arp_expire_arp(unsigned long data); |
void dump_arp_table(struct lec_priv *priv); |
|
/* |
* Arp table funcs |
*/ |
|
#define HASH(ch) (ch & (LEC_ARP_TABLE_SIZE -1)) |
|
static __inline__ void |
lec_arp_get(struct lec_priv *priv) |
{ |
atomic_inc(&priv->lec_arp_users); |
} |
|
static __inline__ void |
lec_arp_put(struct lec_priv *priv) |
{ |
atomic_dec(&priv->lec_arp_users); |
} |
|
/* |
* Initialization of arp-cache |
*/ |
void |
lec_arp_init(struct lec_priv *priv) |
{ |
unsigned short i; |
|
for (i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
priv->lec_arp_tables[i] = NULL; |
} |
spin_lock_init(&priv->lec_arp_lock); |
init_timer(&priv->lec_arp_timer); |
priv->lec_arp_timer.expires = jiffies+LEC_ARP_REFRESH_INTERVAL; |
priv->lec_arp_timer.data = (unsigned long)priv; |
priv->lec_arp_timer.function = lec_arp_check_expire; |
add_timer(&priv->lec_arp_timer); |
} |
|
void |
lec_arp_clear_vccs(struct lec_arp_table *entry) |
{ |
if (entry->vcc) { |
entry->vcc->push = entry->old_push; |
#if 0 /* August 6, 1998 */ |
set_bit(ATM_VF_RELEASED,&entry->vcc->flags); |
clear_bit(ATM_VF_READY,&entry->vcc->flags); |
entry->vcc->push(entry->vcc, NULL); |
#endif |
vcc_release_async(entry->vcc, -EPIPE); |
entry->vcc = NULL; |
} |
if (entry->recv_vcc) { |
entry->recv_vcc->push = entry->old_recv_push; |
#if 0 |
set_bit(ATM_VF_RELEASED,&entry->recv_vcc->flags); |
clear_bit(ATM_VF_READY,&entry->recv_vcc->flags); |
entry->recv_vcc->push(entry->recv_vcc, NULL); |
#endif |
vcc_release_async(entry->recv_vcc, -EPIPE); |
entry->recv_vcc = NULL; |
} |
} |
|
/* |
* Insert entry to lec_arp_table |
* LANE2: Add to the end of the list to satisfy 8.1.13 |
*/ |
static inline void |
lec_arp_add(struct lec_priv *priv, struct lec_arp_table *to_add) |
{ |
unsigned long flags; |
unsigned short place; |
struct lec_arp_table *tmp; |
|
spin_lock_irqsave(&priv->lec_arp_lock, flags); |
|
place = HASH(to_add->mac_addr[ETH_ALEN-1]); |
tmp = priv->lec_arp_tables[place]; |
to_add->next = NULL; |
if (tmp == NULL) |
priv->lec_arp_tables[place] = to_add; |
|
else { /* add to the end */ |
while (tmp->next) |
tmp = tmp->next; |
tmp->next = to_add; |
} |
|
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
|
DPRINTK("LEC_ARP: Added entry:%2.2x %2.2x %2.2x %2.2x %2.2x %2.2x\n", |
0xff&to_add->mac_addr[0], 0xff&to_add->mac_addr[1], |
0xff&to_add->mac_addr[2], 0xff&to_add->mac_addr[3], |
0xff&to_add->mac_addr[4], 0xff&to_add->mac_addr[5]); |
} |
|
/* |
* Remove entry from lec_arp_table |
*/ |
static inline int |
lec_arp_remove(struct lec_priv *priv, |
struct lec_arp_table *to_remove) |
{ |
unsigned long flags; |
unsigned short place; |
struct lec_arp_table *tmp; |
int remove_vcc=1; |
|
spin_lock_irqsave(&priv->lec_arp_lock, flags); |
|
if (!to_remove) { |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
return -1; |
} |
place = HASH(to_remove->mac_addr[ETH_ALEN-1]); |
tmp = priv->lec_arp_tables[place]; |
if (tmp == to_remove) { |
priv->lec_arp_tables[place] = tmp->next; |
} else { |
while(tmp && tmp->next != to_remove) { |
tmp = tmp->next; |
} |
if (!tmp) {/* Entry was not found */ |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
return -1; |
} |
} |
tmp->next = to_remove->next; |
del_timer(&to_remove->timer); |
|
/* If this is the only MAC connected to this VCC, also tear down |
the VCC */ |
if (to_remove->status >= ESI_FLUSH_PENDING) { |
/* |
* ESI_FLUSH_PENDING, ESI_FORWARD_DIRECT |
*/ |
for(place=0;place<LEC_ARP_TABLE_SIZE;place++) { |
for(tmp = priv->lec_arp_tables[place]; tmp != NULL; tmp = tmp->next) { |
if (memcmp(tmp->atm_addr, to_remove->atm_addr, |
ATM_ESA_LEN)==0) { |
remove_vcc=0; |
break; |
} |
} |
} |
if (remove_vcc) |
lec_arp_clear_vccs(to_remove); |
} |
skb_queue_purge(&to_remove->tx_wait); /* FIXME: good place for this? */ |
|
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
|
DPRINTK("LEC_ARP: Removed entry:%2.2x %2.2x %2.2x %2.2x %2.2x %2.2x\n", |
0xff&to_remove->mac_addr[0], 0xff&to_remove->mac_addr[1], |
0xff&to_remove->mac_addr[2], 0xff&to_remove->mac_addr[3], |
0xff&to_remove->mac_addr[4], 0xff&to_remove->mac_addr[5]); |
return 0; |
} |
|
#if DEBUG_ARP_TABLE |
static char* |
get_status_string(unsigned char st) |
{ |
switch(st) { |
case ESI_UNKNOWN: |
return "ESI_UNKNOWN"; |
case ESI_ARP_PENDING: |
return "ESI_ARP_PENDING"; |
case ESI_VC_PENDING: |
return "ESI_VC_PENDING"; |
case ESI_FLUSH_PENDING: |
return "ESI_FLUSH_PENDING"; |
case ESI_FORWARD_DIRECT: |
return "ESI_FORWARD_DIRECT"; |
default: |
return "<UNKNOWN>"; |
} |
} |
#endif |
|
void |
dump_arp_table(struct lec_priv *priv) |
{ |
#if DEBUG_ARP_TABLE |
int i,j, offset; |
struct lec_arp_table *rulla; |
char buf[1024]; |
struct lec_arp_table **lec_arp_tables = |
(struct lec_arp_table **)priv->lec_arp_tables; |
struct lec_arp_table *lec_arp_empty_ones = |
(struct lec_arp_table *)priv->lec_arp_empty_ones; |
struct lec_arp_table *lec_no_forward = |
(struct lec_arp_table *)priv->lec_no_forward; |
struct lec_arp_table *mcast_fwds = priv->mcast_fwds; |
|
|
printk("Dump %p:\n",priv); |
for (i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
rulla = lec_arp_tables[i]; |
offset = 0; |
offset += sprintf(buf,"%d: %p\n",i, rulla); |
while (rulla) { |
offset += sprintf(buf+offset,"Mac:"); |
for(j=0;j<ETH_ALEN;j++) { |
offset+=sprintf(buf+offset, |
"%2.2x ", |
rulla->mac_addr[j]&0xff); |
} |
offset +=sprintf(buf+offset,"Atm:"); |
for(j=0;j<ATM_ESA_LEN;j++) { |
offset+=sprintf(buf+offset, |
"%2.2x ", |
rulla->atm_addr[j]&0xff); |
} |
offset+=sprintf(buf+offset, |
"Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ", |
rulla->vcc?rulla->vcc->vpi:0, |
rulla->vcc?rulla->vcc->vci:0, |
rulla->recv_vcc?rulla->recv_vcc->vpi:0, |
rulla->recv_vcc?rulla->recv_vcc->vci:0, |
rulla->last_used, |
rulla->timestamp, rulla->no_tries); |
offset+=sprintf(buf+offset, |
"Flags:%x, Packets_flooded:%x, Status: %s ", |
rulla->flags, rulla->packets_flooded, |
get_status_string(rulla->status)); |
offset+=sprintf(buf+offset,"->%p\n",rulla->next); |
rulla = rulla->next; |
} |
printk("%s",buf); |
} |
rulla = lec_no_forward; |
if (rulla) |
printk("No forward\n"); |
while(rulla) { |
offset=0; |
offset += sprintf(buf+offset,"Mac:"); |
for(j=0;j<ETH_ALEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x ", |
rulla->mac_addr[j]&0xff); |
} |
offset +=sprintf(buf+offset,"Atm:"); |
for(j=0;j<ATM_ESA_LEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x ", |
rulla->atm_addr[j]&0xff); |
} |
offset+=sprintf(buf+offset, |
"Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ", |
rulla->vcc?rulla->vcc->vpi:0, |
rulla->vcc?rulla->vcc->vci:0, |
rulla->recv_vcc?rulla->recv_vcc->vpi:0, |
rulla->recv_vcc?rulla->recv_vcc->vci:0, |
rulla->last_used, |
rulla->timestamp, rulla->no_tries); |
offset+=sprintf(buf+offset, |
"Flags:%x, Packets_flooded:%x, Status: %s ", |
rulla->flags, rulla->packets_flooded, |
get_status_string(rulla->status)); |
offset+=sprintf(buf+offset,"->%lx\n",(long)rulla->next); |
rulla = rulla->next; |
printk("%s",buf); |
} |
rulla = lec_arp_empty_ones; |
if (rulla) |
printk("Empty ones\n"); |
while(rulla) { |
offset=0; |
offset += sprintf(buf+offset,"Mac:"); |
for(j=0;j<ETH_ALEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x ", |
rulla->mac_addr[j]&0xff); |
} |
offset +=sprintf(buf+offset,"Atm:"); |
for(j=0;j<ATM_ESA_LEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x ", |
rulla->atm_addr[j]&0xff); |
} |
offset+=sprintf(buf+offset, |
"Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ", |
rulla->vcc?rulla->vcc->vpi:0, |
rulla->vcc?rulla->vcc->vci:0, |
rulla->recv_vcc?rulla->recv_vcc->vpi:0, |
rulla->recv_vcc?rulla->recv_vcc->vci:0, |
rulla->last_used, |
rulla->timestamp, rulla->no_tries); |
offset+=sprintf(buf+offset, |
"Flags:%x, Packets_flooded:%x, Status: %s ", |
rulla->flags, rulla->packets_flooded, |
get_status_string(rulla->status)); |
offset+=sprintf(buf+offset,"->%lx\n",(long)rulla->next); |
rulla = rulla->next; |
printk("%s",buf); |
} |
|
rulla = mcast_fwds; |
if (rulla) |
printk("Multicast Forward VCCs\n"); |
while(rulla) { |
offset=0; |
offset += sprintf(buf+offset,"Mac:"); |
for(j=0;j<ETH_ALEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x ", |
rulla->mac_addr[j]&0xff); |
} |
offset +=sprintf(buf+offset,"Atm:"); |
for(j=0;j<ATM_ESA_LEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x ", |
rulla->atm_addr[j]&0xff); |
} |
offset+=sprintf(buf+offset, |
"Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ", |
rulla->vcc?rulla->vcc->vpi:0, |
rulla->vcc?rulla->vcc->vci:0, |
rulla->recv_vcc?rulla->recv_vcc->vpi:0, |
rulla->recv_vcc?rulla->recv_vcc->vci:0, |
rulla->last_used, |
rulla->timestamp, rulla->no_tries); |
offset+=sprintf(buf+offset, |
"Flags:%x, Packets_flooded:%x, Status: %s ", |
rulla->flags, rulla->packets_flooded, |
get_status_string(rulla->status)); |
offset+=sprintf(buf+offset,"->%lx\n",(long)rulla->next); |
rulla = rulla->next; |
printk("%s",buf); |
} |
|
#endif |
} |
|
/* |
* Destruction of arp-cache |
*/ |
void |
lec_arp_destroy(struct lec_priv *priv) |
{ |
struct lec_arp_table *entry, *next; |
int i; |
|
del_timer_sync(&priv->lec_arp_timer); |
|
/* |
* Remove all entries |
*/ |
for (i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
for(entry =priv->lec_arp_tables[i];entry != NULL; entry=next) { |
next = entry->next; |
lec_arp_remove(priv, entry); |
kfree(entry); |
} |
} |
entry = priv->lec_arp_empty_ones; |
while(entry) { |
next = entry->next; |
del_timer_sync(&entry->timer); |
lec_arp_clear_vccs(entry); |
kfree(entry); |
entry = next; |
} |
priv->lec_arp_empty_ones = NULL; |
entry = priv->lec_no_forward; |
while(entry) { |
next = entry->next; |
del_timer_sync(&entry->timer); |
lec_arp_clear_vccs(entry); |
kfree(entry); |
entry = next; |
} |
priv->lec_no_forward = NULL; |
entry = priv->mcast_fwds; |
while(entry) { |
next = entry->next; |
/* No timer, LANEv2 7.1.20 and 2.3.5.3 */ |
lec_arp_clear_vccs(entry); |
kfree(entry); |
entry = next; |
} |
priv->mcast_fwds = NULL; |
priv->mcast_vcc = NULL; |
memset(priv->lec_arp_tables, 0, |
sizeof(struct lec_arp_table*)*LEC_ARP_TABLE_SIZE); |
} |
|
|
/* |
* Find entry by mac_address |
*/ |
static inline struct lec_arp_table* |
lec_arp_find(struct lec_priv *priv, |
unsigned char *mac_addr) |
{ |
unsigned short place; |
struct lec_arp_table *to_return; |
|
DPRINTK("LEC_ARP: lec_arp_find :%2.2x %2.2x %2.2x %2.2x %2.2x %2.2x\n", |
mac_addr[0]&0xff, mac_addr[1]&0xff, mac_addr[2]&0xff, |
mac_addr[3]&0xff, mac_addr[4]&0xff, mac_addr[5]&0xff); |
lec_arp_get(priv); |
place = HASH(mac_addr[ETH_ALEN-1]); |
|
to_return = priv->lec_arp_tables[place]; |
while(to_return) { |
if (memcmp(mac_addr, to_return->mac_addr, ETH_ALEN) == 0) { |
lec_arp_put(priv); |
return to_return; |
} |
to_return = to_return->next; |
} |
lec_arp_put(priv); |
return NULL; |
} |
|
static struct lec_arp_table* |
make_entry(struct lec_priv *priv, unsigned char *mac_addr) |
{ |
struct lec_arp_table *to_return; |
|
to_return=(struct lec_arp_table *)kmalloc(sizeof(struct lec_arp_table), |
GFP_ATOMIC); |
if (!to_return) { |
printk("LEC: Arp entry kmalloc failed\n"); |
return NULL; |
} |
memset(to_return,0,sizeof(struct lec_arp_table)); |
memcpy(to_return->mac_addr, mac_addr, ETH_ALEN); |
init_timer(&to_return->timer); |
to_return->timer.function = lec_arp_expire_arp; |
to_return->timer.data = (unsigned long)to_return; |
to_return->last_used = jiffies; |
to_return->priv = priv; |
skb_queue_head_init(&to_return->tx_wait); |
return to_return; |
} |
|
/* |
* |
* Arp sent timer expired |
* |
*/ |
static void |
lec_arp_expire_arp(unsigned long data) |
{ |
struct lec_arp_table *entry; |
|
entry = (struct lec_arp_table *)data; |
|
DPRINTK("lec_arp_expire_arp\n"); |
if (entry->status == ESI_ARP_PENDING) { |
if (entry->no_tries <= entry->priv->max_retry_count) { |
if (entry->is_rdesc) |
send_to_lecd(entry->priv, l_rdesc_arp_xmt, entry->mac_addr, NULL, NULL); |
else |
send_to_lecd(entry->priv, l_arp_xmt, entry->mac_addr, NULL, NULL); |
entry->no_tries++; |
} |
mod_timer(&entry->timer, jiffies + (1*HZ)); |
} |
} |
|
/* |
* |
* Unknown/unused vcc expire, remove associated entry |
* |
*/ |
static void |
lec_arp_expire_vcc(unsigned long data) |
{ |
struct lec_arp_table *to_remove = (struct lec_arp_table*)data; |
struct lec_priv *priv = (struct lec_priv *)to_remove->priv; |
struct lec_arp_table *entry = NULL; |
|
del_timer(&to_remove->timer); |
|
DPRINTK("LEC_ARP %p %p: lec_arp_expire_vcc vpi:%d vci:%d\n", |
to_remove, priv, |
to_remove->vcc?to_remove->recv_vcc->vpi:0, |
to_remove->vcc?to_remove->recv_vcc->vci:0); |
DPRINTK("eo:%p nf:%p\n",priv->lec_arp_empty_ones,priv->lec_no_forward); |
if (to_remove == priv->lec_arp_empty_ones) |
priv->lec_arp_empty_ones = to_remove->next; |
else { |
entry = priv->lec_arp_empty_ones; |
while (entry && entry->next != to_remove) |
entry = entry->next; |
if (entry) |
entry->next = to_remove->next; |
} |
if (!entry) { |
if (to_remove == priv->lec_no_forward) { |
priv->lec_no_forward = to_remove->next; |
} else { |
entry = priv->lec_no_forward; |
while (entry && entry->next != to_remove) |
entry = entry->next; |
if (entry) |
entry->next = to_remove->next; |
} |
} |
lec_arp_clear_vccs(to_remove); |
kfree(to_remove); |
} |
|
/* |
* Expire entries. |
* 1. Re-set timer |
* 2. For each entry, delete entries that have aged past the age limit. |
* 3. For each entry, depending on the status of the entry, perform |
* the following maintenance. |
* a. If status is ESI_VC_PENDING or ESI_ARP_PENDING then if the |
* tick_count is above the max_unknown_frame_time, clear |
* the tick_count to zero and clear the packets_flooded counter |
* to zero. This supports the packet rate limit per address |
* while flooding unknowns. |
* b. If the status is ESI_FLUSH_PENDING and the tick_count is greater |
* than or equal to the path_switching_delay, change the status |
* to ESI_FORWARD_DIRECT. This causes the flush period to end |
* regardless of the progress of the flush protocol. |
*/ |
static void |
lec_arp_check_expire(unsigned long data) |
{ |
struct lec_priv *priv = (struct lec_priv *)data; |
struct lec_arp_table *entry, *next; |
unsigned long now; |
unsigned long time_to_check; |
int i; |
|
DPRINTK("lec_arp_check_expire %p,%d\n",priv, |
atomic_read(&priv->lec_arp_users)); |
DPRINTK("expire: eo:%p nf:%p\n",priv->lec_arp_empty_ones, |
priv->lec_no_forward); |
if (!atomic_read(&priv->lec_arp_users)) { |
lec_arp_get(priv); |
now = jiffies; |
for(i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
for(entry = priv->lec_arp_tables[i]; entry != NULL; ) { |
if ((entry->flags) & LEC_REMOTE_FLAG && |
priv->topology_change) |
time_to_check=priv->forward_delay_time; |
else |
time_to_check = priv->aging_time; |
|
DPRINTK("About to expire: %lx - %lx > %lx\n", |
now,entry->last_used, time_to_check); |
if( time_after(now, entry->last_used+ |
time_to_check) && |
!(entry->flags & LEC_PERMANENT_FLAG) && |
!(entry->mac_addr[0] & 0x01) ) { /* LANE2: 7.1.20 */ |
/* Remove entry */ |
DPRINTK("LEC:Entry timed out\n"); |
next = entry->next; |
lec_arp_remove(priv, entry); |
kfree(entry); |
entry = next; |
} else { |
/* Something else */ |
if ((entry->status == ESI_VC_PENDING || |
entry->status == ESI_ARP_PENDING) |
&& time_after_eq(now, |
entry->timestamp + |
priv->max_unknown_frame_time)) { |
entry->timestamp = jiffies; |
entry->packets_flooded = 0; |
if (entry->status == ESI_VC_PENDING) |
send_to_lecd(priv, l_svc_setup, entry->mac_addr, entry->atm_addr, NULL); |
} |
if (entry->status == ESI_FLUSH_PENDING |
&& |
time_after_eq(now, entry->timestamp+ |
priv->path_switching_delay)) { |
struct sk_buff *skb; |
|
while ((skb = skb_dequeue(&entry->tx_wait))) |
lec_send(entry->vcc, skb, entry->priv); |
entry->last_used = jiffies; |
entry->status = |
ESI_FORWARD_DIRECT; |
} |
entry = entry->next; |
} |
} |
} |
lec_arp_put(priv); |
} |
|
mod_timer(&priv->lec_arp_timer, jiffies + LEC_ARP_REFRESH_INTERVAL); |
} |
/* |
* Try to find vcc where mac_address is attached. |
* |
*/ |
struct atm_vcc* |
lec_arp_resolve(struct lec_priv *priv, unsigned char *mac_to_find, int is_rdesc, |
struct lec_arp_table **ret_entry) |
{ |
struct lec_arp_table *entry; |
|
if (mac_to_find[0]&0x01) { |
switch (priv->lane_version) { |
case 1: |
return priv->mcast_vcc; |
break; |
case 2: /* LANE2 wants arp for multicast addresses */ |
if ( memcmp(mac_to_find, bus_mac, ETH_ALEN) == 0) |
return priv->mcast_vcc; |
break; |
default: |
break; |
} |
} |
|
entry = lec_arp_find(priv, mac_to_find); |
|
if (entry) { |
if (entry->status == ESI_FORWARD_DIRECT) { |
/* Connection Ok */ |
entry->last_used = jiffies; |
*ret_entry = entry; |
return entry->vcc; |
} |
/* Data direct VC not yet set up, check to see if the unknown |
frame count is greater than the limit. If the limit has |
not been reached, allow the caller to send packet to |
BUS. */ |
if (entry->status != ESI_FLUSH_PENDING && |
entry->packets_flooded<priv->maximum_unknown_frame_count) { |
entry->packets_flooded++; |
DPRINTK("LEC_ARP: Flooding..\n"); |
return priv->mcast_vcc; |
} |
/* We got here because entry->status == ESI_FLUSH_PENDING |
* or BUS flood limit was reached for an entry which is |
* in ESI_ARP_PENDING or ESI_VC_PENDING state. |
*/ |
*ret_entry = entry; |
DPRINTK("lec: entry->status %d entry->vcc %p\n", entry->status, entry->vcc); |
return NULL; |
} else { |
/* No matching entry was found */ |
entry = make_entry(priv, mac_to_find); |
DPRINTK("LEC_ARP: Making entry\n"); |
if (!entry) { |
return priv->mcast_vcc; |
} |
lec_arp_add(priv, entry); |
/* We want arp-request(s) to be sent */ |
entry->packets_flooded =1; |
entry->status = ESI_ARP_PENDING; |
entry->no_tries = 1; |
entry->last_used = entry->timestamp = jiffies; |
entry->is_rdesc = is_rdesc; |
if (entry->is_rdesc) |
send_to_lecd(priv, l_rdesc_arp_xmt, mac_to_find, NULL, NULL); |
else |
send_to_lecd(priv, l_arp_xmt, mac_to_find, NULL, NULL); |
entry->timer.expires = jiffies + (1*HZ); |
entry->timer.function = lec_arp_expire_arp; |
add_timer(&entry->timer); |
return priv->mcast_vcc; |
} |
} |
|
int |
lec_addr_delete(struct lec_priv *priv, unsigned char *atm_addr, |
unsigned long permanent) |
{ |
struct lec_arp_table *entry, *next; |
int i; |
|
lec_arp_get(priv); |
DPRINTK("lec_addr_delete\n"); |
for(i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
for(entry=priv->lec_arp_tables[i];entry != NULL; entry=next) { |
next = entry->next; |
if (!memcmp(atm_addr, entry->atm_addr, ATM_ESA_LEN) |
&& (permanent || |
!(entry->flags & LEC_PERMANENT_FLAG))) { |
lec_arp_remove(priv, entry); |
kfree(entry); |
} |
lec_arp_put(priv); |
return 0; |
} |
} |
lec_arp_put(priv); |
return -1; |
} |
|
/* |
* Notifies: Response to arp_request (atm_addr != NULL) |
*/ |
void |
lec_arp_update(struct lec_priv *priv, unsigned char *mac_addr, |
unsigned char *atm_addr, unsigned long remoteflag, |
unsigned int targetless_le_arp) |
{ |
struct lec_arp_table *entry, *tmp; |
int i; |
|
DPRINTK("lec:%s", (targetless_le_arp) ? "targetless ": " "); |
DPRINTK("lec_arp_update mac:%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n", |
mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3], |
mac_addr[4],mac_addr[5]); |
|
entry = lec_arp_find(priv, mac_addr); |
if (entry == NULL && targetless_le_arp) |
return; /* LANE2: ignore targetless LE_ARPs for which |
* we have no entry in the cache. 7.1.30 |
*/ |
lec_arp_get(priv); |
if (priv->lec_arp_empty_ones) { |
entry = priv->lec_arp_empty_ones; |
if (!memcmp(entry->atm_addr, atm_addr, ATM_ESA_LEN)) { |
priv->lec_arp_empty_ones = entry->next; |
} else { |
while(entry->next && memcmp(entry->next->atm_addr, |
atm_addr, ATM_ESA_LEN)) |
entry = entry->next; |
if (entry->next) { |
tmp = entry; |
entry = entry->next; |
tmp->next = entry->next; |
} else |
entry = NULL; |
|
} |
if (entry) { |
del_timer(&entry->timer); |
tmp = lec_arp_find(priv, mac_addr); |
if (tmp) { |
del_timer(&tmp->timer); |
tmp->status = ESI_FORWARD_DIRECT; |
memcpy(tmp->atm_addr, atm_addr, ATM_ESA_LEN); |
tmp->vcc = entry->vcc; |
tmp->old_push = entry->old_push; |
tmp->last_used = jiffies; |
del_timer(&entry->timer); |
kfree(entry); |
entry=tmp; |
} else { |
entry->status = ESI_FORWARD_DIRECT; |
memcpy(entry->mac_addr, mac_addr, ETH_ALEN); |
entry->last_used = jiffies; |
lec_arp_add(priv, entry); |
} |
if (remoteflag) |
entry->flags|=LEC_REMOTE_FLAG; |
else |
entry->flags&=~LEC_REMOTE_FLAG; |
lec_arp_put(priv); |
DPRINTK("After update\n"); |
dump_arp_table(priv); |
return; |
} |
} |
entry = lec_arp_find(priv, mac_addr); |
if (!entry) { |
entry = make_entry(priv, mac_addr); |
if (!entry) { |
lec_arp_put(priv); |
return; |
} |
entry->status = ESI_UNKNOWN; |
lec_arp_add(priv, entry); |
/* Temporary, changes before end of function */ |
} |
memcpy(entry->atm_addr, atm_addr, ATM_ESA_LEN); |
del_timer(&entry->timer); |
for(i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
for(tmp=priv->lec_arp_tables[i];tmp;tmp=tmp->next) { |
if (entry != tmp && |
!memcmp(tmp->atm_addr, atm_addr, |
ATM_ESA_LEN)) { |
/* Vcc to this host exists */ |
if (tmp->status > ESI_VC_PENDING) { |
/* |
* ESI_FLUSH_PENDING, |
* ESI_FORWARD_DIRECT |
*/ |
entry->vcc = tmp->vcc; |
entry->old_push=tmp->old_push; |
} |
entry->status=tmp->status; |
break; |
} |
} |
} |
if (remoteflag) |
entry->flags|=LEC_REMOTE_FLAG; |
else |
entry->flags&=~LEC_REMOTE_FLAG; |
if (entry->status == ESI_ARP_PENDING || |
entry->status == ESI_UNKNOWN) { |
entry->status = ESI_VC_PENDING; |
send_to_lecd(priv, l_svc_setup, entry->mac_addr, atm_addr, NULL); |
} |
DPRINTK("After update2\n"); |
dump_arp_table(priv); |
lec_arp_put(priv); |
} |
|
/* |
* Notifies: Vcc setup ready |
*/ |
void |
lec_vcc_added(struct lec_priv *priv, struct atmlec_ioc *ioc_data, |
struct atm_vcc *vcc, |
void (*old_push)(struct atm_vcc *vcc, struct sk_buff *skb)) |
{ |
struct lec_arp_table *entry; |
int i, found_entry=0; |
|
lec_arp_get(priv); |
if (ioc_data->receive == 2) { |
/* Vcc for Multicast Forward. No timer, LANEv2 7.1.20 and 2.3.5.3 */ |
|
DPRINTK("LEC_ARP: Attaching mcast forward\n"); |
#if 0 |
entry = lec_arp_find(priv, bus_mac); |
if (!entry) { |
printk("LEC_ARP: Multicast entry not found!\n"); |
lec_arp_put(priv); |
return; |
} |
memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN); |
entry->recv_vcc = vcc; |
entry->old_recv_push = old_push; |
#endif |
entry = make_entry(priv, bus_mac); |
if (entry == NULL) { |
lec_arp_put(priv); |
return; |
} |
del_timer(&entry->timer); |
memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN); |
entry->recv_vcc = vcc; |
entry->old_recv_push = old_push; |
entry->next = priv->mcast_fwds; |
priv->mcast_fwds = entry; |
lec_arp_put(priv); |
return; |
} else if (ioc_data->receive == 1) { |
/* Vcc which we don't want to make default vcc, attach it |
anyway. */ |
DPRINTK("LEC_ARP:Attaching data direct, not default :%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n", |
ioc_data->atm_addr[0],ioc_data->atm_addr[1], |
ioc_data->atm_addr[2],ioc_data->atm_addr[3], |
ioc_data->atm_addr[4],ioc_data->atm_addr[5], |
ioc_data->atm_addr[6],ioc_data->atm_addr[7], |
ioc_data->atm_addr[8],ioc_data->atm_addr[9], |
ioc_data->atm_addr[10],ioc_data->atm_addr[11], |
ioc_data->atm_addr[12],ioc_data->atm_addr[13], |
ioc_data->atm_addr[14],ioc_data->atm_addr[15], |
ioc_data->atm_addr[16],ioc_data->atm_addr[17], |
ioc_data->atm_addr[18],ioc_data->atm_addr[19]); |
entry = make_entry(priv, bus_mac); |
if (entry == NULL) { |
lec_arp_put(priv); |
return; |
} |
memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN); |
memset(entry->mac_addr, 0, ETH_ALEN); |
entry->recv_vcc = vcc; |
entry->old_recv_push = old_push; |
entry->status = ESI_UNKNOWN; |
entry->timer.expires = jiffies + priv->vcc_timeout_period; |
entry->timer.function = lec_arp_expire_vcc; |
add_timer(&entry->timer); |
entry->next = priv->lec_no_forward; |
priv->lec_no_forward = entry; |
lec_arp_put(priv); |
dump_arp_table(priv); |
return; |
} |
DPRINTK("LEC_ARP:Attaching data direct, default:%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n", |
ioc_data->atm_addr[0],ioc_data->atm_addr[1], |
ioc_data->atm_addr[2],ioc_data->atm_addr[3], |
ioc_data->atm_addr[4],ioc_data->atm_addr[5], |
ioc_data->atm_addr[6],ioc_data->atm_addr[7], |
ioc_data->atm_addr[8],ioc_data->atm_addr[9], |
ioc_data->atm_addr[10],ioc_data->atm_addr[11], |
ioc_data->atm_addr[12],ioc_data->atm_addr[13], |
ioc_data->atm_addr[14],ioc_data->atm_addr[15], |
ioc_data->atm_addr[16],ioc_data->atm_addr[17], |
ioc_data->atm_addr[18],ioc_data->atm_addr[19]); |
for (i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
for (entry = priv->lec_arp_tables[i];entry;entry=entry->next) { |
if (memcmp(ioc_data->atm_addr, entry->atm_addr, |
ATM_ESA_LEN)==0) { |
DPRINTK("LEC_ARP: Attaching data direct\n"); |
DPRINTK("Currently -> Vcc: %d, Rvcc:%d\n", |
entry->vcc?entry->vcc->vci:0, |
entry->recv_vcc?entry->recv_vcc->vci:0); |
found_entry=1; |
del_timer(&entry->timer); |
entry->vcc = vcc; |
entry->old_push = old_push; |
if (entry->status == ESI_VC_PENDING) { |
if(priv->maximum_unknown_frame_count |
==0) |
entry->status = |
ESI_FORWARD_DIRECT; |
else { |
entry->timestamp = jiffies; |
entry->status = |
ESI_FLUSH_PENDING; |
#if 0 |
send_to_lecd(priv,l_flush_xmt, |
NULL, |
entry->atm_addr, |
NULL); |
#endif |
} |
} else { |
/* They were forming a connection |
to us, and we to them. Our |
ATM address is numerically lower |
than theirs, so we make connection |
we formed into default VCC (8.1.11). |
Connection they made gets torn |
down. This might confuse some |
clients. Can be changed if |
someone reports trouble... */ |
; |
} |
} |
} |
} |
if (found_entry) { |
lec_arp_put(priv); |
DPRINTK("After vcc was added\n"); |
dump_arp_table(priv); |
return; |
} |
/* Not found, snatch address from first data packet that arrives from |
this vcc */ |
entry = make_entry(priv, bus_mac); |
if (!entry) { |
lec_arp_put(priv); |
return; |
} |
entry->vcc = vcc; |
entry->old_push = old_push; |
memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN); |
memset(entry->mac_addr, 0, ETH_ALEN); |
entry->status = ESI_UNKNOWN; |
entry->next = priv->lec_arp_empty_ones; |
priv->lec_arp_empty_ones = entry; |
entry->timer.expires = jiffies + priv->vcc_timeout_period; |
entry->timer.function = lec_arp_expire_vcc; |
add_timer(&entry->timer); |
lec_arp_put(priv); |
DPRINTK("After vcc was added\n"); |
dump_arp_table(priv); |
} |
|
void |
lec_flush_complete(struct lec_priv *priv, unsigned long tran_id) |
{ |
struct lec_arp_table *entry; |
int i; |
|
DPRINTK("LEC:lec_flush_complete %lx\n",tran_id); |
for (i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
for (entry=priv->lec_arp_tables[i];entry;entry=entry->next) { |
if (entry->flush_tran_id == tran_id && |
entry->status == ESI_FLUSH_PENDING) { |
struct sk_buff *skb; |
|
while ((skb = skb_dequeue(&entry->tx_wait))) |
lec_send(entry->vcc, skb, entry->priv); |
entry->status = ESI_FORWARD_DIRECT; |
DPRINTK("LEC_ARP: Flushed\n"); |
} |
} |
} |
dump_arp_table(priv); |
} |
|
void |
lec_set_flush_tran_id(struct lec_priv *priv, |
unsigned char *atm_addr, unsigned long tran_id) |
{ |
struct lec_arp_table *entry; |
int i; |
|
for (i=0;i<LEC_ARP_TABLE_SIZE;i++) |
for(entry=priv->lec_arp_tables[i];entry;entry=entry->next) |
if (!memcmp(atm_addr, entry->atm_addr, ATM_ESA_LEN)) { |
entry->flush_tran_id = tran_id; |
DPRINTK("Set flush transaction id to %lx for %p\n",tran_id,entry); |
} |
} |
|
int |
lec_mcast_make(struct lec_priv *priv, struct atm_vcc *vcc) |
{ |
unsigned char mac_addr[] = { |
0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; |
struct lec_arp_table *to_add; |
|
lec_arp_get(priv); |
to_add = make_entry(priv, mac_addr); |
if (!to_add) { |
lec_arp_put(priv); |
return -ENOMEM; |
} |
memcpy(to_add->atm_addr, vcc->remote.sas_addr.prv, ATM_ESA_LEN); |
to_add->status = ESI_FORWARD_DIRECT; |
to_add->flags |= LEC_PERMANENT_FLAG; |
to_add->vcc = vcc; |
to_add->old_push = vcc->push; |
vcc->push = lec_push; |
priv->mcast_vcc = vcc; |
lec_arp_add(priv, to_add); |
lec_arp_put(priv); |
return 0; |
} |
|
void |
lec_vcc_close(struct lec_priv *priv, struct atm_vcc *vcc) |
{ |
struct lec_arp_table *entry, *next; |
int i; |
|
DPRINTK("LEC_ARP: lec_vcc_close vpi:%d vci:%d\n",vcc->vpi,vcc->vci); |
dump_arp_table(priv); |
lec_arp_get(priv); |
for(i=0;i<LEC_ARP_TABLE_SIZE;i++) { |
for(entry = priv->lec_arp_tables[i];entry; entry=next) { |
next = entry->next; |
if (vcc == entry->vcc) { |
lec_arp_remove(priv, entry); |
kfree(entry); |
if (priv->mcast_vcc == vcc) { |
priv->mcast_vcc = NULL; |
} |
} |
} |
} |
|
entry = priv->lec_arp_empty_ones; |
priv->lec_arp_empty_ones = NULL; |
while (entry != NULL) { |
next = entry->next; |
if (entry->vcc == vcc) { /* leave it out from the list */ |
lec_arp_clear_vccs(entry); |
del_timer(&entry->timer); |
kfree(entry); |
} |
else { /* put it back to the list */ |
entry->next = priv->lec_arp_empty_ones; |
priv->lec_arp_empty_ones = entry; |
} |
entry = next; |
} |
|
entry = priv->lec_no_forward; |
priv->lec_no_forward = NULL; |
while (entry != NULL) { |
next = entry->next; |
if (entry->recv_vcc == vcc) { |
lec_arp_clear_vccs(entry); |
del_timer(&entry->timer); |
kfree(entry); |
} |
else { |
entry->next = priv->lec_no_forward; |
priv->lec_no_forward = entry; |
} |
entry = next; |
} |
|
entry = priv->mcast_fwds; |
priv->mcast_fwds = NULL; |
while (entry != NULL) { |
next = entry->next; |
if (entry->recv_vcc == vcc) { |
lec_arp_clear_vccs(entry); |
/* No timer, LANEv2 7.1.20 and 2.3.5.3 */ |
kfree(entry); |
} |
else { |
entry->next = priv->mcast_fwds; |
priv->mcast_fwds = entry; |
} |
entry = next; |
} |
|
lec_arp_put(priv); |
dump_arp_table(priv); |
} |
|
void |
lec_arp_check_empties(struct lec_priv *priv, |
struct atm_vcc *vcc, struct sk_buff *skb) |
{ |
unsigned long flags; |
struct lec_arp_table *entry, *prev; |
struct lecdatahdr_8023 *hdr = (struct lecdatahdr_8023 *)skb->data; |
unsigned char *src; |
#ifdef CONFIG_TR |
struct lecdatahdr_8025 *tr_hdr = (struct lecdatahdr_8025 *)skb->data; |
|
if (priv->is_trdev) src = tr_hdr->h_source; |
else |
#endif |
src = hdr->h_source; |
|
lec_arp_get(priv); |
entry = priv->lec_arp_empty_ones; |
if (vcc == entry->vcc) { |
spin_lock_irqsave(&priv->lec_arp_lock, flags); |
del_timer(&entry->timer); |
memcpy(entry->mac_addr, src, ETH_ALEN); |
entry->status = ESI_FORWARD_DIRECT; |
entry->last_used = jiffies; |
priv->lec_arp_empty_ones = entry->next; |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
/* We might have got an entry */ |
if ((prev=lec_arp_find(priv,src))) { |
lec_arp_remove(priv, prev); |
kfree(prev); |
} |
lec_arp_add(priv, entry); |
lec_arp_put(priv); |
return; |
} |
spin_lock_irqsave(&priv->lec_arp_lock, flags); |
prev = entry; |
entry = entry->next; |
while (entry && entry->vcc != vcc) { |
prev= entry; |
entry = entry->next; |
} |
if (!entry) { |
DPRINTK("LEC_ARP: Arp_check_empties: entry not found!\n"); |
lec_arp_put(priv); |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
return; |
} |
del_timer(&entry->timer); |
memcpy(entry->mac_addr, src, ETH_ALEN); |
entry->status = ESI_FORWARD_DIRECT; |
entry->last_used = jiffies; |
prev->next = entry->next; |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
if ((prev = lec_arp_find(priv, src))) { |
lec_arp_remove(priv, prev); |
kfree(prev); |
} |
lec_arp_add(priv, entry); |
lec_arp_put(priv); |
} |
MODULE_LICENSE("GPL"); |
/proc.c
0,0 → 1,684
/* net/atm/proc.c - ATM /proc interface */ |
|
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
/* |
* The mechanism used here isn't designed for speed but rather for convenience |
* of implementation. We only return one entry per read system call, so we can |
* be reasonably sure not to overrun the page and race conditions may lead to |
* the addition or omission of some lines but never to any corruption of a |
* line's internal structure. |
* |
* Making the whole thing slightly more efficient is left as an exercise to the |
* reader. (Suggestions: wrapper which loops to get several entries per system |
* call; or make --left slightly more clever to avoid O(n^2) characteristics.) |
* I find it fast enough on my unloaded 266 MHz Pentium 2 :-) |
*/ |
|
|
#include <linux/config.h> |
#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/errno.h> |
#include <linux/atm.h> |
#include <linux/atmdev.h> |
#include <linux/netdevice.h> |
#include <linux/atmclip.h> |
#include <linux/atmarp.h> |
#include <linux/if_arp.h> |
#include <linux/init.h> /* for __init */ |
#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 */ |
|
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
#include <net/atmclip.h> |
#include "ipcommon.h" |
#endif |
|
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) |
#include "lec.h" |
#include "lec_arpc.h" |
#endif |
|
static ssize_t proc_dev_atm_read(struct file *file,char *buf,size_t count, |
loff_t *pos); |
static ssize_t proc_spec_atm_read(struct file *file,char *buf,size_t count, |
loff_t *pos); |
|
static struct file_operations proc_dev_atm_operations = { |
read: proc_dev_atm_read, |
}; |
|
static struct file_operations proc_spec_atm_operations = { |
read: proc_spec_atm_read, |
}; |
|
static void add_stats(char *buf,const char *aal, |
const struct k_atm_aal_stats *stats) |
{ |
sprintf(strchr(buf,0),"%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 dev_info(const struct atm_dev *dev,char *buf) |
{ |
int off,i; |
|
off = sprintf(buf,"%3d %-8s",dev->number,dev->type); |
for (i = 0; i < ESI_LEN; i++) |
off += sprintf(buf+off,"%02x",dev->esi[i]); |
strcat(buf," "); |
add_stats(buf,"0",&dev->stats.aal0); |
strcat(buf," "); |
add_stats(buf,"5",&dev->stats.aal5); |
sprintf(strchr(buf,0), "\t[%d]", atomic_read(&dev->refcnt)); |
strcat(buf,"\n"); |
} |
|
|
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
|
|
static int svc_addr(char *buf,struct sockaddr_atmsvc *addr) |
{ |
static int code[] = { 1,2,10,6,1,0 }; |
static int e164[] = { 1,8,4,6,1,0 }; |
int *fields; |
int len,i,j,pos; |
|
len = 0; |
if (*addr->sas_addr.pub) { |
strcpy(buf,addr->sas_addr.pub); |
len = strlen(addr->sas_addr.pub); |
buf += len; |
if (*addr->sas_addr.prv) { |
*buf++ = '+'; |
len++; |
} |
} |
else if (!*addr->sas_addr.prv) { |
strcpy(buf,"(none)"); |
return strlen(buf); |
} |
if (*addr->sas_addr.prv) { |
len += 44; |
pos = 0; |
fields = *addr->sas_addr.prv == ATM_AFI_E164 ? e164 : code; |
for (i = 0; fields[i]; i++) { |
for (j = fields[i]; j; j--) { |
sprintf(buf,"%02X",addr->sas_addr.prv[pos++]); |
buf += 2; |
} |
if (fields[i+1]) *buf++ = '.'; |
} |
} |
return len; |
} |
|
|
static void atmarp_info(struct net_device *dev,struct atmarp_entry *entry, |
struct clip_vcc *clip_vcc,char *buf) |
{ |
unsigned char *ip; |
int svc,off,ip_len; |
|
svc = !clip_vcc || clip_vcc->vcc->sk->family == AF_ATMSVC; |
off = sprintf(buf,"%-6s%-4s%-4s%5ld ",dev->name,svc ? "SVC" : "PVC", |
!clip_vcc || clip_vcc->encap ? "LLC" : "NULL", |
(jiffies-(clip_vcc ? clip_vcc->last_use : entry->neigh->used))/ |
HZ); |
ip = (unsigned char *) &entry->ip; |
ip_len = sprintf(buf+off,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); |
off += ip_len; |
while (ip_len++ < 16) buf[off++] = ' '; |
if (!clip_vcc) |
if (time_before(jiffies, entry->expires)) |
strcpy(buf+off,"(resolving)\n"); |
else sprintf(buf+off,"(expired, ref %d)\n", |
atomic_read(&entry->neigh->refcnt)); |
else if (!svc) |
sprintf(buf+off,"%d.%d.%d\n",clip_vcc->vcc->dev->number, |
clip_vcc->vcc->vpi,clip_vcc->vcc->vci); |
else { |
off += svc_addr(buf+off,&clip_vcc->vcc->remote); |
strcpy(buf+off,"\n"); |
} |
} |
|
|
#endif |
|
|
static void pvc_info(struct atm_vcc *vcc, char *buf, int clip_info) |
{ |
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 */ |
int off; |
|
off = sprintf(buf,"%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 defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
if (clip_info && (vcc->push == atm_clip_ops->clip_push)) { |
struct clip_vcc *clip_vcc = CLIP_VCC(vcc); |
struct net_device *dev; |
|
dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : NULL; |
off += sprintf(buf+off,"CLIP, Itf:%s, Encap:", |
dev ? dev->name : "none?"); |
if (clip_vcc->encap) |
off += sprintf(buf+off,"LLC/SNAP"); |
else |
off += sprintf(buf+off,"None"); |
} |
#endif |
strcpy(buf+off,"\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 vc_info(struct atm_vcc *vcc,char *buf) |
{ |
char *here; |
|
here = buf+sprintf(buf,"%p ",vcc); |
if (!vcc->dev) here += sprintf(here,"Unassigned "); |
else here += sprintf(here,"%3d %3d %5d ",vcc->dev->number,vcc->vpi, |
vcc->vci); |
switch (vcc->sk->family) { |
case AF_ATMPVC: |
here += sprintf(here,"PVC"); |
break; |
case AF_ATMSVC: |
here += sprintf(here,"SVC"); |
break; |
default: |
here += sprintf(here,"%3d",vcc->sk->family); |
} |
here += sprintf(here," %04lx %5d %7d/%7d %7d/%7d\n",vcc->flags.bits, |
vcc->reply, |
atomic_read(&vcc->sk->wmem_alloc),vcc->sk->sndbuf, |
atomic_read(&vcc->sk->rmem_alloc),vcc->sk->rcvbuf); |
} |
|
|
static void svc_info(struct atm_vcc *vcc,char *buf) |
{ |
char *here; |
int i; |
|
if (!vcc->dev) |
sprintf(buf,sizeof(void *) == 4 ? "N/A@%p%10s" : "N/A@%p%2s", |
vcc,""); |
else sprintf(buf,"%3d %3d %5d ",vcc->dev->number,vcc->vpi, |
vcc->vci); |
here = strchr(buf,0); |
here += sprintf(here,"%-10s ",vcc_state(vcc)); |
here += sprintf(here,"%s%s",vcc->remote.sas_addr.pub, |
*vcc->remote.sas_addr.pub && *vcc->remote.sas_addr.prv ? "+" : ""); |
if (*vcc->remote.sas_addr.prv) |
for (i = 0; i < ATM_ESA_LEN; i++) |
here += sprintf(here,"%02x", |
vcc->remote.sas_addr.prv[i]); |
strcat(here,"\n"); |
} |
|
|
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) |
|
static char* |
lec_arp_get_status_string(unsigned char status) |
{ |
switch(status) { |
case ESI_UNKNOWN: |
return "ESI_UNKNOWN "; |
case ESI_ARP_PENDING: |
return "ESI_ARP_PENDING "; |
case ESI_VC_PENDING: |
return "ESI_VC_PENDING "; |
case ESI_FLUSH_PENDING: |
return "ESI_FLUSH_PENDING "; |
case ESI_FORWARD_DIRECT: |
return "ESI_FORWARD_DIRECT"; |
default: |
return "<Unknown> "; |
} |
} |
|
static void |
lec_info(struct lec_arp_table *entry, char *buf) |
{ |
int j, offset=0; |
|
for(j=0;j<ETH_ALEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x",0xff&entry->mac_addr[j]); |
} |
offset+=sprintf(buf+offset, " "); |
for(j=0;j<ATM_ESA_LEN;j++) { |
offset+=sprintf(buf+offset,"%2.2x",0xff&entry->atm_addr[j]); |
} |
offset+=sprintf(buf+offset, " %s %4.4x", |
lec_arp_get_status_string(entry->status), |
entry->flags&0xffff); |
if (entry->vcc) { |
offset+=sprintf(buf+offset, "%3d %3d ", entry->vcc->vpi, |
entry->vcc->vci); |
} else |
offset+=sprintf(buf+offset, " "); |
if (entry->recv_vcc) { |
offset+=sprintf(buf+offset, " %3d %3d", |
entry->recv_vcc->vpi, entry->recv_vcc->vci); |
} |
|
sprintf(buf+offset,"\n"); |
} |
|
#endif |
|
static int atm_devices_info(loff_t pos,char *buf) |
{ |
struct atm_dev *dev; |
struct list_head *p; |
int left; |
|
if (!pos) { |
return sprintf(buf,"Itf Type ESI/\"MAC\"addr " |
"AAL(TX,err,RX,err,drop) ... [refcnt]\n"); |
} |
left = pos-1; |
spin_lock(&atm_dev_lock); |
list_for_each(p, &atm_devs) { |
dev = list_entry(p, struct atm_dev, dev_list); |
if (left-- == 0) { |
dev_info(dev,buf); |
spin_unlock(&atm_dev_lock); |
return strlen(buf); |
} |
} |
spin_unlock(&atm_dev_lock); |
return 0; |
} |
|
/* |
* FIXME: it isn't safe to walk the VCC list without turning off interrupts. |
* What is really needed is some lock on the devices. Ditto for ATMARP. |
*/ |
|
static int atm_pvc_info(loff_t pos,char *buf) |
{ |
struct sock *s; |
struct atm_vcc *vcc; |
int left, clip_info = 0; |
|
if (!pos) { |
return sprintf(buf,"Itf VPI VCI AAL RX(PCR,Class) " |
"TX(PCR,Class)\n"); |
} |
left = pos-1; |
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
if (try_atm_clip_ops()) |
clip_info = 1; |
#endif |
read_lock(&vcc_sklist_lock); |
for(s = vcc_sklist; s; s = s->next) { |
vcc = s->protinfo.af_atm; |
if (vcc->sk->family == PF_ATMPVC && vcc->dev && !left--) { |
pvc_info(vcc,buf,clip_info); |
read_unlock(&vcc_sklist_lock); |
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
if (clip_info && atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
#endif |
return strlen(buf); |
} |
} |
read_unlock(&vcc_sklist_lock); |
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
if (clip_info && atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
#endif |
return 0; |
} |
|
|
static int atm_vc_info(loff_t pos,char *buf) |
{ |
struct atm_vcc *vcc; |
struct sock *s; |
int left; |
|
if (!pos) |
return sprintf(buf,sizeof(void *) == 4 ? "%-8s%s" : "%-16s%s", |
"Address"," Itf VPI VCI Fam Flags Reply Send buffer" |
" Recv buffer\n"); |
left = pos-1; |
read_lock(&vcc_sklist_lock); |
for(s = vcc_sklist; s; s = s->next) { |
vcc = s->protinfo.af_atm; |
if (!left--) { |
vc_info(vcc,buf); |
read_unlock(&vcc_sklist_lock); |
return strlen(buf); |
} |
} |
read_unlock(&vcc_sklist_lock); |
|
return 0; |
} |
|
|
static int atm_svc_info(loff_t pos,char *buf) |
{ |
struct sock *s; |
struct atm_vcc *vcc; |
int left; |
|
if (!pos) |
return sprintf(buf,"Itf VPI VCI State Remote\n"); |
left = pos-1; |
read_lock(&vcc_sklist_lock); |
for(s = vcc_sklist; s; s = s->next) { |
vcc = s->protinfo.af_atm; |
if (vcc->sk->family == PF_ATMSVC && !left--) { |
svc_info(vcc,buf); |
read_unlock(&vcc_sklist_lock); |
return strlen(buf); |
} |
} |
read_unlock(&vcc_sklist_lock); |
|
return 0; |
} |
|
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
static int atm_arp_info(loff_t pos,char *buf) |
{ |
struct neighbour *n; |
int i,count; |
|
if (!pos) { |
return sprintf(buf,"IPitf TypeEncp Idle IP address " |
"ATM address\n"); |
} |
if (!try_atm_clip_ops()) |
return 0; |
count = pos; |
read_lock_bh(&clip_tbl_hook->lock); |
for (i = 0; i <= NEIGH_HASHMASK; i++) |
for (n = clip_tbl_hook->hash_buckets[i]; n; n = n->next) { |
struct atmarp_entry *entry = NEIGH2ENTRY(n); |
struct clip_vcc *vcc; |
|
if (!entry->vccs) { |
if (--count) continue; |
atmarp_info(n->dev,entry,NULL,buf); |
read_unlock_bh(&clip_tbl_hook->lock); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
return strlen(buf); |
} |
for (vcc = entry->vccs; vcc; |
vcc = vcc->next) { |
if (--count) continue; |
atmarp_info(n->dev,entry,vcc,buf); |
read_unlock_bh(&clip_tbl_hook->lock); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
return strlen(buf); |
} |
} |
read_unlock_bh(&clip_tbl_hook->lock); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
return 0; |
} |
#endif |
|
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) |
static int atm_lec_info(loff_t pos,char *buf) |
{ |
unsigned long flags; |
struct lec_priv *priv; |
struct lec_arp_table *entry; |
int i, count, d, e; |
struct net_device *dev; |
|
if (!pos) { |
return sprintf(buf,"Itf MAC ATM destination" |
" Status Flags " |
"VPI/VCI Recv VPI/VCI\n"); |
} |
if (!try_atm_lane_ops()) |
return 0; /* the lane module is not there yet */ |
|
count = pos; |
for(d = 0; d < MAX_LEC_ITF; d++) { |
dev = atm_lane_ops->get_lec(d); |
if (!dev || !(priv = (struct lec_priv *) dev->priv)) |
continue; |
spin_lock_irqsave(&priv->lec_arp_lock, flags); |
for(i = 0; i < LEC_ARP_TABLE_SIZE; i++) { |
for(entry = priv->lec_arp_tables[i]; entry; entry = entry->next) { |
if (--count) |
continue; |
e = sprintf(buf,"%s ", dev->name); |
lec_info(entry, buf+e); |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
dev_put(dev); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
return strlen(buf); |
} |
} |
for(entry = priv->lec_arp_empty_ones; entry; entry = entry->next) { |
if (--count) |
continue; |
e = sprintf(buf,"%s ", dev->name); |
lec_info(entry, buf+e); |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
dev_put(dev); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
return strlen(buf); |
} |
for(entry = priv->lec_no_forward; entry; entry=entry->next) { |
if (--count) |
continue; |
e = sprintf(buf,"%s ", dev->name); |
lec_info(entry, buf+e); |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
dev_put(dev); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
return strlen(buf); |
} |
for(entry = priv->mcast_fwds; entry; entry = entry->next) { |
if (--count) |
continue; |
e = sprintf(buf,"%s ", dev->name); |
lec_info(entry, buf+e); |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
dev_put(dev); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
return strlen(buf); |
} |
spin_unlock_irqrestore(&priv->lec_arp_lock, flags); |
dev_put(dev); |
} |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
return 0; |
} |
#endif |
|
|
static ssize_t proc_dev_atm_read(struct file *file,char *buf,size_t count, |
loff_t *pos) |
{ |
struct atm_dev *dev; |
unsigned long page; |
int length; |
|
if (count == 0) return 0; |
page = get_free_page(GFP_KERNEL); |
if (!page) return -ENOMEM; |
dev = ((struct proc_dir_entry *) file->f_dentry->d_inode->u.generic_ip) |
->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; |
} |
|
|
static ssize_t proc_spec_atm_read(struct file *file,char *buf,size_t count, |
loff_t *pos) |
{ |
unsigned long page; |
int length; |
int (*info)(loff_t,char *); |
info = ((struct proc_dir_entry *) file->f_dentry->d_inode->u.generic_ip) |
->data; |
|
if (count == 0) return 0; |
page = get_free_page(GFP_KERNEL); |
if (!page) return -ENOMEM; |
length = (*info)(*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; |
|
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_ATOMIC); |
if (!dev->proc_name) |
goto fail1; |
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 fail0; |
dev->proc_entry->data = dev; |
dev->proc_entry->proc_fops = &proc_dev_atm_operations; |
dev->proc_entry->owner = THIS_MODULE; |
return 0; |
fail0: |
kfree(dev->proc_name); |
fail1: |
return error; |
} |
|
|
void atm_proc_dev_deregister(struct atm_dev *dev) |
{ |
remove_proc_entry(dev->proc_name, atm_proc_root); |
kfree(dev->proc_name); |
} |
|
|
#define CREATE_ENTRY(name) \ |
name = create_proc_entry(#name,0,atm_proc_root); \ |
if (!name) goto cleanup; \ |
name->data = atm_##name##_info; \ |
name->proc_fops = &proc_spec_atm_operations; \ |
name->owner = THIS_MODULE |
|
static struct proc_dir_entry *devices = NULL, *pvc = NULL, |
*svc = NULL, *arp = NULL, *lec = NULL, *vc = NULL; |
|
static void atm_proc_cleanup(void) |
{ |
if (devices) |
remove_proc_entry("devices",atm_proc_root); |
if (pvc) |
remove_proc_entry("pvc",atm_proc_root); |
if (svc) |
remove_proc_entry("svc",atm_proc_root); |
if (arp) |
remove_proc_entry("arp",atm_proc_root); |
if (lec) |
remove_proc_entry("lec",atm_proc_root); |
if (vc) |
remove_proc_entry("vc",atm_proc_root); |
remove_proc_entry("net/atm",NULL); |
} |
|
int atm_proc_init(void) |
{ |
atm_proc_root = proc_mkdir("net/atm",NULL); |
if (!atm_proc_root) |
return -ENOMEM; |
CREATE_ENTRY(devices); |
CREATE_ENTRY(pvc); |
CREATE_ENTRY(svc); |
CREATE_ENTRY(vc); |
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
CREATE_ENTRY(arp); |
#endif |
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) |
CREATE_ENTRY(lec); |
#endif |
return 0; |
|
cleanup: |
atm_proc_cleanup(); |
return -ENOMEM; |
} |
|
void atm_proc_exit(void) |
{ |
atm_proc_cleanup(); |
} |
/mpoa_caches.h
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)(uint32_t dst_ip, |
struct mpoa_client *client); |
in_cache_entry *(*get)(uint32_t dst_ip, struct mpoa_client *client); |
in_cache_entry *(*get_with_mask)(uint32_t dst_ip, |
struct mpoa_client *client, |
uint32_t 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; |
uint32_t 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)(uint32_t cache_id, struct mpoa_client *client); |
eg_cache_entry *(*get_by_tag)(uint32_t 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)(uint32_t 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 |
/lec.h
0,0 → 1,165
/* |
* |
* Lan Emulation client header file |
* |
* Marko Kiiskila carnil@cs.tut.fi |
* |
*/ |
|
#ifndef _LEC_H_ |
#define _LEC_H_ |
|
#include <linux/config.h> |
#include <linux/atmdev.h> |
#include <linux/netdevice.h> |
#include <linux/atmlec.h> |
|
#if defined (CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
#include <linux/if_bridge.h> |
extern struct net_bridge_fdb_entry *(*br_fdb_get_hook)(struct net_bridge *br, |
unsigned char *addr); |
extern void (*br_fdb_put_hook)(struct net_bridge_fdb_entry *ent); |
#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */ |
|
#define LEC_HEADER_LEN 16 |
|
struct lecdatahdr_8023 { |
unsigned short le_header; |
unsigned char h_dest[ETH_ALEN]; |
unsigned char h_source[ETH_ALEN]; |
unsigned short h_type; |
}; |
|
struct lecdatahdr_8025 { |
unsigned short 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); |
}; |
|
struct atm_lane_ops { |
int (*lecd_attach)(struct atm_vcc *vcc, int arg); |
int (*mcast_attach)(struct atm_vcc *vcc, int arg); |
int (*vcc_attach)(struct atm_vcc *vcc, void *arg); |
struct net_device * (*get_lec)(int itf); |
struct module *owner; |
}; |
|
/* |
* 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 lec_arp_table *lec_arp_empty_ones; |
/* Used for storing VCC's that don't have a MAC address attached yet */ |
struct lec_arp_table *lec_arp_tables[LEC_ARP_TABLE_SIZE]; |
/* Actual LE ARP table */ |
struct lec_arp_table *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 lec_arp_table *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. */ |
atomic_t lec_arp_users; |
spinlock_t lec_arp_lock; |
struct atm_vcc *mcast_vcc; /* Default Multicast Send VCC */ |
struct atm_vcc *lecd; |
struct timer_list lec_arp_timer; |
/* 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 */ |
}; |
|
int lecd_attach(struct atm_vcc *vcc, int arg); |
int lec_vcc_attach(struct atm_vcc *vcc, void *arg); |
int lec_mcast_attach(struct atm_vcc *vcc, int arg); |
struct net_device *get_dev_lec(int itf); |
int make_lec(struct atm_vcc *vcc); |
int send_to_lecd(struct lec_priv *priv, |
atmlec_msg_type type, unsigned char *mac_addr, |
unsigned char *atm_addr, struct sk_buff *data); |
void lec_push(struct atm_vcc *vcc, struct sk_buff *skb); |
|
extern struct atm_lane_ops *atm_lane_ops; |
void atm_lane_ops_set(struct atm_lane_ops *hook); |
int try_atm_lane_ops(void); |
|
#endif /* _LEC_H_ */ |
|
/addr.c
0,0 → 1,141
/* net/atm/addr.c - Local ATM address registry */ |
|
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#include <linux/atm.h> |
#include <linux/atmdev.h> |
#include <linux/sched.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) |
{ |
unsigned long flags; |
struct atm_dev_addr *this; |
|
spin_lock_irqsave(&dev->lock, flags); |
while (dev->local) { |
this = dev->local; |
dev->local = this->next; |
kfree(this); |
} |
spin_unlock_irqrestore(&dev->lock, flags); |
notify_sigd(dev); |
} |
|
|
int atm_add_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr) |
{ |
unsigned long flags; |
struct atm_dev_addr **walk; |
int error; |
|
error = check_addr(addr); |
if (error) |
return error; |
spin_lock_irqsave(&dev->lock, flags); |
for (walk = &dev->local; *walk; walk = &(*walk)->next) |
if (identical(&(*walk)->addr,addr)) { |
spin_unlock_irqrestore(&dev->lock, flags); |
return -EEXIST; |
} |
*walk = kmalloc(sizeof(struct atm_dev_addr), GFP_ATOMIC); |
if (!*walk) { |
spin_unlock_irqrestore(&dev->lock, flags); |
return -ENOMEM; |
} |
(*walk)->addr = *addr; |
(*walk)->next = NULL; |
spin_unlock_irqrestore(&dev->lock, flags); |
notify_sigd(dev); |
return 0; |
} |
|
|
int atm_del_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr) |
{ |
unsigned long flags; |
struct atm_dev_addr **walk,*this; |
int error; |
|
error = check_addr(addr); |
if (error) |
return error; |
spin_lock_irqsave(&dev->lock, flags); |
for (walk = &dev->local; *walk; walk = &(*walk)->next) |
if (identical(&(*walk)->addr,addr)) break; |
if (!*walk) { |
spin_unlock_irqrestore(&dev->lock, flags); |
return -ENOENT; |
} |
this = *walk; |
*walk = this->next; |
kfree(this); |
spin_unlock_irqrestore(&dev->lock, flags); |
notify_sigd(dev); |
return 0; |
} |
|
|
int atm_get_addr(struct atm_dev *dev,struct sockaddr_atmsvc *u_buf,int size) |
{ |
unsigned long flags; |
struct atm_dev_addr *walk; |
int total = 0, error; |
struct sockaddr_atmsvc *tmp_buf, *tmp_bufp; |
|
|
spin_lock_irqsave(&dev->lock, flags); |
for (walk = dev->local; walk; walk = walk->next) |
total += sizeof(struct sockaddr_atmsvc); |
tmp_buf = tmp_bufp = kmalloc(total, GFP_ATOMIC); |
if (!tmp_buf) { |
spin_unlock_irqrestore(&dev->lock, flags); |
return -ENOMEM; |
} |
for (walk = dev->local; walk; walk = walk->next) |
memcpy(tmp_bufp++, &walk->addr, sizeof(struct sockaddr_atmsvc)); |
spin_unlock_irqrestore(&dev->lock, flags); |
error = total > size ? -E2BIG : total; |
if (copy_to_user(u_buf, tmp_buf, total < size ? total : size)) |
error = -EFAULT; |
kfree(tmp_buf); |
return error; |
} |
/resources.c
0,0 → 1,405
/* net/atm/resources.c - Staticly allocated resources */ |
|
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#include <linux/config.h> |
#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 <net/sock.h> /* for struct sock */ |
#include <asm/segment.h> /* for get_fs_long and put_fs_long */ |
|
#include "common.h" |
#include "resources.h" |
#include "addr.h" |
|
|
LIST_HEAD(atm_devs); |
spinlock_t atm_dev_lock = SPIN_LOCK_UNLOCKED; |
|
|
static struct atm_dev *__alloc_atm_dev(const char *type) |
{ |
struct atm_dev *dev; |
|
dev = kmalloc(sizeof(*dev), GFP_ATOMIC); |
if (!dev) |
return NULL; |
memset(dev, 0, sizeof(*dev)); |
dev->type = type; |
dev->signal = ATM_PHY_SIG_UNKNOWN; |
dev->link_rate = ATM_OC3_PCR; |
spin_lock_init(&dev->lock); |
|
return dev; |
} |
|
|
static void __free_atm_dev(struct atm_dev *dev) |
{ |
kfree(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->ops) && (dev->number == number)) { |
atm_dev_hold(dev); |
return dev; |
} |
} |
return NULL; |
} |
|
struct atm_dev *atm_dev_lookup(int number) |
{ |
struct atm_dev *dev; |
|
spin_lock(&atm_dev_lock); |
dev = __atm_dev_lookup(number); |
spin_unlock(&atm_dev_lock); |
return dev; |
} |
|
struct atm_dev *atm_dev_register(const char *type, const struct atmdev_ops *ops, |
int number, atm_dev_flags_t *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; |
} |
spin_lock(&atm_dev_lock); |
if (number != -1) { |
if ((inuse = __atm_dev_lookup(number))) { |
atm_dev_put(inuse); |
spin_unlock(&atm_dev_lock); |
__free_atm_dev(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); |
list_add_tail(&dev->dev_list, &atm_devs); |
spin_unlock(&atm_dev_lock); |
|
#ifdef CONFIG_PROC_FS |
if (ops->proc_read) { |
if (atm_proc_dev_register(dev) < 0) { |
printk(KERN_ERR "atm_dev_register: " |
"atm_proc_dev_register failed for dev %s\n", |
type); |
spin_lock(&atm_dev_lock); |
list_del(&dev->dev_list); |
spin_unlock(&atm_dev_lock); |
__free_atm_dev(dev); |
return NULL; |
} |
} |
#endif |
|
return dev; |
} |
|
|
void atm_dev_deregister(struct atm_dev *dev) |
{ |
unsigned long warning_time; |
|
#ifdef CONFIG_PROC_FS |
if (dev->ops->proc_read) |
atm_proc_dev_deregister(dev); |
#endif |
spin_lock(&atm_dev_lock); |
list_del(&dev->dev_list); |
spin_unlock(&atm_dev_lock); |
|
warning_time = jiffies; |
while (atomic_read(&dev->refcnt) != 1) { |
current->state = TASK_INTERRUPTIBLE; |
schedule_timeout(HZ / 4); |
current->state = TASK_RUNNING; |
if ((jiffies - warning_time) > 10 * HZ) { |
printk(KERN_EMERG "atm_dev_deregister: waiting for " |
"dev %d to become free. Usage count = %d\n", |
dev->number, atomic_read(&dev->refcnt)); |
warning_time = jiffies; |
} |
} |
|
__free_atm_dev(dev); |
} |
|
void shutdown_atm_dev(struct atm_dev *dev) |
{ |
if (atomic_read(&dev->refcnt) > 1) { |
set_bit(ATM_DF_CLOSE, &dev->flags); |
return; |
} |
if (dev->ops->dev_close) |
dev->ops->dev_close(dev); |
atm_dev_deregister(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 *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, unsigned long arg) |
{ |
void *buf; |
int error = 0, len, number, size = 0; |
struct atm_dev *dev; |
|
if (cmd == ATM_GETNAMES) { |
int *tmp_buf, *tmp_bufp; |
struct list_head *p; |
/* |
* ATM_GETNAMES is a special case: it doesn't require a |
* device number argument |
*/ |
if (get_user(buf, &((struct atm_iobuf *) arg)->buffer)) |
return -EFAULT; |
if (get_user(len, &((struct atm_iobuf *) arg)->length)) |
return -EFAULT; |
spin_lock(&atm_dev_lock); |
list_for_each(p, &atm_devs) |
size += sizeof(int); |
if (size > len) { |
spin_unlock(&atm_dev_lock); |
return -E2BIG; |
} |
tmp_buf = tmp_bufp = kmalloc(size, GFP_ATOMIC); |
if (!tmp_buf) { |
spin_unlock(&atm_dev_lock); |
return -ENOMEM; |
} |
list_for_each(p, &atm_devs) { |
dev = list_entry(p, struct atm_dev, dev_list); |
*tmp_bufp++ = dev->number; |
} |
spin_unlock(&atm_dev_lock); |
error = (copy_to_user(buf, tmp_buf, size) || |
put_user(size, &((struct atm_iobuf *) arg)->length)) |
? -EFAULT : 0; |
kfree(tmp_buf); |
return error; |
} |
|
if (get_user(buf, &((struct atmif_sioc *) arg)->arg)) |
return -EFAULT; |
if (get_user(len, &((struct atmif_sioc *) arg)->length)) |
return -EFAULT; |
if (get_user(number, &((struct atmif_sioc *) arg)->number)) |
return -EFAULT; |
|
if (!(dev = atm_dev_lookup(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); |
break; |
case ATM_ADDADDR: |
case ATM_DELADDR: |
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) |
error = atm_add_addr(dev, &addr); |
else |
error = atm_del_addr(dev, &addr); |
goto done; |
} |
case ATM_GETADDR: |
error = atm_get_addr(dev, buf, len); |
if (error < 0) |
goto done; |
size = error; |
/* write back size even if it's zero */ |
goto write_size; |
case ATM_SETLOOP: |
if (__ATM_LM_XTRMT((int) (long) buf) && |
__ATM_LM_XTLOC((int) (long) buf) > |
__ATM_LM_XTRMT((int) (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) { |
write_size: |
error = put_user(size, |
&((struct atmif_sioc *) arg)->length) |
? -EFAULT : 0; |
} |
done: |
atm_dev_put(dev); |
return error; |
} |
|
|
EXPORT_SYMBOL(atm_dev_register); |
EXPORT_SYMBOL(atm_dev_deregister); |
EXPORT_SYMBOL(atm_dev_lookup); |
EXPORT_SYMBOL(shutdown_atm_dev); |
/signaling.c
0,0 → 1,260
/* 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; |
static DECLARE_WAIT_QUEUE_HEAD(sigd_sleep); |
|
|
static void sigd_put_skb(struct sk_buff *skb) |
{ |
#ifdef WAIT_FOR_DEMON |
static unsigned long silence = 0; |
DECLARE_WAITQUEUE(wait,current); |
|
add_wait_queue(&sigd_sleep,&wait); |
while (!sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
if (time_after(jiffies, silence) || silence == 0) { |
printk(KERN_INFO "atmsvc: waiting for signaling demon " |
"...\n"); |
silence = (jiffies+30*HZ)|1; |
} |
schedule(); |
} |
current->state = TASK_RUNNING; |
remove_wait_queue(&sigd_sleep,&wait); |
#else |
if (!sigd) { |
printk(KERN_WARNING "atmsvc: no signaling demon\n"); |
kfree_skb(skb); |
return; |
} |
#endif |
atm_force_charge(sigd,skb->truesize); |
skb_queue_tail(&sigd->sk->receive_queue,skb); |
wake_up(&sigd->sleep); |
} |
|
|
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; |
|
msg = (struct atmsvc_msg *) skb->data; |
atomic_sub(skb->truesize, &vcc->sk->wmem_alloc); |
DPRINTK("sigd_send %d (0x%lx)\n",(int) msg->type, |
(unsigned long) msg->vcc); |
vcc = *(struct atm_vcc **) &msg->vcc; |
switch (msg->type) { |
case as_okay: |
vcc->reply = msg->reply; |
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); |
vcc->reply = msg->reply; |
vcc->sk->err = -msg->reply; |
break; |
case as_indicate: |
vcc = *(struct atm_vcc **) &msg->listen_vcc; |
DPRINTK("as_indicate!!!\n"); |
lock_sock(vcc->sk); |
if (vcc->sk->ack_backlog == vcc->sk->max_ack_backlog) { |
sigd_enq(0,as_reject,vcc,NULL,NULL); |
goto as_indicate_complete; |
} |
vcc->sk->ack_backlog++; |
skb_queue_tail(&vcc->sk->receive_queue,skb); |
if (vcc->callback) { |
DPRINTK("waking vcc->sleep 0x%p\n", |
&vcc->sleep); |
vcc->callback(vcc); |
} |
as_indicate_complete: |
release_sock(vcc->sk); |
return 0; |
case as_close: |
set_bit(ATM_VF_RELEASED,&vcc->flags); |
clear_bit(ATM_VF_READY,&vcc->flags); |
vcc->reply = msg->reply; |
vcc->sk->err = -msg->reply; |
break; |
case as_modify: |
modify_qos(vcc,msg); |
break; |
default: |
printk(KERN_ALERT "sigd_send: bad message type %d\n", |
(int) msg->type); |
return -EINVAL; |
} |
if (vcc->callback) vcc->callback(vcc); |
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; |
|
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; |
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 (vcc->sk->family == PF_ATMSVC && |
!test_bit(ATM_VF_META, &vcc->flags)) { |
set_bit(ATM_VF_RELEASED, &vcc->flags); |
vcc->reply = -EUNATCH; |
vcc->sk->err = EUNATCH; |
wake_up(&vcc->sleep); |
} |
} |
|
|
static void sigd_close(struct atm_vcc *vcc) |
{ |
struct sock *s; |
|
DPRINTK("sigd_close\n"); |
sigd = NULL; |
if (skb_peek(&vcc->sk->receive_queue)) |
printk(KERN_ERR "sigd_close: closing with requests pending\n"); |
skb_queue_purge(&vcc->sk->receive_queue); |
|
read_lock(&vcc_sklist_lock); |
for(s = vcc_sklist; s; s = s->next) { |
struct atm_vcc *vcc = s->protinfo.af_atm; |
|
if (vcc->dev) |
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(vcc->sk); |
set_bit(ATM_VF_META,&vcc->flags); |
set_bit(ATM_VF_READY,&vcc->flags); |
wake_up(&sigd_sleep); |
return 0; |
} |
/lec_arpc.h
0,0 → 1,116
/* |
* Lec arp cache |
* Marko Kiiskila carnil@cs.tut.fi |
* |
*/ |
#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 lec_arp_table *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 */ |
void (*old_close)(struct atm_vcc *vcc); |
/* We want to see when this |
* vcc gets closed */ |
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; /* LANE2: Each MAC address can have TLVs */ |
u32 sizeoftlvs; /* associated with it. sizeoftlvs tells the */ |
/* the length of the tlvs array */ |
struct sk_buff_head tx_wait; /* wait queue for outgoing packets */ |
}; |
|
struct tlv { /* LANE2: Template tlv struct for accessing */ |
/* the tlvs in the lec_arp_table->tlvs array*/ |
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 |
|
/* Protos */ |
void lec_arp_init(struct lec_priv *priv); |
int lec_mcast_make(struct lec_priv *priv, struct atm_vcc *vcc); |
void lec_arp_destroy(struct lec_priv *priv); |
void lec_vcc_close(struct lec_priv *priv, struct atm_vcc *vcc); |
|
struct atm_vcc *lec_arp_resolve(struct lec_priv *priv, |
unsigned char *mac_to_addr, |
int is_rdesc, |
struct lec_arp_table **ret_entry); |
void lec_vcc_added(struct lec_priv *dev, |
struct atmlec_ioc *ioc_data, struct atm_vcc *vcc, |
void (*old_push)(struct atm_vcc *vcc, struct sk_buff *skb)); |
void lec_arp_check_empties(struct lec_priv *priv, |
struct atm_vcc *vcc, struct sk_buff *skb); |
int lec_addr_delete(struct lec_priv *priv, |
unsigned char *mac_addr, unsigned long permanent); |
void lec_flush_complete(struct lec_priv *priv, unsigned long tran_id); |
void lec_arp_update(struct lec_priv *priv, |
unsigned char *mac_addr, unsigned char *atm_addr, |
unsigned long remoteflag, unsigned int targetless_le_arp); |
void lec_set_flush_tran_id(struct lec_priv *priv, |
unsigned char *mac_addr, unsigned long tran_id); |
|
#endif |
/addr.h
0,0 → 1,18
/* 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); |
int atm_add_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr); |
int atm_del_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr); |
int atm_get_addr(struct atm_dev *dev,struct sockaddr_atmsvc *u_buf,int size); |
|
#endif |
/resources.h
0,0 → 1,29
/* 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/config.h> |
#include <linux/atmdev.h> |
|
|
extern struct list_head atm_devs; |
extern spinlock_t atm_dev_lock; |
|
|
int atm_dev_ioctl(unsigned int cmd, unsigned long arg); |
|
|
#ifdef CONFIG_PROC_FS |
|
#include <linux/proc_fs.h> |
|
int atm_proc_dev_register(struct atm_dev *dev); |
void atm_proc_dev_deregister(struct atm_dev *dev); |
|
#endif |
|
#endif |
/mpc.c
0,0 → 1,1478
#include <linux/kernel.h> |
#include <linux/string.h> |
#include <linux/timer.h> |
#include <linux/init.h> |
#include <linux/bitops.h> |
|
/* We are an ethernet device */ |
#include <linux/if_ether.h> |
#include <linux/netdevice.h> |
#include <linux/etherdevice.h> |
#include <net/sock.h> |
#include <linux/skbuff.h> |
#include <linux/ip.h> |
#include <asm/byteorder.h> |
#include <asm/uaccess.h> |
#include <asm/checksum.h> /* for ip_fast_csum() */ |
#include <net/arp.h> |
#include <net/dst.h> |
#include <linux/proc_fs.h> |
|
/* And atm device */ |
#include <linux/atmdev.h> |
#include <linux/atmlec.h> |
#include <linux/atmmpc.h> |
/* Modular too */ |
#include <linux/config.h> |
#include <linux/module.h> |
|
#include "lec.h" |
#include "mpc.h" |
#include "resources.h" |
|
/* |
* mpc.c: Implementation of MPOA client kernel part |
*/ |
|
#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 |
|
|
|
#define MPOA_TAG_LEN 4 |
|
/* mpc_daemon -> kernel */ |
static void MPOA_trigger_rcvd (struct k_message *msg, struct mpoa_client *mpc); |
static void MPOA_res_reply_rcvd(struct k_message *msg, struct mpoa_client *mpc); |
static void ingress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc); |
static void egress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc); |
static void mps_death(struct k_message *msg, struct mpoa_client *mpc); |
static void clean_up(struct k_message *msg, struct mpoa_client *mpc, int action); |
static void MPOA_cache_impos_rcvd(struct k_message *msg, struct mpoa_client *mpc); |
static void set_mpc_ctrl_addr_rcvd(struct k_message *mesg, struct mpoa_client *mpc); |
static void set_mps_mac_addr_rcvd(struct k_message *mesg, struct mpoa_client *mpc); |
|
static uint8_t *copy_macs(struct mpoa_client *mpc, uint8_t *router_mac, |
uint8_t *tlvs, uint8_t mps_macs, uint8_t device_type); |
static void purge_egress_shortcut(struct atm_vcc *vcc, eg_cache_entry *entry); |
|
static void send_set_mps_ctrl_addr(char *addr, struct mpoa_client *mpc); |
static void mpoad_close(struct atm_vcc *vcc); |
static int msg_from_mpoad(struct atm_vcc *vcc, struct sk_buff *skb); |
|
static void mpc_push(struct atm_vcc *vcc, struct sk_buff *skb); |
static int mpc_send_packet(struct sk_buff *skb, struct net_device *dev); |
static int mpoa_event_listener(struct notifier_block *mpoa_notifier, unsigned long event, void *dev); |
static void mpc_timer_refresh(void); |
static void mpc_cache_check( unsigned long checking_time ); |
|
static struct llc_snap_hdr llc_snap_mpoa_ctrl = { |
0xaa, 0xaa, 0x03, |
{0x00, 0x00, 0x5e}, |
{0x00, 0x03} /* For MPOA control PDUs */ |
}; |
static struct llc_snap_hdr llc_snap_mpoa_data = { |
0xaa, 0xaa, 0x03, |
{0x00, 0x00, 0x00}, |
{0x08, 0x00} /* This is for IP PDUs only */ |
}; |
static struct llc_snap_hdr llc_snap_mpoa_data_tagged = { |
0xaa, 0xaa, 0x03, |
{0x00, 0x00, 0x00}, |
{0x88, 0x4c} /* This is for tagged data PDUs */ |
}; |
|
static struct notifier_block mpoa_notifier = { |
mpoa_event_listener, |
NULL, |
0 |
}; |
|
#ifdef CONFIG_PROC_FS |
extern int mpc_proc_init(void); |
extern void mpc_proc_clean(void); |
#endif |
|
struct mpoa_client *mpcs = NULL; /* FIXME */ |
static struct atm_mpoa_qos *qos_head = NULL; |
static struct timer_list mpc_timer; |
|
|
static struct mpoa_client *find_mpc_by_itfnum(int itf) |
{ |
struct mpoa_client *mpc; |
|
mpc = mpcs; /* our global linked list */ |
while (mpc != NULL) { |
if (mpc->dev_num == itf) |
return mpc; |
mpc = mpc->next; |
} |
|
return NULL; /* not found */ |
} |
|
static struct mpoa_client *find_mpc_by_vcc(struct atm_vcc *vcc) |
{ |
struct mpoa_client *mpc; |
|
mpc = mpcs; /* our global linked list */ |
while (mpc != NULL) { |
if (mpc->mpoad_vcc == vcc) |
return mpc; |
mpc = mpc->next; |
} |
|
return NULL; /* not found */ |
} |
|
static struct mpoa_client *find_mpc_by_lec(struct net_device *dev) |
{ |
struct mpoa_client *mpc; |
|
mpc = mpcs; /* our global linked list */ |
while (mpc != NULL) { |
if (mpc->dev == dev) |
return mpc; |
mpc = mpc->next; |
} |
|
return NULL; /* not found */ |
} |
|
/* |
* Functions for managing QoS list |
*/ |
|
/* |
* Overwrites the old entry or makes a new one. |
*/ |
struct atm_mpoa_qos *atm_mpoa_add_qos(uint32_t dst_ip, struct atm_qos *qos) |
{ |
struct atm_mpoa_qos *entry; |
|
entry = atm_mpoa_search_qos(dst_ip); |
if (entry != NULL) { |
entry->qos = *qos; |
return entry; |
} |
|
entry = kmalloc(sizeof(struct atm_mpoa_qos), GFP_KERNEL); |
if (entry == NULL) { |
printk("mpoa: atm_mpoa_add_qos: out of memory\n"); |
return entry; |
} |
|
entry->ipaddr = dst_ip; |
entry->qos = *qos; |
|
entry->next = qos_head; |
qos_head = entry; |
|
return entry; |
} |
|
struct atm_mpoa_qos *atm_mpoa_search_qos(uint32_t dst_ip) |
{ |
struct atm_mpoa_qos *qos; |
|
qos = qos_head; |
while( qos != NULL ){ |
if(qos->ipaddr == dst_ip) { |
break; |
} |
qos = qos->next; |
} |
|
return qos; |
} |
|
/* |
* Returns 0 for failure |
*/ |
int atm_mpoa_delete_qos(struct atm_mpoa_qos *entry) |
{ |
|
struct atm_mpoa_qos *curr; |
|
if (entry == NULL) return 0; |
if (entry == qos_head) { |
qos_head = qos_head->next; |
kfree(entry); |
return 1; |
} |
|
curr = qos_head; |
while (curr != NULL) { |
if (curr->next == entry) { |
curr->next = entry->next; |
kfree(entry); |
return 1; |
} |
curr = curr->next; |
} |
|
return 0; |
} |
|
void atm_mpoa_disp_qos(char *page, int *len) |
{ |
|
unsigned char *ip; |
char ipaddr[16]; |
struct atm_mpoa_qos *qos; |
|
qos = qos_head; |
*len += sprintf(page + *len, "QoS entries for shortcuts:\n"); |
*len += sprintf(page + *len, "IP address\n TX:max_pcr pcr min_pcr max_cdv max_sdu\n RX:max_pcr pcr min_pcr max_cdv max_sdu\n"); |
|
ipaddr[sizeof(ipaddr)-1] = '\0'; |
while (qos != NULL) { |
ip = (unsigned char *)&qos->ipaddr; |
sprintf(ipaddr, "%u.%u.%u.%u", NIPQUAD(ip)); |
*len += sprintf(page + *len, "%u.%u.%u.%u\n %-7d %-7d %-7d %-7d %-7d\n %-7d %-7d %-7d %-7d %-7d\n", |
NIPQUAD(ipaddr), |
qos->qos.txtp.max_pcr, qos->qos.txtp.pcr, qos->qos.txtp.min_pcr, qos->qos.txtp.max_cdv, qos->qos.txtp.max_sdu, |
qos->qos.rxtp.max_pcr, qos->qos.rxtp.pcr, qos->qos.rxtp.min_pcr, qos->qos.rxtp.max_cdv, qos->qos.rxtp.max_sdu); |
qos = qos->next; |
} |
|
return; |
} |
|
static struct net_device *find_lec_by_itfnum(int itf) |
{ |
struct net_device *dev; |
if (!try_atm_lane_ops()) |
return NULL; |
|
dev = atm_lane_ops->get_lec(itf); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
return dev; |
} |
|
static struct mpoa_client *alloc_mpc(void) |
{ |
struct mpoa_client *mpc; |
|
mpc = kmalloc(sizeof (struct mpoa_client), GFP_KERNEL); |
if (mpc == NULL) |
return NULL; |
memset(mpc, 0, sizeof(struct mpoa_client)); |
mpc->ingress_lock = RW_LOCK_UNLOCKED; |
mpc->egress_lock = RW_LOCK_UNLOCKED; |
mpc->next = mpcs; |
atm_mpoa_init_cache(mpc); |
|
mpc->parameters.mpc_p1 = MPC_P1; |
mpc->parameters.mpc_p2 = MPC_P2; |
memset(mpc->parameters.mpc_p3,0,sizeof(mpc->parameters.mpc_p3)); |
mpc->parameters.mpc_p4 = MPC_P4; |
mpc->parameters.mpc_p5 = MPC_P5; |
mpc->parameters.mpc_p6 = MPC_P6; |
|
mpcs = mpc; |
|
return mpc; |
} |
|
/* |
* |
* start_mpc() puts the MPC on line. All the packets destined |
* to the lec underneath us are now being monitored and |
* shortcuts will be established. |
* |
*/ |
static void start_mpc(struct mpoa_client *mpc, struct net_device *dev) |
{ |
|
dprintk("mpoa: (%s) start_mpc:\n", mpc->dev->name); |
if (dev->hard_start_xmit == NULL) { |
printk("mpoa: (%s) start_mpc: dev->hard_start_xmit == NULL, not starting\n", |
dev->name); |
return; |
} |
mpc->old_hard_start_xmit = dev->hard_start_xmit; |
dev->hard_start_xmit = mpc_send_packet; |
|
return; |
} |
|
static void stop_mpc(struct mpoa_client *mpc) |
{ |
|
dprintk("mpoa: (%s) stop_mpc:", mpc->dev->name); |
|
/* Lets not nullify lec device's dev->hard_start_xmit */ |
if (mpc->dev->hard_start_xmit != mpc_send_packet) { |
dprintk(" mpc already stopped, not fatal\n"); |
return; |
} |
dprintk("\n"); |
mpc->dev->hard_start_xmit = mpc->old_hard_start_xmit; |
mpc->old_hard_start_xmit = NULL; |
/* close_shortcuts(mpc); ??? FIXME */ |
|
return; |
} |
|
static const char * __attribute__ ((unused)) mpoa_device_type_string(char type) |
{ |
switch(type) { |
case NON_MPOA: |
return "non-MPOA device"; |
break; |
case MPS: |
return "MPS"; |
break; |
case MPC: |
return "MPC"; |
break; |
case MPS_AND_MPC: |
return "both MPS and MPC"; |
break; |
default: |
return "unspecified (non-MPOA) device"; |
break; |
} |
|
return ""; /* not reached */ |
} |
|
/* |
* lec device calls this via its dev->priv->lane2_ops->associate_indicator() |
* when it sees a TLV in LE_ARP packet. |
* We fill in the pointer above when we see a LANE2 lec initializing |
* See LANE2 spec 3.1.5 |
* |
* Quite a big and ugly function but when you look at it |
* all it does is to try to locate and parse MPOA Device |
* Type TLV. |
* We give our lec a pointer to this function and when the |
* lec sees a TLV it uses the pointer to call this function. |
* |
*/ |
static void lane2_assoc_ind(struct net_device *dev, uint8_t *mac_addr, |
uint8_t *tlvs, uint32_t sizeoftlvs) |
{ |
uint32_t type; |
uint8_t length, mpoa_device_type, number_of_mps_macs; |
uint8_t *end_of_tlvs; |
struct mpoa_client *mpc; |
|
mpoa_device_type = number_of_mps_macs = 0; /* silence gcc */ |
dprintk("mpoa: (%s) lane2_assoc_ind: received TLV(s), ", dev->name); |
dprintk("total length of all TLVs %d\n", sizeoftlvs); |
mpc = find_mpc_by_lec(dev); /* Sampo-Fix: moved here from below */ |
if (mpc == NULL) { |
printk("mpoa: (%s) lane2_assoc_ind: no mpc\n", dev->name); |
return; |
} |
end_of_tlvs = tlvs + sizeoftlvs; |
while (end_of_tlvs - tlvs >= 5) { |
type = (tlvs[0] << 24) | (tlvs[1] << 16) | (tlvs[2] << 8) | tlvs[3]; |
length = tlvs[4]; |
tlvs += 5; |
dprintk(" type 0x%x length %02x\n", type, length); |
if (tlvs + length > end_of_tlvs) { |
printk("TLV value extends past its buffer, aborting parse\n"); |
return; |
} |
|
if (type == 0) { |
printk("mpoa: (%s) lane2_assoc_ind: TLV type was 0, returning\n", dev->name); |
return; |
} |
|
if (type != TLV_MPOA_DEVICE_TYPE) { |
tlvs += length; |
continue; /* skip other TLVs */ |
} |
mpoa_device_type = *tlvs++; |
number_of_mps_macs = *tlvs++; |
dprintk("mpoa: (%s) MPOA device type '%s', ", dev->name, mpoa_device_type_string(mpoa_device_type)); |
if (mpoa_device_type == MPS_AND_MPC && |
length < (42 + number_of_mps_macs*ETH_ALEN)) { /* :) */ |
printk("\nmpoa: (%s) lane2_assoc_ind: short MPOA Device Type TLV\n", |
dev->name); |
continue; |
} |
if ((mpoa_device_type == MPS || mpoa_device_type == MPC) |
&& length < 22 + number_of_mps_macs*ETH_ALEN) { |
printk("\nmpoa: (%s) lane2_assoc_ind: short MPOA Device Type TLV\n", |
dev->name); |
continue; |
} |
if (mpoa_device_type != MPS && mpoa_device_type != MPS_AND_MPC) { |
dprintk("ignoring non-MPS device\n"); |
if (mpoa_device_type == MPC) tlvs += 20; |
continue; /* we are only interested in MPSs */ |
} |
if (number_of_mps_macs == 0 && mpoa_device_type == MPS_AND_MPC) { |
printk("\nmpoa: (%s) lane2_assoc_ind: MPS_AND_MPC has zero MACs\n", dev->name); |
continue; /* someone should read the spec */ |
} |
dprintk("this MPS has %d MAC addresses\n", number_of_mps_macs); |
|
/* ok, now we can go and tell our daemon the control address of MPS */ |
send_set_mps_ctrl_addr(tlvs, mpc); |
|
tlvs = copy_macs(mpc, mac_addr, tlvs, number_of_mps_macs, mpoa_device_type); |
if (tlvs == NULL) return; |
} |
if (end_of_tlvs - tlvs != 0) |
printk("mpoa: (%s) lane2_assoc_ind: ignoring %d bytes of trailing TLV carbage\n", |
dev->name, end_of_tlvs - tlvs); |
return; |
} |
|
/* |
* Store at least advertizing router's MAC address |
* plus the possible MAC address(es) to mpc->mps_macs. |
* For a freshly allocated MPOA client mpc->mps_macs == 0. |
*/ |
static uint8_t *copy_macs(struct mpoa_client *mpc, uint8_t *router_mac, |
uint8_t *tlvs, uint8_t mps_macs, uint8_t device_type) |
{ |
int num_macs; |
num_macs = (mps_macs > 1) ? mps_macs : 1; |
|
if (mpc->number_of_mps_macs != num_macs) { /* need to reallocate? */ |
if (mpc->number_of_mps_macs != 0) kfree(mpc->mps_macs); |
mpc->number_of_mps_macs = 0; |
mpc->mps_macs = kmalloc(num_macs*ETH_ALEN, GFP_KERNEL); |
if (mpc->mps_macs == NULL) { |
printk("mpoa: (%s) copy_macs: out of mem\n", mpc->dev->name); |
return NULL; |
} |
} |
memcpy(mpc->mps_macs, router_mac, ETH_ALEN); |
tlvs += 20; if (device_type == MPS_AND_MPC) tlvs += 20; |
if (mps_macs > 0) |
memcpy(mpc->mps_macs, tlvs, mps_macs*ETH_ALEN); |
tlvs += mps_macs*ETH_ALEN; |
mpc->number_of_mps_macs = num_macs; |
|
return tlvs; |
} |
|
static int send_via_shortcut(struct sk_buff *skb, struct mpoa_client *mpc) |
{ |
in_cache_entry *entry; |
struct iphdr *iph; |
char *buff; |
uint32_t ipaddr = 0; |
|
static struct { |
struct llc_snap_hdr hdr; |
uint32_t tag; |
} tagged_llc_snap_hdr = { |
{0xaa, 0xaa, 0x03, {0x00, 0x00, 0x00}, {0x88, 0x4c}}, |
0 |
}; |
|
buff = skb->data + mpc->dev->hard_header_len; |
iph = (struct iphdr *)buff; |
ipaddr = iph->daddr; |
|
ddprintk("mpoa: (%s) send_via_shortcut: ipaddr 0x%x\n", mpc->dev->name, ipaddr); |
|
entry = mpc->in_ops->get(ipaddr, mpc); |
if (entry == NULL) { |
entry = mpc->in_ops->add_entry(ipaddr, mpc); |
if (entry != NULL) mpc->in_ops->put(entry); |
return 1; |
} |
if (mpc->in_ops->cache_hit(entry, mpc) != OPEN){ /* threshold not exceeded or VCC not ready */ |
ddprintk("mpoa: (%s) send_via_shortcut: cache_hit: returns != OPEN\n", mpc->dev->name); |
mpc->in_ops->put(entry); |
return 1; |
} |
|
ddprintk("mpoa: (%s) send_via_shortcut: using shortcut\n", mpc->dev->name); |
/* MPOA spec A.1.4, MPOA client must decrement IP ttl at least by one */ |
if (iph->ttl <= 1) { |
ddprintk("mpoa: (%s) send_via_shortcut: IP ttl = %u, using LANE\n", mpc->dev->name, iph->ttl); |
mpc->in_ops->put(entry); |
return 1; |
} |
iph->ttl--; |
iph->check = 0; |
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); |
|
if (entry->ctrl_info.tag != 0) { |
ddprintk("mpoa: (%s) send_via_shortcut: adding tag 0x%x\n", mpc->dev->name, entry->ctrl_info.tag); |
tagged_llc_snap_hdr.tag = entry->ctrl_info.tag; |
skb_pull(skb, ETH_HLEN); /* get rid of Eth header */ |
skb_push(skb, sizeof(tagged_llc_snap_hdr)); /* add LLC/SNAP header */ |
memcpy(skb->data, &tagged_llc_snap_hdr, sizeof(tagged_llc_snap_hdr)); |
} else { |
skb_pull(skb, ETH_HLEN); /* get rid of Eth header */ |
skb_push(skb, sizeof(struct llc_snap_hdr)); /* add LLC/SNAP header + tag */ |
memcpy(skb->data, &llc_snap_mpoa_data, sizeof(struct llc_snap_hdr)); |
} |
|
atomic_add(skb->truesize, &entry->shortcut->sk->wmem_alloc); |
ATM_SKB(skb)->atm_options = entry->shortcut->atm_options; |
entry->shortcut->send(entry->shortcut, skb); |
entry->packets_fwded++; |
mpc->in_ops->put(entry); |
|
return 0; |
} |
|
/* |
* Probably needs some error checks and locking, not sure... |
*/ |
static int mpc_send_packet(struct sk_buff *skb, struct net_device *dev) |
{ |
int retval; |
struct mpoa_client *mpc; |
struct ethhdr *eth; |
int i = 0; |
|
mpc = find_mpc_by_lec(dev); /* this should NEVER fail */ |
if(mpc == NULL) { |
printk("mpoa: (%s) mpc_send_packet: no MPC found\n", dev->name); |
goto non_ip; |
} |
|
eth = (struct ethhdr *)skb->data; |
if (eth->h_proto != htons(ETH_P_IP)) |
goto non_ip; /* Multi-Protocol Over ATM :-) */ |
|
while (i < mpc->number_of_mps_macs) { |
if (memcmp(eth->h_dest, (mpc->mps_macs + i*ETH_ALEN), ETH_ALEN) == 0) |
if ( send_via_shortcut(skb, mpc) == 0 ) /* try shortcut */ |
return 0; /* success! */ |
i++; |
} |
|
non_ip: |
retval = mpc->old_hard_start_xmit(skb,dev); |
|
return retval; |
} |
|
int atm_mpoa_vcc_attach(struct atm_vcc *vcc, long arg) |
{ |
int bytes_left; |
struct mpoa_client *mpc; |
struct atmmpc_ioc ioc_data; |
in_cache_entry *in_entry; |
uint32_t ipaddr; |
unsigned char *ip; |
|
bytes_left = copy_from_user(&ioc_data, (void *)arg, sizeof(struct atmmpc_ioc)); |
if (bytes_left != 0) { |
printk("mpoa: mpc_vcc_attach: Short read (missed %d bytes) from userland\n", bytes_left); |
return -EFAULT; |
} |
ipaddr = ioc_data.ipaddr; |
if (ioc_data.dev_num < 0 || ioc_data.dev_num >= MAX_LEC_ITF) |
return -EINVAL; |
|
mpc = find_mpc_by_itfnum(ioc_data.dev_num); |
if (mpc == NULL) |
return -EINVAL; |
|
if (ioc_data.type == MPC_SOCKET_INGRESS) { |
in_entry = mpc->in_ops->get(ipaddr, mpc); |
if (in_entry == NULL || in_entry->entry_state < INGRESS_RESOLVED) { |
printk("mpoa: (%s) mpc_vcc_attach: did not find RESOLVED entry from ingress cache\n", |
mpc->dev->name); |
if (in_entry != NULL) mpc->in_ops->put(in_entry); |
return -EINVAL; |
} |
ip = (unsigned char*)&in_entry->ctrl_info.in_dst_ip; |
printk("mpoa: (%s) mpc_vcc_attach: attaching ingress SVC, entry = %u.%u.%u.%u\n", |
mpc->dev->name, ip[0], ip[1], ip[2], ip[3]); |
in_entry->shortcut = vcc; |
mpc->in_ops->put(in_entry); |
} else { |
printk("mpoa: (%s) mpc_vcc_attach: attaching egress SVC\n", mpc->dev->name); |
} |
|
vcc->proto_data = mpc->dev; |
vcc->push = mpc_push; |
|
return 0; |
} |
|
/* |
* |
*/ |
static void mpc_vcc_close(struct atm_vcc *vcc, struct net_device *dev) |
{ |
struct mpoa_client *mpc; |
in_cache_entry *in_entry; |
eg_cache_entry *eg_entry; |
|
mpc = find_mpc_by_lec(dev); |
if (mpc == NULL) { |
printk("mpoa: (%s) mpc_vcc_close: close for unknown MPC\n", dev->name); |
return; |
} |
|
dprintk("mpoa: (%s) mpc_vcc_close:\n", dev->name); |
in_entry = mpc->in_ops->get_by_vcc(vcc, mpc); |
if (in_entry) { |
unsigned char *ip __attribute__ ((unused)) = |
(unsigned char *)&in_entry->ctrl_info.in_dst_ip; |
dprintk("mpoa: (%s) mpc_vcc_close: ingress SVC closed ip = %u.%u.%u.%u\n", |
mpc->dev->name, ip[0], ip[1], ip[2], ip[3]); |
in_entry->shortcut = NULL; |
mpc->in_ops->put(in_entry); |
} |
eg_entry = mpc->eg_ops->get_by_vcc(vcc, mpc); |
if (eg_entry) { |
dprintk("mpoa: (%s) mpc_vcc_close: egress SVC closed\n", mpc->dev->name); |
eg_entry->shortcut = NULL; |
mpc->eg_ops->put(eg_entry); |
} |
|
if (in_entry == NULL && eg_entry == NULL) |
dprintk("mpoa: (%s) mpc_vcc_close: unused vcc closed\n", dev->name); |
|
return; |
} |
|
static void mpc_push(struct atm_vcc *vcc, struct sk_buff *skb) |
{ |
struct net_device *dev = (struct net_device *)vcc->proto_data; |
struct sk_buff *new_skb; |
eg_cache_entry *eg; |
struct mpoa_client *mpc; |
uint32_t tag; |
char *tmp; |
|
ddprintk("mpoa: (%s) mpc_push:\n", dev->name); |
if (skb == NULL) { |
dprintk("mpoa: (%s) mpc_push: null skb, closing VCC\n", dev->name); |
mpc_vcc_close(vcc, dev); |
return; |
} |
|
skb->dev = dev; |
if (memcmp(skb->data, &llc_snap_mpoa_ctrl, sizeof(struct llc_snap_hdr)) == 0) { |
dprintk("mpoa: (%s) mpc_push: control packet arrived\n", dev->name); |
skb_queue_tail(&vcc->sk->receive_queue, skb); /* Pass control packets to daemon */ |
wake_up(&vcc->sleep); |
return; |
} |
|
/* data coming over the shortcut */ |
atm_return(vcc, skb->truesize); |
|
mpc = find_mpc_by_lec(dev); |
if (mpc == NULL) { |
printk("mpoa: (%s) mpc_push: unknown MPC\n", dev->name); |
return; |
} |
|
if (memcmp(skb->data, &llc_snap_mpoa_data_tagged, sizeof(struct llc_snap_hdr)) == 0) { /* MPOA tagged data */ |
ddprintk("mpoa: (%s) mpc_push: tagged data packet arrived\n", dev->name); |
|
} else if (memcmp(skb->data, &llc_snap_mpoa_data, sizeof(struct llc_snap_hdr)) == 0) { /* MPOA data */ |
printk("mpoa: (%s) mpc_push: non-tagged data packet arrived\n", dev->name); |
printk(" mpc_push: non-tagged data unsupported, purging\n"); |
dev_kfree_skb_any(skb); |
return; |
} else { |
printk("mpoa: (%s) mpc_push: garbage arrived, purging\n", dev->name); |
dev_kfree_skb_any(skb); |
return; |
} |
|
tmp = skb->data + sizeof(struct llc_snap_hdr); |
tag = *(uint32_t *)tmp; |
|
eg = mpc->eg_ops->get_by_tag(tag, mpc); |
if (eg == NULL) { |
printk("mpoa: (%s) mpc_push: Didn't find egress cache entry, tag = %u\n", |
dev->name,tag); |
purge_egress_shortcut(vcc, NULL); |
dev_kfree_skb_any(skb); |
return; |
} |
|
/* |
* See if ingress MPC is using shortcut we opened as a return channel. |
* This means we have a bi-directional vcc opened by us. |
*/ |
if (eg->shortcut == NULL) { |
eg->shortcut = vcc; |
printk("mpoa: (%s) mpc_push: egress SVC in use\n", dev->name); |
} |
|
skb_pull(skb, sizeof(struct llc_snap_hdr) + sizeof(tag)); /* get rid of LLC/SNAP header */ |
new_skb = skb_realloc_headroom(skb, eg->ctrl_info.DH_length); /* LLC/SNAP is shorter than MAC header :( */ |
dev_kfree_skb_any(skb); |
if (new_skb == NULL){ |
mpc->eg_ops->put(eg); |
return; |
} |
skb_push(new_skb, eg->ctrl_info.DH_length); /* add MAC header */ |
memcpy(new_skb->data, eg->ctrl_info.DLL_header, eg->ctrl_info.DH_length); |
new_skb->protocol = eth_type_trans(new_skb, dev); |
new_skb->nh.raw = new_skb->data; |
|
eg->latest_ip_addr = new_skb->nh.iph->saddr; |
eg->packets_rcvd++; |
mpc->eg_ops->put(eg); |
|
memset(ATM_SKB(new_skb), 0, sizeof(struct atm_skb_data)); |
netif_rx(new_skb); |
|
return; |
} |
|
static struct atmdev_ops mpc_ops = { /* only send is required */ |
.close = mpoad_close, |
.send = msg_from_mpoad |
}; |
|
static struct atm_dev mpc_dev = { |
.ops = &mpc_ops, |
.type = "mpc", |
.number = 42, |
.lock = SPIN_LOCK_UNLOCKED |
/* rest of the members will be 0 */ |
}; |
|
int atm_mpoa_mpoad_attach (struct atm_vcc *vcc, int arg) |
{ |
struct mpoa_client *mpc; |
struct lec_priv *priv; |
|
if (mpcs == NULL) { |
init_timer(&mpc_timer); |
mpc_timer_refresh(); |
|
/* This lets us now how our LECs are doing */ |
register_netdevice_notifier(&mpoa_notifier); |
} |
|
mpc = find_mpc_by_itfnum(arg); |
if (mpc == NULL) { |
dprintk("mpoa: mpoad_attach: allocating new mpc for itf %d\n", arg); |
mpc = alloc_mpc(); |
if (mpc == NULL) |
return -ENOMEM; |
mpc->dev_num = arg; |
mpc->dev = find_lec_by_itfnum(arg); /* NULL if there was no lec */ |
} |
if (mpc->mpoad_vcc) { |
printk("mpoa: mpoad_attach: mpoad is already present for itf %d\n", arg); |
return -EADDRINUSE; |
} |
|
if (mpc->dev) { /* check if the lec is LANE2 capable */ |
priv = (struct lec_priv *)mpc->dev->priv; |
if (priv->lane_version < 2) { |
dev_put(mpc->dev); |
mpc->dev = NULL; |
} else |
priv->lane2_ops->associate_indicator = lane2_assoc_ind; |
} |
|
mpc->mpoad_vcc = vcc; |
vcc->dev = &mpc_dev; |
vcc_insert_socket(vcc->sk); |
set_bit(ATM_VF_META,&vcc->flags); |
set_bit(ATM_VF_READY,&vcc->flags); |
|
if (mpc->dev) { |
char empty[ATM_ESA_LEN]; |
memset(empty, 0, ATM_ESA_LEN); |
|
start_mpc(mpc, mpc->dev); |
/* set address if mpcd e.g. gets killed and restarted. |
* If we do not do it now we have to wait for the next LE_ARP |
*/ |
if ( memcmp(mpc->mps_ctrl_addr, empty, ATM_ESA_LEN) != 0 ) |
send_set_mps_ctrl_addr(mpc->mps_ctrl_addr, mpc); |
} |
|
MOD_INC_USE_COUNT; |
return arg; |
} |
|
static void send_set_mps_ctrl_addr(char *addr, struct mpoa_client *mpc) |
{ |
struct k_message mesg; |
|
memcpy (mpc->mps_ctrl_addr, addr, ATM_ESA_LEN); |
|
mesg.type = SET_MPS_CTRL_ADDR; |
memcpy(mesg.MPS_ctrl, addr, ATM_ESA_LEN); |
msg_to_mpoad(&mesg, mpc); |
|
return; |
} |
|
static void mpoad_close(struct atm_vcc *vcc) |
{ |
struct mpoa_client *mpc; |
struct sk_buff *skb; |
|
mpc = find_mpc_by_vcc(vcc); |
if (mpc == NULL) { |
printk("mpoa: mpoad_close: did not find MPC\n"); |
return; |
} |
if (!mpc->mpoad_vcc) { |
printk("mpoa: mpoad_close: close for non-present mpoad\n"); |
return; |
} |
|
mpc->mpoad_vcc = NULL; |
if (mpc->dev) { |
struct lec_priv *priv = (struct lec_priv *)mpc->dev->priv; |
priv->lane2_ops->associate_indicator = NULL; |
stop_mpc(mpc); |
dev_put(mpc->dev); |
} |
|
mpc->in_ops->destroy_cache(mpc); |
mpc->eg_ops->destroy_cache(mpc); |
|
while ( (skb = skb_dequeue(&vcc->sk->receive_queue)) ){ |
atm_return(vcc, skb->truesize); |
kfree_skb(skb); |
} |
|
printk("mpoa: (%s) going down\n", |
(mpc->dev) ? mpc->dev->name : "<unknown>"); |
MOD_DEC_USE_COUNT; |
|
return; |
} |
|
/* |
* |
*/ |
static int msg_from_mpoad(struct atm_vcc *vcc, struct sk_buff *skb) |
{ |
|
struct mpoa_client *mpc = find_mpc_by_vcc(vcc); |
struct k_message *mesg = (struct k_message*)skb->data; |
atomic_sub(skb->truesize, &vcc->sk->wmem_alloc); |
|
if (mpc == NULL) { |
printk("mpoa: msg_from_mpoad: no mpc found\n"); |
return 0; |
} |
dprintk("mpoa: (%s) msg_from_mpoad:", (mpc->dev) ? mpc->dev->name : "<unknown>"); |
switch(mesg->type) { |
case MPOA_RES_REPLY_RCVD: |
dprintk(" mpoa_res_reply_rcvd\n"); |
MPOA_res_reply_rcvd(mesg, mpc); |
break; |
case MPOA_TRIGGER_RCVD: |
dprintk(" mpoa_trigger_rcvd\n"); |
MPOA_trigger_rcvd(mesg, mpc); |
break; |
case INGRESS_PURGE_RCVD: |
dprintk(" nhrp_purge_rcvd\n"); |
ingress_purge_rcvd(mesg, mpc); |
break; |
case EGRESS_PURGE_RCVD: |
dprintk(" egress_purge_reply_rcvd\n"); |
egress_purge_rcvd(mesg, mpc); |
break; |
case MPS_DEATH: |
dprintk(" mps_death\n"); |
mps_death(mesg, mpc); |
break; |
case CACHE_IMPOS_RCVD: |
dprintk(" cache_impos_rcvd\n"); |
MPOA_cache_impos_rcvd(mesg, mpc); |
break; |
case SET_MPC_CTRL_ADDR: |
dprintk(" set_mpc_ctrl_addr\n"); |
set_mpc_ctrl_addr_rcvd(mesg, mpc); |
break; |
case SET_MPS_MAC_ADDR: |
dprintk(" set_mps_mac_addr\n"); |
set_mps_mac_addr_rcvd(mesg, mpc); |
break; |
case CLEAN_UP_AND_EXIT: |
dprintk(" clean_up_and_exit\n"); |
clean_up(mesg, mpc, DIE); |
break; |
case RELOAD: |
dprintk(" reload\n"); |
clean_up(mesg, mpc, RELOAD); |
break; |
case SET_MPC_PARAMS: |
dprintk(" set_mpc_params\n"); |
mpc->parameters = mesg->content.params; |
break; |
default: |
dprintk(" unknown message %d\n", mesg->type); |
break; |
} |
kfree_skb(skb); |
|
return 0; |
} |
|
/* Remember that this function may not do things that sleep */ |
int msg_to_mpoad(struct k_message *mesg, struct mpoa_client *mpc) |
{ |
struct sk_buff *skb; |
|
if (mpc == NULL || !mpc->mpoad_vcc) { |
printk("mpoa: msg_to_mpoad: mesg %d to a non-existent mpoad\n", mesg->type); |
return -ENXIO; |
} |
|
skb = alloc_skb(sizeof(struct k_message), GFP_ATOMIC); |
if (skb == NULL) |
return -ENOMEM; |
skb_put(skb, sizeof(struct k_message)); |
memcpy(skb->data, mesg, sizeof(struct k_message)); |
atm_force_charge(mpc->mpoad_vcc, skb->truesize); |
skb_queue_tail(&mpc->mpoad_vcc->sk->receive_queue, skb); |
wake_up(&mpc->mpoad_vcc->sleep); |
|
return 0; |
} |
|
static int mpoa_event_listener(struct notifier_block *mpoa_notifier, unsigned long event, void *dev_ptr) |
{ |
struct net_device *dev; |
struct mpoa_client *mpc; |
struct lec_priv *priv; |
|
dev = (struct net_device *)dev_ptr; |
if (dev->name == NULL || strncmp(dev->name, "lec", 3)) |
return NOTIFY_DONE; /* we are only interested in lec:s */ |
|
switch (event) { |
case NETDEV_REGISTER: /* a new lec device was allocated */ |
priv = (struct lec_priv *)dev->priv; |
if (priv->lane_version < 2) |
break; |
priv->lane2_ops->associate_indicator = lane2_assoc_ind; |
mpc = find_mpc_by_itfnum(priv->itfnum); |
if (mpc == NULL) { |
dprintk("mpoa: mpoa_event_listener: allocating new mpc for %s\n", |
dev->name); |
mpc = alloc_mpc(); |
if (mpc == NULL) { |
printk("mpoa: mpoa_event_listener: no new mpc"); |
break; |
} |
} |
mpc->dev_num = priv->itfnum; |
mpc->dev = dev; |
dev_hold(dev); |
dprintk("mpoa: (%s) was initialized\n", dev->name); |
break; |
case NETDEV_UNREGISTER: |
/* the lec device was deallocated */ |
mpc = find_mpc_by_lec(dev); |
if (mpc == NULL) |
break; |
dprintk("mpoa: device (%s) was deallocated\n", dev->name); |
stop_mpc(mpc); |
dev_put(mpc->dev); |
mpc->dev = NULL; |
break; |
case NETDEV_UP: |
/* the dev was ifconfig'ed up */ |
mpc = find_mpc_by_lec(dev); |
if (mpc == NULL) |
break; |
if (mpc->mpoad_vcc != NULL) { |
start_mpc(mpc, dev); |
} |
break; |
case NETDEV_DOWN: |
/* the dev was ifconfig'ed down */ |
/* this means that the flow of packets from the |
* upper layer stops |
*/ |
mpc = find_mpc_by_lec(dev); |
if (mpc == NULL) |
break; |
if (mpc->mpoad_vcc != NULL) { |
stop_mpc(mpc); |
} |
break; |
case NETDEV_REBOOT: |
case NETDEV_CHANGE: |
case NETDEV_CHANGEMTU: |
case NETDEV_CHANGEADDR: |
case NETDEV_GOING_DOWN: |
break; |
default: |
break; |
} |
|
return NOTIFY_DONE; |
} |
|
/* |
* Functions which are called after a message is received from mpcd. |
* Msg is reused on purpose. |
*/ |
|
|
static void MPOA_trigger_rcvd(struct k_message *msg, struct mpoa_client *mpc) |
{ |
uint32_t dst_ip = msg->content.in_info.in_dst_ip; |
in_cache_entry *entry; |
|
entry = mpc->in_ops->get(dst_ip, mpc); |
if(entry == NULL){ |
entry = mpc->in_ops->add_entry(dst_ip, mpc); |
entry->entry_state = INGRESS_RESOLVING; |
msg->type = SND_MPOA_RES_RQST; |
msg->content.in_info = entry->ctrl_info; |
msg_to_mpoad(msg, mpc); |
do_gettimeofday(&(entry->reply_wait)); |
mpc->in_ops->put(entry); |
return; |
} |
|
if(entry->entry_state == INGRESS_INVALID){ |
entry->entry_state = INGRESS_RESOLVING; |
msg->type = SND_MPOA_RES_RQST; |
msg->content.in_info = entry->ctrl_info; |
msg_to_mpoad(msg, mpc); |
do_gettimeofday(&(entry->reply_wait)); |
mpc->in_ops->put(entry); |
return; |
} |
|
printk("mpoa: (%s) MPOA_trigger_rcvd: entry already in resolving state\n", |
(mpc->dev) ? mpc->dev->name : "<unknown>"); |
mpc->in_ops->put(entry); |
return; |
} |
|
/* |
* Things get complicated because we have to check if there's an egress |
* shortcut with suitable traffic parameters we could use. |
*/ |
static void check_qos_and_open_shortcut(struct k_message *msg, struct mpoa_client *client, in_cache_entry *entry) |
{ |
uint32_t dst_ip = msg->content.in_info.in_dst_ip; |
unsigned char *ip __attribute__ ((unused)) = (unsigned char *)&dst_ip; |
struct atm_mpoa_qos *qos = atm_mpoa_search_qos(dst_ip); |
eg_cache_entry *eg_entry = client->eg_ops->get_by_src_ip(dst_ip, client); |
|
if(eg_entry && eg_entry->shortcut){ |
if(eg_entry->shortcut->qos.txtp.traffic_class & |
msg->qos.txtp.traffic_class & |
(qos ? qos->qos.txtp.traffic_class : ATM_UBR | ATM_CBR)){ |
if(eg_entry->shortcut->qos.txtp.traffic_class == ATM_UBR) |
entry->shortcut = eg_entry->shortcut; |
else if(eg_entry->shortcut->qos.txtp.max_pcr > 0) |
entry->shortcut = eg_entry->shortcut; |
} |
if(entry->shortcut){ |
dprintk("mpoa: (%s) using egress SVC to reach %u.%u.%u.%u\n",client->dev->name, NIPQUAD(ip)); |
client->eg_ops->put(eg_entry); |
return; |
} |
} |
if (eg_entry != NULL) |
client->eg_ops->put(eg_entry); |
|
/* No luck in the egress cache we must open an ingress SVC */ |
msg->type = OPEN_INGRESS_SVC; |
if (qos && (qos->qos.txtp.traffic_class == msg->qos.txtp.traffic_class)) |
{ |
msg->qos = qos->qos; |
printk("mpoa: (%s) trying to get a CBR shortcut\n",client->dev->name); |
} |
else memset(&msg->qos,0,sizeof(struct atm_qos)); |
msg_to_mpoad(msg, client); |
return; |
} |
|
static void MPOA_res_reply_rcvd(struct k_message *msg, struct mpoa_client *mpc) |
{ |
unsigned char *ip; |
|
uint32_t dst_ip = msg->content.in_info.in_dst_ip; |
in_cache_entry *entry = mpc->in_ops->get(dst_ip, mpc); |
ip = (unsigned char *)&dst_ip; |
dprintk("mpoa: (%s) MPOA_res_reply_rcvd: ip %u.%u.%u.%u\n", mpc->dev->name, NIPQUAD(ip)); |
ddprintk("mpoa: (%s) MPOA_res_reply_rcvd() entry = %p", mpc->dev->name, entry); |
if(entry == NULL){ |
printk("\nmpoa: (%s) ARGH, received res. reply for an entry that doesn't exist.\n", mpc->dev->name); |
return; |
} |
ddprintk(" entry_state = %d ", entry->entry_state); |
|
if (entry->entry_state == INGRESS_RESOLVED) { |
printk("\nmpoa: (%s) MPOA_res_reply_rcvd for RESOLVED entry!\n", mpc->dev->name); |
mpc->in_ops->put(entry); |
return; |
} |
|
entry->ctrl_info = msg->content.in_info; |
do_gettimeofday(&(entry->tv)); |
do_gettimeofday(&(entry->reply_wait)); /* Used in refreshing func from now on */ |
entry->refresh_time = 0; |
ddprintk("entry->shortcut = %p\n", entry->shortcut); |
|
if(entry->entry_state == INGRESS_RESOLVING && entry->shortcut != NULL){ |
entry->entry_state = INGRESS_RESOLVED; |
mpc->in_ops->put(entry); |
return; /* Shortcut already open... */ |
} |
|
if (entry->shortcut != NULL) { |
printk("mpoa: (%s) MPOA_res_reply_rcvd: entry->shortcut != NULL, impossible!\n", |
mpc->dev->name); |
mpc->in_ops->put(entry); |
return; |
} |
|
check_qos_and_open_shortcut(msg, mpc, entry); |
entry->entry_state = INGRESS_RESOLVED; |
mpc->in_ops->put(entry); |
|
return; |
|
} |
|
static void ingress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc) |
{ |
uint32_t dst_ip = msg->content.in_info.in_dst_ip; |
uint32_t mask = msg->ip_mask; |
unsigned char *ip = (unsigned char *)&dst_ip; |
in_cache_entry *entry = mpc->in_ops->get_with_mask(dst_ip, mpc, mask); |
|
if(entry == NULL){ |
printk("mpoa: (%s) ingress_purge_rcvd: purge for a non-existing entry, ", mpc->dev->name); |
printk("ip = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); |
return; |
} |
|
do { |
dprintk("mpoa: (%s) ingress_purge_rcvd: removing an ingress entry, ip = %u.%u.%u.%u\n" , |
mpc->dev->name, ip[0], ip[1], ip[2], ip[3]); |
write_lock_bh(&mpc->ingress_lock); |
mpc->in_ops->remove_entry(entry, mpc); |
write_unlock_bh(&mpc->ingress_lock); |
mpc->in_ops->put(entry); |
entry = mpc->in_ops->get_with_mask(dst_ip, mpc, mask); |
} while (entry != NULL); |
|
return; |
} |
|
static void egress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc) |
{ |
uint32_t cache_id = msg->content.eg_info.cache_id; |
eg_cache_entry *entry = mpc->eg_ops->get_by_cache_id(cache_id, mpc); |
|
if (entry == NULL) { |
dprintk("mpoa: (%s) egress_purge_rcvd: purge for a non-existing entry\n", mpc->dev->name); |
return; |
} |
|
write_lock_irq(&mpc->egress_lock); |
mpc->eg_ops->remove_entry(entry, mpc); |
write_unlock_irq(&mpc->egress_lock); |
|
mpc->eg_ops->put(entry); |
|
return; |
} |
|
static void purge_egress_shortcut(struct atm_vcc *vcc, eg_cache_entry *entry) |
{ |
struct k_message *purge_msg; |
struct sk_buff *skb; |
|
dprintk("mpoa: purge_egress_shortcut: entering\n"); |
if (vcc == NULL) { |
printk("mpoa: purge_egress_shortcut: vcc == NULL\n"); |
return; |
} |
|
skb = alloc_skb(sizeof(struct k_message), GFP_ATOMIC); |
if (skb == NULL) { |
printk("mpoa: purge_egress_shortcut: out of memory\n"); |
return; |
} |
|
skb_put(skb, sizeof(struct k_message)); |
memset(skb->data, 0, sizeof(struct k_message)); |
purge_msg = (struct k_message *)skb->data; |
purge_msg->type = DATA_PLANE_PURGE; |
if (entry != NULL) |
purge_msg->content.eg_info = entry->ctrl_info; |
|
atm_force_charge(vcc, skb->truesize); |
skb_queue_tail(&vcc->sk->receive_queue, skb); |
wake_up(&vcc->sleep); |
dprintk("mpoa: purge_egress_shortcut: exiting:\n"); |
|
return; |
} |
|
/* |
* Our MPS died. Tell our daemon to send NHRP data plane purge to each |
* of the egress shortcuts we have. |
*/ |
static void mps_death( struct k_message * msg, struct mpoa_client * mpc ) |
{ |
eg_cache_entry *entry; |
|
dprintk("mpoa: (%s) mps_death:\n", mpc->dev->name); |
|
if(memcmp(msg->MPS_ctrl, mpc->mps_ctrl_addr, ATM_ESA_LEN)){ |
printk("mpoa: (%s) mps_death: wrong MPS\n", mpc->dev->name); |
return; |
} |
|
/* FIXME: This knows too much of the cache structure */ |
read_lock_irq(&mpc->egress_lock); |
entry = mpc->eg_cache; |
while (entry != NULL) { |
purge_egress_shortcut(entry->shortcut, entry); |
entry = entry->next; |
} |
read_unlock_irq(&mpc->egress_lock); |
|
mpc->in_ops->destroy_cache(mpc); |
mpc->eg_ops->destroy_cache(mpc); |
|
return; |
} |
|
static void MPOA_cache_impos_rcvd( struct k_message * msg, struct mpoa_client * mpc) |
{ |
uint16_t holding_time; |
eg_cache_entry *entry = mpc->eg_ops->get_by_cache_id(msg->content.eg_info.cache_id, mpc); |
|
holding_time = msg->content.eg_info.holding_time; |
dprintk("mpoa: (%s) MPOA_cache_impos_rcvd: entry = %p, holding_time = %u\n", |
mpc->dev->name, entry, holding_time); |
if(entry == NULL && holding_time) { |
entry = mpc->eg_ops->add_entry(msg, mpc); |
mpc->eg_ops->put(entry); |
return; |
} |
if(holding_time){ |
mpc->eg_ops->update(entry, holding_time); |
return; |
} |
|
write_lock_irq(&mpc->egress_lock); |
mpc->eg_ops->remove_entry(entry, mpc); |
write_unlock_irq(&mpc->egress_lock); |
|
mpc->eg_ops->put(entry); |
|
return; |
} |
|
static void set_mpc_ctrl_addr_rcvd(struct k_message *mesg, struct mpoa_client *mpc) |
{ |
struct lec_priv *priv; |
int i, retval ; |
|
uint8_t tlv[4 + 1 + 1 + 1 + ATM_ESA_LEN]; |
|
tlv[0] = 00; tlv[1] = 0xa0; tlv[2] = 0x3e; tlv[3] = 0x2a; /* type */ |
tlv[4] = 1 + 1 + ATM_ESA_LEN; /* length */ |
tlv[5] = 0x02; /* MPOA client */ |
tlv[6] = 0x00; /* number of MPS MAC addresses */ |
|
memcpy(&tlv[7], mesg->MPS_ctrl, ATM_ESA_LEN); /* MPC ctrl ATM addr */ |
memcpy(mpc->our_ctrl_addr, mesg->MPS_ctrl, ATM_ESA_LEN); |
|
dprintk("mpoa: (%s) setting MPC ctrl ATM address to ", |
(mpc->dev) ? mpc->dev->name : "<unknown>"); |
for (i = 7; i < sizeof(tlv); i++) |
dprintk("%02x ", tlv[i]); |
dprintk("\n"); |
|
if (mpc->dev) { |
priv = (struct lec_priv *)mpc->dev->priv; |
retval = priv->lane2_ops->associate_req(mpc->dev, mpc->dev->dev_addr, tlv, sizeof(tlv)); |
if (retval == 0) |
printk("mpoa: (%s) MPOA device type TLV association failed\n", mpc->dev->name); |
retval = priv->lane2_ops->resolve(mpc->dev, NULL, 1, NULL, NULL); |
if (retval < 0) |
printk("mpoa: (%s) targetless LE_ARP request failed\n", mpc->dev->name); |
} |
|
return; |
} |
|
static void set_mps_mac_addr_rcvd(struct k_message *msg, struct mpoa_client *client) |
{ |
|
if(client->number_of_mps_macs) |
kfree(client->mps_macs); |
client->number_of_mps_macs = 0; |
client->mps_macs = kmalloc(ETH_ALEN,GFP_KERNEL); |
if (client->mps_macs == NULL) { |
printk("mpoa: set_mps_mac_addr_rcvd: out of memory\n"); |
return; |
} |
client->number_of_mps_macs = 1; |
memcpy(client->mps_macs, msg->MPS_ctrl, ETH_ALEN); |
|
return; |
} |
|
/* |
* purge egress cache and tell daemon to 'action' (DIE, RELOAD) |
*/ |
static void clean_up(struct k_message *msg, struct mpoa_client *mpc, int action) |
{ |
|
eg_cache_entry *entry; |
msg->type = SND_EGRESS_PURGE; |
|
|
/* FIXME: This knows too much of the cache structure */ |
read_lock_irq(&mpc->egress_lock); |
entry = mpc->eg_cache; |
while (entry != NULL){ |
msg->content.eg_info = entry->ctrl_info; |
dprintk("mpoa: cache_id %u\n", entry->ctrl_info.cache_id); |
msg_to_mpoad(msg, mpc); |
entry = entry->next; |
} |
read_unlock_irq(&mpc->egress_lock); |
|
msg->type = action; |
msg_to_mpoad(msg, mpc); |
return; |
} |
|
static void mpc_timer_refresh() |
{ |
mpc_timer.expires = jiffies + (MPC_P2 * HZ); |
mpc_timer.data = mpc_timer.expires; |
mpc_timer.function = mpc_cache_check; |
add_timer(&mpc_timer); |
|
return; |
} |
|
static void mpc_cache_check( unsigned long checking_time ) |
{ |
struct mpoa_client *mpc = mpcs; |
static unsigned long previous_resolving_check_time = 0; |
static unsigned long previous_refresh_time = 0; |
|
while( mpc != NULL ){ |
mpc->in_ops->clear_count(mpc); |
mpc->eg_ops->clear_expired(mpc); |
if(checking_time - previous_resolving_check_time > mpc->parameters.mpc_p4 * HZ ){ |
mpc->in_ops->check_resolving(mpc); |
previous_resolving_check_time = checking_time; |
} |
if(checking_time - previous_refresh_time > mpc->parameters.mpc_p5 * HZ ){ |
mpc->in_ops->refresh(mpc); |
previous_refresh_time = checking_time; |
} |
mpc = mpc->next; |
} |
mpc_timer_refresh(); |
|
return; |
} |
|
static struct atm_mpoa_ops __atm_mpoa_ops = { |
.mpoad_attach = atm_mpoa_mpoad_attach, |
.vcc_attach = atm_mpoa_vcc_attach, |
.owner = THIS_MODULE |
}; |
|
static __init int atm_mpoa_init(void) |
{ |
atm_mpoa_ops_set(&__atm_mpoa_ops); |
|
#ifdef CONFIG_PROC_FS |
if (mpc_proc_init() != 0) |
printk(KERN_INFO "mpoa: failed to initialize /proc/mpoa\n"); |
else |
printk(KERN_INFO "mpoa: /proc/mpoa initialized\n"); |
#endif |
|
printk("mpc.c: " __DATE__ " " __TIME__ " initialized\n"); |
|
return 0; |
} |
|
void __exit atm_mpoa_cleanup(void) |
{ |
struct mpoa_client *mpc, *tmp; |
struct atm_mpoa_qos *qos, *nextqos; |
struct lec_priv *priv; |
|
if (MOD_IN_USE) { |
printk("mpc.c: module in use\n"); |
return; |
} |
#ifdef CONFIG_PROC_FS |
mpc_proc_clean(); |
#endif |
|
del_timer(&mpc_timer); |
unregister_netdevice_notifier(&mpoa_notifier); |
atm_mpoa_ops_set(NULL); |
|
mpc = mpcs; |
mpcs = NULL; |
while (mpc != NULL) { |
tmp = mpc->next; |
if (mpc->dev != NULL) { |
stop_mpc(mpc); |
priv = (struct lec_priv *)mpc->dev->priv; |
if (priv->lane2_ops != NULL) |
priv->lane2_ops->associate_indicator = NULL; |
} |
ddprintk("mpoa: cleanup_module: about to clear caches\n"); |
mpc->in_ops->destroy_cache(mpc); |
mpc->eg_ops->destroy_cache(mpc); |
ddprintk("mpoa: cleanup_module: caches cleared\n"); |
kfree(mpc->mps_macs); |
memset(mpc, 0, sizeof(struct mpoa_client)); |
ddprintk("mpoa: cleanup_module: about to kfree %p\n", mpc); |
kfree(mpc); |
ddprintk("mpoa: cleanup_module: next mpc is at %p\n", tmp); |
mpc = tmp; |
} |
|
qos = qos_head; |
qos_head = NULL; |
while (qos != NULL) { |
nextqos = qos->next; |
dprintk("mpoa: cleanup_module: freeing qos entry %p\n", qos); |
kfree(qos); |
qos = nextqos; |
} |
|
return; |
} |
|
module_init(atm_mpoa_init); |
module_exit(atm_mpoa_cleanup); |
|
MODULE_LICENSE("GPL"); |
/mpoa_proc.c
0,0 → 1,347
#include <linux/config.h> |
|
#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 <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 ssize_t proc_mpc_read(struct file *file, char *buff, |
size_t count, loff_t *pos); |
|
static ssize_t proc_mpc_write(struct file *file, const char *buff, |
size_t nbytes, loff_t *ppos); |
|
static int parse_qos(const char *buff, int len); |
|
/* |
* Define allowed FILE OPERATIONS |
*/ |
static struct file_operations mpc_file_operations = { |
read: proc_mpc_read, |
write: proc_mpc_write, |
}; |
|
static int print_header(char *buff,struct mpoa_client *mpc){ |
if(mpc != NULL){ |
return sprintf(buff,"\nInterface %d:\n\n",mpc->dev_num); |
|
} |
return 0; |
} |
|
/* |
* 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 ""; |
} |
} |
|
/* |
* READING function - called when the /proc/atm/mpoa file is read from. |
*/ |
static ssize_t proc_mpc_read(struct file *file, char *buff, |
size_t count, loff_t *pos){ |
unsigned long page = 0; |
unsigned char *temp; |
int length = 0; |
int i = 0; |
struct mpoa_client *mpc = mpcs; |
in_cache_entry *in_entry; |
eg_cache_entry *eg_entry; |
struct timeval now; |
unsigned char ip_string[16]; |
if(count == 0) |
return 0; |
page = get_free_page(GFP_KERNEL); |
if(!page) |
return -ENOMEM; |
atm_mpoa_disp_qos((char *)page, &length); |
while(mpc != NULL){ |
length += print_header((char *)page + length, mpc); |
length += sprintf((char *)page + length,"Ingress Entries:\nIP address State Holding time Packets fwded VPI VCI\n"); |
in_entry = mpc->in_cache; |
do_gettimeofday(&now); |
while(in_entry != NULL){ |
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]); |
length += sprintf((char *)page + length,"%-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) |
length += sprintf((char *)page + length," %-3d %-3d",in_entry->shortcut->vpi,in_entry->shortcut->vci); |
length += sprintf((char *)page + length,"\n"); |
in_entry = in_entry->next; |
} |
length += sprintf((char *)page + length,"\n"); |
eg_entry = mpc->eg_cache; |
length += sprintf((char *)page + length,"Egress Entries:\nIngress MPC ATM addr\nCache-id State Holding time Packets recvd Latest IP addr VPI VCI\n"); |
while(eg_entry != NULL){ |
for(i=0;i<ATM_ESA_LEN;i++){ |
length += sprintf((char *)page + length,"%02x",eg_entry->ctrl_info.in_MPC_data_ATM_addr[i]);} |
length += sprintf((char *)page + length,"\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]); |
length += sprintf((char *)page + length, "%-16s", ip_string); |
|
if(eg_entry->shortcut) |
length += sprintf((char *)page + length," %-3d %-3d",eg_entry->shortcut->vpi,eg_entry->shortcut->vci); |
length += sprintf((char *)page + length,"\n"); |
eg_entry = eg_entry->next; |
} |
length += sprintf((char *)page + length,"\n"); |
mpc = mpc->next; |
} |
|
if (*pos >= length) length = 0; |
else { |
if ((count + *pos) > length) count = length - *pos; |
if (copy_to_user(buff, (char *)page , count)) { |
free_page(page); |
return -EFAULT; |
} |
*pos += count; |
} |
|
free_page(page); |
return length; |
} |
|
static ssize_t proc_mpc_write(struct file *file, const char *buff, |
size_t nbytes, loff_t *ppos) |
{ |
int incoming, error, retval; |
char *page, c; |
const char *tmp; |
|
if (nbytes == 0) return 0; |
if (nbytes >= PAGE_SIZE) nbytes = PAGE_SIZE-1; |
|
error = verify_area(VERIFY_READ, buff, nbytes); |
if (error) return error; |
|
page = (char *)__get_free_page(GFP_KERNEL); |
if (page == NULL) return -ENOMEM; |
|
incoming = 0; |
tmp = buff; |
while(incoming < nbytes){ |
if (get_user(c, tmp++)) return -EFAULT; |
incoming++; |
if (c == '\0' || c == '\n') |
break; |
} |
|
retval = copy_from_user(page, buff, incoming); |
if (retval != 0) { |
printk("mpoa: proc_mpc_write: copy_from_user() failed\n"); |
return -EFAULT; |
} |
|
*ppos += incoming; |
|
page[incoming] = '\0'; |
retval = parse_qos(page, incoming); |
if (retval == 0) |
printk("mpoa: proc_mpc_write: could not parse '%s'\n", page); |
|
free_page((unsigned long)page); |
|
return nbytes; |
} |
|
static int parse_qos(const char *buff, int len) |
{ |
/* possible lines look like this |
* add 130.230.54.142 tx=max_pcr,max_sdu rx=max_pcr,max_sdu |
*/ |
|
int pos, i; |
uint32_t ipaddr; |
unsigned char ip[4]; |
char cmd[4], temp[256]; |
const char *tmp, *prev; |
struct atm_qos qos; |
int value[5]; |
|
memset(&qos, 0, sizeof(struct atm_qos)); |
strncpy(cmd, buff, 3); |
if( strncmp(cmd,"add", 3) && strncmp(cmd,"del", 3)) |
return 0; /* not add or del */ |
|
pos = 4; |
/* next parse ip */ |
prev = buff + pos; |
for (i = 0; i < 3; i++) { |
tmp = strchr(prev, '.'); |
if (tmp == NULL) return 0; |
memset(temp, '\0', 256); |
memcpy(temp, prev, tmp-prev); |
ip[i] = (char)simple_strtoul(temp, NULL, 0); |
tmp ++; |
prev = tmp; |
} |
tmp = strchr(prev, ' '); |
if (tmp == NULL) return 0; |
memset(temp, '\0', 256); |
memcpy(temp, prev, tmp-prev); |
ip[i] = (char)simple_strtoul(temp, NULL, 0); |
ipaddr = *(uint32_t *)ip; |
|
if(!strncmp(cmd, "del", 3)) |
return atm_mpoa_delete_qos(atm_mpoa_search_qos(ipaddr)); |
|
/* next transmit values */ |
tmp = strstr(buff, "tx="); |
if(tmp == NULL) return 0; |
tmp += 3; |
prev = tmp; |
for( i = 0; i < 1; i++){ |
tmp = strchr(prev, ','); |
if (tmp == NULL) return 0; |
memset(temp, '\0', 256); |
memcpy(temp, prev, tmp-prev); |
value[i] = (int)simple_strtoul(temp, NULL, 0); |
tmp ++; |
prev = tmp; |
} |
tmp = strchr(prev, ' '); |
if (tmp == NULL) return 0; |
memset(temp, '\0', 256); |
memcpy(temp, prev, tmp-prev); |
value[i] = (int)simple_strtoul(temp, NULL, 0); |
qos.txtp.traffic_class = ATM_CBR; |
qos.txtp.max_pcr = value[0]; |
qos.txtp.max_sdu = value[1]; |
|
/* next receive values */ |
tmp = strstr(buff, "rx="); |
if(tmp == NULL) return 0; |
if (strstr(buff, "rx=tx")) { /* rx == tx */ |
qos.rxtp.traffic_class = qos.txtp.traffic_class; |
qos.rxtp.max_pcr = qos.txtp.max_pcr; |
qos.rxtp.max_cdv = qos.txtp.max_cdv; |
qos.rxtp.max_sdu = qos.txtp.max_sdu; |
} else { |
tmp += 3; |
prev = tmp; |
for( i = 0; i < 1; i++){ |
tmp = strchr(prev, ','); |
if (tmp == NULL) return 0; |
memset(temp, '\0', 256); |
memcpy(temp, prev, tmp-prev); |
value[i] = (int)simple_strtoul(temp, NULL, 0); |
tmp ++; |
prev = tmp; |
} |
tmp = strchr(prev, '\0'); |
if (tmp == NULL) return 0; |
memset(temp, '\0', 256); |
memcpy(temp, prev, tmp-prev); |
value[i] = (int)simple_strtoul(temp, NULL, 0); |
qos.rxtp.traffic_class = ATM_CBR; |
qos.rxtp.max_pcr = value[0]; |
qos.rxtp.max_sdu = value[1]; |
} |
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 */ |
|
|
|
|
|
|
/signaling.h
0,0 → 1,33
/* 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> |
|
|
#define WAITING 1 /* for reply: 0: no error, < 0: error, ... */ |
|
|
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 |
/pppoatm.c
0,0 → 1,365
/* 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/config.h> |
#include <linux/init.h> |
#include <linux/skbuff.h> |
#include <linux/atm.h> |
#include <linux/atmdev.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... */ |
MOD_DEC_USE_COUNT; |
} |
|
/* 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, &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 *) arg) |
? -EFAULT : 0; |
case PPPIOCSFLAGS: |
return get_user(chan_to_pvcc(chan)->flags, (int *) 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, unsigned long 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, (void *) arg, sizeof be)) |
return -EFAULT; |
if (be.encaps != PPPOATM_ENCAPS_AUTODETECT && |
be.encaps != PPPOATM_ENCAPS_VC && be.encaps != PPPOATM_ENCAPS_LLC) |
return -EINVAL; |
MOD_INC_USE_COUNT; |
pvcc = kmalloc(sizeof(*pvcc), GFP_KERNEL); |
if (pvcc == NULL) { |
MOD_DEC_USE_COUNT; |
return -ENOMEM; |
} |
memset(pvcc, 0, sizeof(*pvcc)); |
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; |
return 0; |
} |
|
/* |
* This handles ioctls actually performed on our vcc - we must return |
* -ENOIOCTLCMD for any unrecognized ioctl |
*/ |
static int pppoatm_ioctl(struct atm_vcc *atmvcc, unsigned int cmd, |
unsigned long 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 *) arg)) |
return -EFAULT; |
if (b != ATM_BACKEND_PPP) |
return -ENOIOCTLCMD; |
if (!capable(CAP_NET_ADMIN)) |
return -EPERM; |
return pppoatm_assign_vcc(atmvcc, arg); |
} |
case PPPIOCGCHAN: |
return put_user(ppp_channel_index(&atmvcc_to_pvcc(atmvcc)-> |
chan), (int *) arg) ? -EFAULT : 0; |
case PPPIOCGUNIT: |
return put_user(ppp_unit_number(&atmvcc_to_pvcc(atmvcc)-> |
chan), (int *) arg) ? -EFAULT : 0; |
} |
return -ENOIOCTLCMD; |
} |
|
/* the following avoids some spurious warnings from the compiler */ |
#define UNUSED __attribute__((unused)) |
|
static int __init UNUSED pppoatm_init(void) |
{ |
pppoatm_ioctl_set(pppoatm_ioctl); |
return 0; |
} |
|
static void __exit UNUSED pppoatm_exit(void) |
{ |
pppoatm_ioctl_set(NULL); |
} |
|
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"); |
/ipcommon.c
0,0 → 1,71
/* net/atm/ipcommon.c - Common items for all ways of doing IP over ATM */ |
|
/* Written 1996-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#include <linux/module.h> |
#include <linux/string.h> |
#include <linux/skbuff.h> |
#include <linux/netdevice.h> |
#include <linux/in.h> |
#include <linux/atmdev.h> |
#include <linux/atmclip.h> |
|
#include "common.h" |
#include "ipcommon.h" |
|
|
#if 0 |
#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) |
#else |
#define DPRINTK(format,args...) |
#endif |
|
|
const unsigned char llc_oui[] = { |
0xaa, /* DSAP: non-ISO */ |
0xaa, /* SSAP: non-ISO */ |
0x03, /* Ctrl: Unnumbered Information Command PDU */ |
0x00, /* OUI: EtherType */ |
0x00, |
0x00 }; |
|
|
/* |
* skb_migrate appends the list at "from" to "to", emptying "from" in the |
* process. skb_migrate is atomic with respect to all other skb operations on |
* "from" and "to". Note that it locks both lists at the same time, so beware |
* of potential deadlocks. |
* |
* This function should live in skbuff.c or skbuff.h. |
*/ |
|
|
void skb_migrate(struct sk_buff_head *from,struct sk_buff_head *to) |
{ |
struct sk_buff *skb; |
unsigned long flags; |
struct sk_buff *skb_from = (struct sk_buff *) from; |
struct sk_buff *skb_to = (struct sk_buff *) to; |
struct sk_buff *prev; |
|
spin_lock_irqsave(&from->lock,flags); |
spin_lock(&to->lock); |
prev = from->prev; |
from->next->prev = to->prev; |
prev->next = skb_to; |
to->prev->next = from->next; |
to->prev = from->prev; |
for (skb = from->next; skb != skb_to; skb = skb->next) |
skb->list = to; |
to->qlen += from->qlen; |
spin_unlock(&to->lock); |
from->prev = skb_from; |
from->next = skb_from; |
from->qlen = 0; |
spin_unlock_irqrestore(&from->lock,flags); |
} |
|
|
EXPORT_SYMBOL(llc_oui); |
EXPORT_SYMBOL(skb_migrate); |
/mpc.h
0,0 → 1,67
#ifndef _MPC_H_ |
#define _MPC_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); |
|
/* Functions for ioctl(ATMMPC_*) operations */ |
int atm_mpoa_mpoad_attach(struct atm_vcc *vcc, int arg); |
int atm_mpoa_vcc_attach(struct atm_vcc *vcc, long arg); |
|
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; |
uint32_t ipaddr; |
struct atm_qos qos; |
}; |
|
|
/* Functions to call during ioctl(ATMMPC, ) */ |
struct atm_mpoa_ops { |
int (*mpoad_attach)(struct atm_vcc *vcc, int arg); /* attach mpoa daemon */ |
int (*vcc_attach)(struct atm_vcc *vcc, long arg); /* attach shortcut vcc */ |
struct module *owner; |
}; |
|
/* Boot/module initialization function */ |
extern struct atm_mpoa_ops *atm_mpoa_ops; |
int try_atm_mpoa_ops(void); |
void atm_mpoa_ops_set(struct atm_mpoa_ops *hook); |
|
/* MPOA QoS operations */ |
struct atm_mpoa_qos *atm_mpoa_add_qos(uint32_t dst_ip, struct atm_qos *qos); |
struct atm_mpoa_qos *atm_mpoa_search_qos(uint32_t dst_ip); |
int atm_mpoa_delete_qos(struct atm_mpoa_qos *qos); |
|
/* Display QoS entries. This is for the procfs */ |
void atm_mpoa_disp_qos(char *page, int *len); |
|
#endif /* _MPC_H_ */ |
/ipcommon.h
0,0 → 1,25
/* net/atm/ipcommon.h - Common items for all ways of doing IP over ATM */ |
|
/* Written 1996-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#ifndef NET_ATM_IPCOMMON_H |
#define NET_ATM_IPCOMMON_H |
|
|
#include <linux/string.h> |
#include <linux/skbuff.h> |
#include <linux/netdevice.h> |
#include <linux/atmdev.h> |
|
|
extern struct net_device *clip_devs; |
|
/* |
* Appends all skbs from "from" to "to". The operation is atomic with respect |
* to all other skb operations on "from" or "to". |
*/ |
|
void skb_migrate(struct sk_buff_head *from,struct sk_buff_head *to); |
|
#endif |
/br2684.c
0,0 → 1,815
/* |
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/config.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/atmbr2684.h> |
|
#include "common.h" |
#include "ipcommon.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 br2684_dev *brdev; |
/* 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 rwlock_t devs_lock = RW_LOCK_UNLOCKED; |
|
static LIST_HEAD(br2684_devs); |
|
static inline struct br2684_dev *BRPRIV(const struct net_device *net_dev) |
{ |
return (struct br2684_dev *) ((char *) (net_dev) - |
(unsigned long) (&((struct br2684_dev *) 0)->net_dev)); |
} |
|
static inline struct br2684_dev *list_entry_brdev(const struct list_head *le) |
{ |
return list_entry(le, struct br2684_dev, br2684_devs); |
} |
|
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 br2684_dev *br2684_find_dev(const struct br2684_if_spec *s) |
{ |
struct list_head *lh; |
struct br2684_dev *brdev; |
switch (s->method) { |
case BR2684_FIND_BYNUM: |
list_for_each(lh, &br2684_devs) { |
brdev = list_entry_brdev(lh); |
if (brdev->number == s->spec.devnum) |
return brdev; |
} |
break; |
case BR2684_FIND_BYIFNAME: |
list_for_each(lh, &br2684_devs) { |
brdev = list_entry_brdev(lh); |
if (!strncmp(brdev->net_dev.name, s->spec.ifname, |
sizeof brdev->net_dev.name)) |
return brdev; |
} |
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, &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 -EUNATCH; |
} |
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 unsigned short br_type_trans(struct sk_buff *skb, |
struct net_device *dev) |
{ |
struct ethhdr *eth; |
unsigned char *rawp; |
eth = skb->mac.ethernet; |
|
if (*eth->h_dest & 1) { |
if (memcmp(eth->h_dest, dev->broadcast, ETH_ALEN) == 0) |
skb->pkt_type = PACKET_BROADCAST; |
else |
skb->pkt_type = PACKET_MULTICAST; |
} |
|
else if (memcmp(eth->h_dest, dev->dev_addr, ETH_ALEN)) |
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, unsigned long arg) |
{ |
struct br2684_vcc *brvcc; |
struct br2684_filter_set fs; |
|
if (copy_from_user(&fs, (void *) 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 = 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(u16 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->brdev); |
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); |
MOD_DEC_USE_COUNT; |
} |
|
/* 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 br2684_dev *brdev = brvcc->brdev; |
int plen = sizeof(llc_oui_pid_pad) + ETH_HLEN; |
|
DPRINTK("br2684_push\n"); |
|
if (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(&brdev->net_dev); |
kfree(brdev); |
} |
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, &brdev->net_dev); |
#endif /* CONFIG_BR2684_FAST_TRANS */ |
#else |
skb_pull(skb, plen - ETH_HLEN); |
skb->protocol = eth_type_trans(skb, &brdev->net_dev); |
#endif /* FASTER_VERSION */ |
#ifdef CONFIG_ATM_BR2684_IPFILTER |
if (packet_fails_filter(skb->protocol, brvcc, skb)) { |
brdev->stats.rx_dropped++; |
dev_kfree_skb(skb); |
return; |
} |
#endif /* CONFIG_ATM_BR2684_IPFILTER */ |
skb->dev = &brdev->net_dev; |
ATM_SKB(skb)->vcc = atmvcc; /* needed ? */ |
DPRINTK("received packet's protocol: %x\n", ntohs(skb->protocol)); |
skb_debug(skb); |
if (!(brdev->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, unsigned long 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_head copy; |
struct sk_buff *skb; |
struct br2684_dev *brdev; |
struct atm_backend_br2684 be; |
|
MOD_INC_USE_COUNT; |
if (copy_from_user(&be, (void *) arg, sizeof be)) { |
MOD_DEC_USE_COUNT; |
return -EFAULT; |
} |
write_lock_irq(&devs_lock); |
brdev = br2684_find_dev(&be.ifspec); |
if (brdev == NULL) { |
printk(KERN_ERR |
"br2684: tried to attach to non-existant device\n"); |
err = -ENXIO; |
goto error; |
} |
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; |
} |
brvcc = kmalloc(sizeof(struct br2684_vcc), GFP_KERNEL); |
if (!brvcc) { |
err = -ENOMEM; |
goto error; |
} |
memset(brvcc, 0, sizeof(struct br2684_vcc)); |
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(brdev->net_dev.dev_addr, esi, |
brdev->net_dev.addr_len); |
else |
brdev->net_dev.dev_addr[2] = 1; |
} |
list_add(&brvcc->brvccs, &brdev->brvccs); |
write_unlock_irq(&devs_lock); |
brvcc->brdev = brdev; |
brvcc->atmvcc = atmvcc; |
atmvcc->user_back = brvcc; |
brvcc->encaps = (enum br2684_encaps) be.encaps; |
brvcc->old_push = atmvcc->push; |
barrier(); |
atmvcc->push = br2684_push; |
skb_queue_head_init(©); |
skb_migrate(&atmvcc->sk->receive_queue, ©); |
while ((skb = skb_dequeue(©))) { |
BRPRIV(skb->dev)->stats.rx_bytes -= skb->len; |
BRPRIV(skb->dev)->stats.rx_packets--; |
br2684_push(atmvcc, skb); |
} |
return 0; |
error: |
write_unlock_irq(&devs_lock); |
MOD_DEC_USE_COUNT; |
return err; |
} |
|
static int br2684_create(unsigned long arg) |
{ |
int err; |
struct br2684_dev *brdev; |
struct atm_newif_br2684 ni; |
|
DPRINTK("br2684_create\n"); |
/* |
* We track module use by vcc's NOT the devices they're on. We're |
* protected here against module death by the kernel_lock, but if |
* we need to sleep we should make sure that the module doesn't |
* disappear under us. |
*/ |
MOD_INC_USE_COUNT; |
if (copy_from_user(&ni, (void *) arg, sizeof ni)) { |
MOD_DEC_USE_COUNT; |
return -EFAULT; |
} |
if (ni.media != BR2684_MEDIA_ETHERNET || ni.mtu != 1500) { |
MOD_DEC_USE_COUNT; |
return -EINVAL; |
} |
if ((brdev = kmalloc(sizeof(struct br2684_dev), GFP_KERNEL)) == NULL) { |
MOD_DEC_USE_COUNT; |
return -ENOMEM; |
} |
memset(brdev, 0, sizeof(struct br2684_dev)); |
INIT_LIST_HEAD(&brdev->brvccs); |
|
write_lock_irq(&devs_lock); |
brdev->number = list_empty(&br2684_devs) ? 1 : |
list_entry_brdev(br2684_devs.prev)->number + 1; |
list_add_tail(&brdev->br2684_devs, &br2684_devs); |
write_unlock_irq(&devs_lock); |
|
if (ni.ifname[0] != '\0') { |
memcpy(brdev->net_dev.name, ni.ifname, |
sizeof(brdev->net_dev.name)); |
brdev->net_dev.name[sizeof(brdev->net_dev.name) - 1] = '\0'; |
} else |
sprintf(brdev->net_dev.name, "nas%d", brdev->number); |
DPRINTK("registered netdev %s\n", brdev->net_dev.name); |
ether_setup(&brdev->net_dev); |
brdev->mac_was_set = 0; |
#ifdef FASTER_VERSION |
my_eth_header = brdev->net_dev.hard_header; |
brdev->net_dev.hard_header = br2684_header; |
my_eth_header_cache = brdev->net_dev.hard_header_cache; |
brdev->net_dev.hard_header_cache = br2684_header_cache; |
brdev->net_dev.hard_header_len = sizeof(llc_oui_pid_pad) + ETH_HLEN; /* 10 + 14 */ |
#endif |
my_eth_mac_addr = brdev->net_dev.set_mac_address; |
brdev->net_dev.set_mac_address = br2684_mac_addr; |
brdev->net_dev.hard_start_xmit = br2684_start_xmit; |
brdev->net_dev.get_stats = br2684_get_stats; |
|
/* open, stop, do_ioctl ? */ |
err = register_netdev(&brdev->net_dev); |
MOD_DEC_USE_COUNT; |
if (err < 0) { |
printk(KERN_ERR "br2684_create: register_netdev failed\n"); |
write_lock_irq(&devs_lock); |
list_del(&brdev->br2684_devs); |
write_unlock_irq(&devs_lock); |
kfree(brdev); |
return err; |
} |
return 0; |
} |
|
/* |
* This handles ioctls actually performed on our vcc - we must return |
* -ENOIOCTLCMD for any unrecognized ioctl |
*/ |
static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd, |
unsigned long arg) |
{ |
int err; |
switch(cmd) { |
case ATM_SETBACKEND: |
case ATM_NEWBACKENDIF: { |
atm_backend_t b; |
MOD_INC_USE_COUNT; |
err = get_user(b, (atm_backend_t *) arg); |
MOD_DEC_USE_COUNT; |
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, arg); |
else |
return br2684_create(arg); |
} |
#ifdef CONFIG_ATM_BR2684_IPFILTER |
case BR2684_SETFILT: |
if (atmvcc->push != br2684_push) |
return -ENOIOCTLCMD; |
if (!capable(CAP_NET_ADMIN)) |
return -EPERM; |
MOD_INC_USE_COUNT; |
err = br2684_setfilt(atmvcc, arg); |
MOD_DEC_USE_COUNT; |
return err; |
#endif /* CONFIG_ATM_BR2684_IPFILTER */ |
} |
return -ENOIOCTLCMD; |
} |
|
#ifdef CONFIG_PROC_FS |
/* Never put more than 256 bytes in at once */ |
static int br2684_proc_engine(loff_t pos, char *buf) |
{ |
struct list_head *lhd, *lhc; |
struct br2684_dev *brdev; |
struct br2684_vcc *brvcc; |
list_for_each(lhd, &br2684_devs) { |
brdev = list_entry_brdev(lhd); |
if (pos-- == 0) |
return sprintf(buf, "dev %.16s: num=%d, mac=%02X:%02X:" |
"%02X:%02X:%02X:%02X (%s)\n", brdev->net_dev.name, |
brdev->number, |
brdev->net_dev.dev_addr[0], |
brdev->net_dev.dev_addr[1], |
brdev->net_dev.dev_addr[2], |
brdev->net_dev.dev_addr[3], |
brdev->net_dev.dev_addr[4], |
brdev->net_dev.dev_addr[5], |
brdev->mac_was_set ? "set" : "auto"); |
list_for_each(lhc, &brdev->brvccs) { |
brvcc = list_entry_brvcc(lhc); |
if (pos-- == 0) |
return sprintf(buf, " 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 && pos-- == 0) |
return sprintf(buf, " 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 ssize_t br2684_proc_read(struct file *file, char *buf, size_t count, |
loff_t *pos) |
{ |
unsigned long page; |
int len = 0, x, left; |
page = get_free_page(GFP_KERNEL); |
if (!page) |
return -ENOMEM; |
left = PAGE_SIZE - 256; |
if (count < left) |
left = count; |
read_lock(&devs_lock); |
for (;;) { |
x = br2684_proc_engine(*pos, &((char *) page)[len]); |
if (x == 0) |
break; |
if (x > left) |
/* |
* This should only happen if the user passed in |
* a "count" too small for even one line |
*/ |
x = -EINVAL; |
if (x < 0) { |
len = x; |
break; |
} |
len += x; |
left -= x; |
(*pos)++; |
if (left < 256) |
break; |
} |
read_unlock(&devs_lock); |
if (len > 0 && copy_to_user(buf, (char *) page, len)) |
len = -EFAULT; |
free_page(page); |
return len; |
} |
|
static struct file_operations br2684_proc_operations = { |
read: br2684_proc_read, |
}; |
|
extern struct proc_dir_entry *atm_proc_root; /* from proc.c */ |
#endif /* CONFIG_PROC_FS */ |
|
/* the following avoids some spurious warnings from the compiler */ |
#define UNUSED __attribute__((unused)) |
|
static int __init UNUSED 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_operations; |
#endif /* CONFIG_PROC_FS */ |
br2684_ioctl_set(br2684_ioctl); |
return 0; |
} |
|
static void __exit UNUSED br2684_exit(void) |
{ |
struct br2684_dev *brdev; |
br2684_ioctl_set(NULL); |
#ifdef CONFIG_PROC_FS |
remove_proc_entry("br2684", atm_proc_root); |
#endif /* CONFIG_PROC_FS */ |
while (!list_empty(&br2684_devs)) { |
brdev = list_entry_brdev(br2684_devs.next); |
unregister_netdev(&brdev->net_dev); |
list_del(&brdev->br2684_devs); |
kfree(brdev); |
} |
} |
|
module_init(br2684_init); |
module_exit(br2684_exit); |
|
MODULE_AUTHOR("Marcell GAL"); |
MODULE_DESCRIPTION("RFC2684 bridged protocols over ATM/AAL5"); |
MODULE_LICENSE("GPL"); |
/clip.c
0,0 → 1,815
/* net/atm/clip.c - RFC1577 Classical IP over ATM */ |
|
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#include <linux/config.h> |
#include <linux/string.h> |
#include <linux/errno.h> |
#include <linux/kernel.h> /* for UINT_MAX */ |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/netdevice.h> |
#include <linux/skbuff.h> |
#include <linux/wait.h> |
#include <linux/timer.h> |
#include <linux/if_arp.h> /* for some manifest constants */ |
#include <linux/notifier.h> |
#include <linux/atm.h> |
#include <linux/atmdev.h> |
#include <linux/atmclip.h> |
#include <linux/atmarp.h> |
#include <linux/ip.h> /* for net/route.h */ |
#include <linux/in.h> /* for struct sockaddr_in */ |
#include <linux/if.h> /* for IFF_UP */ |
#include <linux/inetdevice.h> |
#include <linux/bitops.h> |
#include <net/route.h> /* for struct rtable and routing */ |
#include <net/icmp.h> /* icmp_send */ |
#include <asm/param.h> /* for HZ */ |
#include <asm/byteorder.h> /* for htons etc. */ |
#include <asm/system.h> /* save/restore_flags */ |
#include <asm/uaccess.h> |
#include <asm/atomic.h> |
|
#include "common.h" |
#include "resources.h" |
#include "ipcommon.h" |
#include <net/atmclip.h> |
|
|
#if 0 |
#define DPRINTK(format,args...) printk(format,##args) |
#else |
#define DPRINTK(format,args...) |
#endif |
|
|
struct net_device *clip_devs = NULL; |
struct atm_vcc *atmarpd = NULL; |
static struct neigh_table clip_tbl; |
static struct timer_list idle_timer; |
static int start_timer = 1; |
|
|
static int to_atmarpd(enum atmarp_ctrl_type type,int itf,unsigned long ip) |
{ |
struct atmarp_ctrl *ctrl; |
struct sk_buff *skb; |
|
DPRINTK("to_atmarpd(%d)\n",type); |
if (!atmarpd) return -EUNATCH; |
skb = alloc_skb(sizeof(struct atmarp_ctrl),GFP_ATOMIC); |
if (!skb) return -ENOMEM; |
ctrl = (struct atmarp_ctrl *) skb_put(skb,sizeof(struct atmarp_ctrl)); |
ctrl->type = type; |
ctrl->itf_num = itf; |
ctrl->ip = ip; |
atm_force_charge(atmarpd,skb->truesize); |
skb_queue_tail(&atmarpd->sk->receive_queue,skb); |
wake_up(&atmarpd->sleep); |
return 0; |
} |
|
|
static void link_vcc(struct clip_vcc *clip_vcc,struct atmarp_entry *entry) |
{ |
DPRINTK("link_vcc %p to entry %p (neigh %p)\n",clip_vcc,entry, |
entry->neigh); |
clip_vcc->entry = entry; |
clip_vcc->xoff = 0; /* @@@ may overrun buffer by one packet */ |
clip_vcc->next = entry->vccs; |
entry->vccs = clip_vcc; |
entry->neigh->used = jiffies; |
} |
|
|
static void unlink_clip_vcc(struct clip_vcc *clip_vcc) |
{ |
struct atmarp_entry *entry = clip_vcc->entry; |
struct clip_vcc **walk; |
|
if (!entry) { |
printk(KERN_CRIT "!clip_vcc->entry (clip_vcc %p)\n",clip_vcc); |
return; |
} |
spin_lock_bh(&entry->neigh->dev->xmit_lock); /* block clip_start_xmit() */ |
entry->neigh->used = jiffies; |
for (walk = &entry->vccs; *walk; walk = &(*walk)->next) |
if (*walk == clip_vcc) { |
int error; |
|
*walk = clip_vcc->next; /* atomic */ |
clip_vcc->entry = NULL; |
if (clip_vcc->xoff) |
netif_wake_queue(entry->neigh->dev); |
if (entry->vccs) |
goto out; |
entry->expires = jiffies-1; |
/* force resolution or expiration */ |
error = neigh_update(entry->neigh,NULL,NUD_NONE,0,0); |
if (error) |
printk(KERN_CRIT "unlink_clip_vcc: " |
"neigh_update failed with %d\n",error); |
goto out; |
} |
printk(KERN_CRIT "ATMARP: unlink_clip_vcc failed (entry %p, vcc " |
"0x%p)\n",entry,clip_vcc); |
out: |
spin_unlock_bh(&entry->neigh->dev->xmit_lock); |
} |
|
|
static void idle_timer_check(unsigned long dummy) |
{ |
int i; |
|
/*DPRINTK("idle_timer_check\n");*/ |
write_lock(&clip_tbl.lock); |
for (i = 0; i <= NEIGH_HASHMASK; i++) { |
struct neighbour **np; |
|
for (np = &clip_tbl.hash_buckets[i]; *np;) { |
struct neighbour *n = *np; |
struct atmarp_entry *entry = NEIGH2ENTRY(n); |
struct clip_vcc *clip_vcc; |
|
write_lock(&n->lock); |
|
for (clip_vcc = entry->vccs; clip_vcc; |
clip_vcc = clip_vcc->next) |
if (clip_vcc->idle_timeout && |
time_after(jiffies, clip_vcc->last_use+ |
clip_vcc->idle_timeout)) { |
DPRINTK("releasing vcc %p->%p of " |
"entry %p\n",clip_vcc,clip_vcc->vcc, |
entry); |
vcc_release_async(clip_vcc->vcc, |
-ETIMEDOUT); |
} |
if (entry->vccs || |
time_before(jiffies, entry->expires)) { |
np = &n->next; |
write_unlock(&n->lock); |
continue; |
} |
if (atomic_read(&n->refcnt) > 1) { |
struct sk_buff *skb; |
|
DPRINTK("destruction postponed with ref %d\n", |
atomic_read(&n->refcnt)); |
while ((skb = skb_dequeue(&n->arp_queue)) != |
NULL) |
dev_kfree_skb(skb); |
np = &n->next; |
write_unlock(&n->lock); |
continue; |
} |
*np = n->next; |
DPRINTK("expired neigh %p\n",n); |
n->dead = 1; |
write_unlock(&n->lock); |
neigh_release(n); |
} |
} |
mod_timer(&idle_timer, jiffies+CLIP_CHECK_INTERVAL*HZ); |
write_unlock(&clip_tbl.lock); |
} |
|
|
static int clip_arp_rcv(struct sk_buff *skb) |
{ |
struct atm_vcc *vcc; |
|
DPRINTK("clip_arp_rcv\n"); |
vcc = ATM_SKB(skb)->vcc; |
if (!vcc || !atm_charge(vcc,skb->truesize)) { |
dev_kfree_skb_any(skb); |
return 0; |
} |
DPRINTK("pushing to %p\n",vcc); |
DPRINTK("using %p\n",CLIP_VCC(vcc)->old_push); |
CLIP_VCC(vcc)->old_push(vcc,skb); |
return 0; |
} |
|
|
static void clip_push(struct atm_vcc *vcc,struct sk_buff *skb) |
{ |
struct clip_vcc *clip_vcc = CLIP_VCC(vcc); |
|
DPRINTK("clip push\n"); |
if (!skb) { |
DPRINTK("removing VCC %p\n",clip_vcc); |
if (clip_vcc->entry) unlink_clip_vcc(clip_vcc); |
clip_vcc->old_push(vcc,NULL); /* pass on the bad news */ |
kfree(clip_vcc); |
return; |
} |
atm_return(vcc,skb->truesize); |
skb->dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : clip_devs; |
/* clip_vcc->entry == NULL if we don't have an IP address yet */ |
if (!skb->dev) { |
dev_kfree_skb_any(skb); |
return; |
} |
ATM_SKB(skb)->vcc = vcc; |
skb->mac.raw = skb->data; |
if (!clip_vcc->encap || skb->len < RFC1483LLC_LEN || memcmp(skb->data, |
llc_oui,sizeof(llc_oui))) skb->protocol = htons(ETH_P_IP); |
else { |
skb->protocol = ((u16 *) skb->data)[3]; |
skb_pull(skb,RFC1483LLC_LEN); |
if (skb->protocol == htons(ETH_P_ARP)) { |
PRIV(skb->dev)->stats.rx_packets++; |
PRIV(skb->dev)->stats.rx_bytes += skb->len; |
clip_arp_rcv(skb); |
return; |
} |
} |
clip_vcc->last_use = jiffies; |
PRIV(skb->dev)->stats.rx_packets++; |
PRIV(skb->dev)->stats.rx_bytes += skb->len; |
memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data)); |
netif_rx(skb); |
} |
|
|
/* |
* Note: these spinlocks _must_not_ block on non-SMP. The only goal is that |
* clip_pop is atomic with respect to the critical section in clip_start_xmit. |
*/ |
|
|
static void clip_pop(struct atm_vcc *vcc,struct sk_buff *skb) |
{ |
struct clip_vcc *clip_vcc = CLIP_VCC(vcc); |
struct net_device *dev = skb->dev; |
int old; |
unsigned long flags; |
|
DPRINTK("clip_pop(vcc %p)\n",vcc); |
clip_vcc->old_pop(vcc,skb); |
/* skb->dev == NULL in outbound ARP packets */ |
if (!dev) return; |
spin_lock_irqsave(&PRIV(dev)->xoff_lock,flags); |
if (atm_may_send(vcc,0)) { |
old = xchg(&clip_vcc->xoff,0); |
if (old) netif_wake_queue(dev); |
} |
spin_unlock_irqrestore(&PRIV(dev)->xoff_lock,flags); |
} |
|
|
static void clip_neigh_destroy(struct neighbour *neigh) |
{ |
DPRINTK("clip_neigh_destroy (neigh %p)\n",neigh); |
if (NEIGH2ENTRY(neigh)->vccs) |
printk(KERN_CRIT "clip_neigh_destroy: vccs != NULL !!!\n"); |
NEIGH2ENTRY(neigh)->vccs = (void *) 0xdeadbeef; |
} |
|
|
static void clip_neigh_solicit(struct neighbour *neigh,struct sk_buff *skb) |
{ |
DPRINTK("clip_neigh_solicit (neigh %p, skb %p)\n",neigh,skb); |
to_atmarpd(act_need,PRIV(neigh->dev)->number,NEIGH2ENTRY(neigh)->ip); |
} |
|
|
static void clip_neigh_error(struct neighbour *neigh,struct sk_buff *skb) |
{ |
#ifndef CONFIG_ATM_CLIP_NO_ICMP |
icmp_send(skb,ICMP_DEST_UNREACH,ICMP_HOST_UNREACH,0); |
#endif |
kfree_skb(skb); |
} |
|
|
static struct neigh_ops clip_neigh_ops = { |
family: AF_INET, |
destructor: clip_neigh_destroy, |
solicit: clip_neigh_solicit, |
error_report: clip_neigh_error, |
output: dev_queue_xmit, |
connected_output: dev_queue_xmit, |
hh_output: dev_queue_xmit, |
queue_xmit: dev_queue_xmit, |
}; |
|
|
static int clip_constructor(struct neighbour *neigh) |
{ |
struct atmarp_entry *entry = NEIGH2ENTRY(neigh); |
struct net_device *dev = neigh->dev; |
struct in_device *in_dev = dev->ip_ptr; |
|
DPRINTK("clip_constructor (neigh %p, entry %p)\n",neigh,entry); |
if (!in_dev) return -EINVAL; |
neigh->type = inet_addr_type(entry->ip); |
if (neigh->type != RTN_UNICAST) return -EINVAL; |
if (in_dev->arp_parms) neigh->parms = in_dev->arp_parms; |
neigh->ops = &clip_neigh_ops; |
neigh->output = neigh->nud_state & NUD_VALID ? |
neigh->ops->connected_output : neigh->ops->output; |
entry->neigh = neigh; |
entry->vccs = NULL; |
entry->expires = jiffies-1; |
return 0; |
} |
|
static u32 clip_hash(const void *pkey, const struct net_device *dev) |
{ |
u32 hash_val; |
|
hash_val = *(u32*)pkey; |
hash_val ^= (hash_val>>16); |
hash_val ^= hash_val>>8; |
hash_val ^= hash_val>>3; |
hash_val = (hash_val^dev->ifindex)&NEIGH_HASHMASK; |
|
return hash_val; |
} |
|
|
static struct neigh_table clip_tbl = { |
NULL, /* next */ |
AF_INET, /* family */ |
sizeof(struct neighbour)+sizeof(struct atmarp_entry), /* entry_size */ |
4, /* key_len */ |
clip_hash, |
clip_constructor, /* constructor */ |
NULL, /* pconstructor */ |
NULL, /* pdestructor */ |
NULL, /* proxy_redo */ |
"clip_arp_cache", |
{ /* neigh_parms */ |
NULL, /* next */ |
NULL, /* neigh_setup */ |
&clip_tbl, /* tbl */ |
0, /* entries */ |
NULL, /* priv */ |
NULL, /* sysctl_table */ |
30*HZ, /* base_reachable_time */ |
1*HZ, /* retrans_time */ |
60*HZ, /* gc_staletime */ |
30*HZ, /* reachable_time */ |
5*HZ, /* delay_probe_time */ |
3, /* queue_len */ |
3, /* ucast_probes */ |
0, /* app_probes */ |
3, /* mcast_probes */ |
1*HZ, /* anycast_delay */ |
(8*HZ)/10, /* proxy_delay */ |
1*HZ, /* proxy_qlen */ |
64 /* locktime */ |
}, |
30*HZ,128,512,1024 /* copied from ARP ... */ |
}; |
|
|
/* @@@ copy bh locking from arp.c -- need to bh-enable atm code before */ |
|
/* |
* We play with the resolve flag: 0 and 1 have the usual meaning, but -1 means |
* to allocate the neighbour entry but not to ask atmarpd for resolution. Also, |
* don't increment the usage count. This is used to create entries in |
* clip_setentry. |
*/ |
|
|
static int clip_encap(struct atm_vcc *vcc,int mode) |
{ |
CLIP_VCC(vcc)->encap = mode; |
return 0; |
} |
|
|
static int clip_start_xmit(struct sk_buff *skb,struct net_device *dev) |
{ |
struct clip_priv *clip_priv = PRIV(dev); |
struct atmarp_entry *entry; |
struct atm_vcc *vcc; |
int old; |
unsigned long flags; |
|
DPRINTK("clip_start_xmit (skb %p)\n",skb); |
if (!skb->dst) { |
printk(KERN_ERR "clip_start_xmit: skb->dst == NULL\n"); |
dev_kfree_skb(skb); |
clip_priv->stats.tx_dropped++; |
return 0; |
} |
if (!skb->dst->neighbour) { |
#if 0 |
skb->dst->neighbour = clip_find_neighbour(skb->dst,1); |
if (!skb->dst->neighbour) { |
dev_kfree_skb(skb); /* lost that one */ |
clip_priv->stats.tx_dropped++; |
return 0; |
} |
#endif |
printk(KERN_ERR "clip_start_xmit: NO NEIGHBOUR !\n"); |
dev_kfree_skb(skb); |
clip_priv->stats.tx_dropped++; |
return 0; |
} |
entry = NEIGH2ENTRY(skb->dst->neighbour); |
if (!entry->vccs) { |
if (time_after(jiffies, entry->expires)) { |
/* should be resolved */ |
entry->expires = jiffies+ATMARP_RETRY_DELAY*HZ; |
to_atmarpd(act_need,PRIV(dev)->number,entry->ip); |
} |
if (entry->neigh->arp_queue.qlen < ATMARP_MAX_UNRES_PACKETS) |
skb_queue_tail(&entry->neigh->arp_queue,skb); |
else { |
dev_kfree_skb(skb); |
clip_priv->stats.tx_dropped++; |
} |
return 0; |
} |
DPRINTK("neigh %p, vccs %p\n",entry,entry->vccs); |
ATM_SKB(skb)->vcc = vcc = entry->vccs->vcc; |
DPRINTK("using neighbour %p, vcc %p\n",skb->dst->neighbour,vcc); |
if (entry->vccs->encap) { |
void *here; |
|
here = skb_push(skb,RFC1483LLC_LEN); |
memcpy(here,llc_oui,sizeof(llc_oui)); |
((u16 *) here)[3] = skb->protocol; |
} |
atomic_add(skb->truesize,&vcc->sk->wmem_alloc); |
ATM_SKB(skb)->atm_options = vcc->atm_options; |
entry->vccs->last_use = jiffies; |
DPRINTK("atm_skb(%p)->vcc(%p)->dev(%p)\n",skb,vcc,vcc->dev); |
old = xchg(&entry->vccs->xoff,1); /* assume XOFF ... */ |
if (old) { |
printk(KERN_WARNING "clip_start_xmit: XOFF->XOFF transition\n"); |
return 0; |
} |
clip_priv->stats.tx_packets++; |
clip_priv->stats.tx_bytes += skb->len; |
(void) vcc->send(vcc,skb); |
if (atm_may_send(vcc,0)) { |
entry->vccs->xoff = 0; |
return 0; |
} |
spin_lock_irqsave(&clip_priv->xoff_lock,flags); |
netif_stop_queue(dev); /* XOFF -> throttle immediately */ |
barrier(); |
if (!entry->vccs->xoff) |
netif_start_queue(dev); |
/* Oh, we just raced with clip_pop. netif_start_queue should be |
good enough, because nothing should really be asleep because |
of the brief netif_stop_queue. If this isn't true or if it |
changes, use netif_wake_queue instead. */ |
spin_unlock_irqrestore(&clip_priv->xoff_lock,flags); |
return 0; |
} |
|
|
static struct net_device_stats *clip_get_stats(struct net_device *dev) |
{ |
return &PRIV(dev)->stats; |
} |
|
|
static int clip_mkip(struct atm_vcc *vcc,int timeout) |
{ |
struct clip_vcc *clip_vcc; |
struct sk_buff_head copy; |
struct sk_buff *skb; |
|
if (!vcc->push) return -EBADFD; |
clip_vcc = kmalloc(sizeof(struct clip_vcc),GFP_KERNEL); |
if (!clip_vcc) return -ENOMEM; |
DPRINTK("mkip clip_vcc %p vcc %p\n",clip_vcc,vcc); |
clip_vcc->vcc = vcc; |
vcc->user_back = clip_vcc; |
clip_vcc->entry = NULL; |
clip_vcc->xoff = 0; |
clip_vcc->encap = 1; |
clip_vcc->last_use = jiffies; |
clip_vcc->idle_timeout = timeout*HZ; |
clip_vcc->old_push = vcc->push; |
clip_vcc->old_pop = vcc->pop; |
vcc->push = clip_push; |
vcc->pop = clip_pop; |
skb_queue_head_init(©); |
skb_migrate(&vcc->sk->receive_queue,©); |
/* re-process everything received between connection setup and MKIP */ |
while ((skb = skb_dequeue(©))) |
if (!clip_devs) { |
atm_return(vcc,skb->truesize); |
kfree_skb(skb); |
} |
else { |
unsigned int len = skb->len; |
|
clip_push(vcc,skb); |
PRIV(skb->dev)->stats.rx_packets--; |
PRIV(skb->dev)->stats.rx_bytes -= len; |
} |
return 0; |
} |
|
|
static int clip_setentry(struct atm_vcc *vcc,u32 ip) |
{ |
struct neighbour *neigh; |
struct atmarp_entry *entry; |
int error; |
struct clip_vcc *clip_vcc; |
struct rtable *rt; |
|
if (vcc->push != clip_push) { |
printk(KERN_WARNING "clip_setentry: non-CLIP VCC\n"); |
return -EBADF; |
} |
clip_vcc = CLIP_VCC(vcc); |
if (!ip) { |
if (!clip_vcc->entry) { |
printk(KERN_ERR "hiding hidden ATMARP entry\n"); |
return 0; |
} |
DPRINTK("setentry: remove\n"); |
unlink_clip_vcc(clip_vcc); |
return 0; |
} |
error = ip_route_output(&rt,ip,0,1,0); |
if (error) return error; |
neigh = __neigh_lookup(&clip_tbl,&ip,rt->u.dst.dev,1); |
ip_rt_put(rt); |
if (!neigh) |
return -ENOMEM; |
entry = NEIGH2ENTRY(neigh); |
if (entry != clip_vcc->entry) { |
if (!clip_vcc->entry) DPRINTK("setentry: add\n"); |
else { |
DPRINTK("setentry: update\n"); |
unlink_clip_vcc(clip_vcc); |
} |
link_vcc(clip_vcc,entry); |
} |
error = neigh_update(neigh,llc_oui,NUD_PERMANENT,1,0); |
neigh_release(neigh); |
return error; |
} |
|
|
static int clip_init(struct net_device *dev) |
{ |
DPRINTK("clip_init %s\n",dev->name); |
dev->hard_start_xmit = clip_start_xmit; |
/* sg_xmit ... */ |
dev->hard_header = NULL; |
dev->rebuild_header = NULL; |
dev->set_mac_address = NULL; |
dev->hard_header_parse = NULL; |
dev->hard_header_cache = NULL; |
dev->header_cache_update = NULL; |
dev->change_mtu = NULL; |
dev->do_ioctl = NULL; |
dev->get_stats = clip_get_stats; |
dev->type = ARPHRD_ATM; |
dev->hard_header_len = RFC1483LLC_LEN; |
dev->mtu = RFC1626_MTU; |
dev->addr_len = 0; |
dev->tx_queue_len = 100; /* "normal" queue (packets) */ |
/* When using a "real" qdisc, the qdisc determines the queue */ |
/* length. tx_queue_len is only used for the default case, */ |
/* without any more elaborate queuing. 100 is a reasonable */ |
/* compromise between decent burst-tolerance and protection */ |
/* against memory hogs. */ |
dev->flags = 0; |
return 0; |
} |
|
|
static int clip_create(int number) |
{ |
struct net_device *dev; |
struct clip_priv *clip_priv; |
int error; |
|
if (number != -1) { |
for (dev = clip_devs; dev; dev = PRIV(dev)->next) |
if (PRIV(dev)->number == number) return -EEXIST; |
} |
else { |
number = 0; |
for (dev = clip_devs; dev; dev = PRIV(dev)->next) |
if (PRIV(dev)->number >= number) |
number = PRIV(dev)->number+1; |
} |
dev = kmalloc(sizeof(struct net_device)+sizeof(struct clip_priv), |
GFP_KERNEL); |
if (!dev) return -ENOMEM; |
memset(dev,0,sizeof(struct net_device)+sizeof(struct clip_priv)); |
clip_priv = PRIV(dev); |
sprintf(dev->name,"atm%d",number); |
dev->init = clip_init; |
spin_lock_init(&clip_priv->xoff_lock); |
clip_priv->number = number; |
error = register_netdev(dev); |
if (error) { |
kfree(dev); |
return error; |
} |
clip_priv->next = clip_devs; |
clip_devs = dev; |
DPRINTK("registered (net:%s)\n",dev->name); |
return number; |
} |
|
|
static int clip_device_event(struct notifier_block *this,unsigned long event, |
void *dev) |
{ |
/* ignore non-CLIP devices */ |
if (((struct net_device *) dev)->type != ARPHRD_ATM || |
((struct net_device *) dev)->init != clip_init) |
return NOTIFY_DONE; |
switch (event) { |
case NETDEV_UP: |
DPRINTK("clip_device_event NETDEV_UP\n"); |
(void) to_atmarpd(act_up,PRIV(dev)->number,0); |
break; |
case NETDEV_GOING_DOWN: |
DPRINTK("clip_device_event NETDEV_DOWN\n"); |
(void) to_atmarpd(act_down,PRIV(dev)->number,0); |
break; |
case NETDEV_CHANGE: |
case NETDEV_CHANGEMTU: |
DPRINTK("clip_device_event NETDEV_CHANGE*\n"); |
(void) to_atmarpd(act_change,PRIV(dev)->number,0); |
break; |
case NETDEV_REBOOT: |
case NETDEV_REGISTER: |
case NETDEV_DOWN: |
DPRINTK("clip_device_event %ld\n",event); |
/* ignore */ |
break; |
default: |
printk(KERN_WARNING "clip_device_event: unknown event " |
"%ld\n",event); |
break; |
} |
return NOTIFY_DONE; |
} |
|
|
static int clip_inet_event(struct notifier_block *this,unsigned long event, |
void *ifa) |
{ |
struct in_device *in_dev; |
|
in_dev = ((struct in_ifaddr *) ifa)->ifa_dev; |
if (!in_dev || !in_dev->dev) { |
printk(KERN_WARNING "clip_inet_event: no device\n"); |
return NOTIFY_DONE; |
} |
/* |
* Transitions are of the down-change-up type, so it's sufficient to |
* handle the change on up. |
*/ |
if (event != NETDEV_UP) return NOTIFY_DONE; |
return clip_device_event(this,NETDEV_CHANGE,in_dev->dev); |
} |
|
|
static struct notifier_block clip_dev_notifier = { |
clip_device_event, |
NULL, |
0 |
}; |
|
|
|
static struct notifier_block clip_inet_notifier = { |
clip_inet_event, |
NULL, |
0 |
}; |
|
|
|
static void atmarpd_close(struct atm_vcc *vcc) |
{ |
DPRINTK("atmarpd_close\n"); |
atmarpd = NULL; /* assumed to be atomic */ |
barrier(); |
unregister_inetaddr_notifier(&clip_inet_notifier); |
unregister_netdevice_notifier(&clip_dev_notifier); |
if (skb_peek(&vcc->sk->receive_queue)) |
printk(KERN_ERR "atmarpd_close: closing with requests " |
"pending\n"); |
skb_queue_purge(&vcc->sk->receive_queue); |
DPRINTK("(done)\n"); |
MOD_DEC_USE_COUNT; |
} |
|
|
static struct atmdev_ops atmarpd_dev_ops = { |
.close = atmarpd_close, |
}; |
|
|
static struct atm_dev atmarpd_dev = { |
.ops = &atmarpd_dev_ops, |
.type = "arpd", |
.number = 999, |
.lock = SPIN_LOCK_UNLOCKED |
}; |
|
|
static int atm_init_atmarp(struct atm_vcc *vcc) |
{ |
struct net_device *dev; |
|
if (atmarpd) return -EADDRINUSE; |
if (start_timer) { |
start_timer = 0; |
init_timer(&idle_timer); |
idle_timer.expires = jiffies+CLIP_CHECK_INTERVAL*HZ; |
idle_timer.function = idle_timer_check; |
add_timer(&idle_timer); |
} |
atmarpd = vcc; |
set_bit(ATM_VF_META,&vcc->flags); |
set_bit(ATM_VF_READY,&vcc->flags); |
/* allow replies and avoid getting closed if signaling dies */ |
vcc->dev = &atmarpd_dev; |
vcc_insert_socket(vcc->sk); |
vcc->push = NULL; |
vcc->pop = NULL; /* crash */ |
vcc->push_oam = NULL; /* crash */ |
if (register_netdevice_notifier(&clip_dev_notifier)) |
printk(KERN_ERR "register_netdevice_notifier failed\n"); |
if (register_inetaddr_notifier(&clip_inet_notifier)) |
printk(KERN_ERR "register_inetaddr_notifier failed\n"); |
for (dev = clip_devs; dev; dev = PRIV(dev)->next) |
if (dev->flags & IFF_UP) |
(void) to_atmarpd(act_up,PRIV(dev)->number,0); |
MOD_INC_USE_COUNT; |
return 0; |
} |
|
static struct atm_clip_ops __atm_clip_ops = { |
.clip_create = clip_create, |
.clip_mkip = clip_mkip, |
.clip_setentry = clip_setentry, |
.clip_encap = clip_encap, |
.clip_push = clip_push, |
.atm_init_atmarp = atm_init_atmarp, |
.owner = THIS_MODULE |
}; |
|
static int __init atm_clip_init(void) |
{ |
/* we should use neigh_table_init() */ |
clip_tbl.lock = RW_LOCK_UNLOCKED; |
clip_tbl.kmem_cachep = kmem_cache_create(clip_tbl.id, |
clip_tbl.entry_size, 0, SLAB_HWCACHE_ALIGN, NULL, NULL); |
|
if (!clip_tbl.kmem_cachep) |
return -ENOMEM; |
|
/* so neigh_ifdown() doesn't complain */ |
clip_tbl.proxy_timer.data = 0; |
clip_tbl.proxy_timer.function = 0; |
init_timer(&clip_tbl.proxy_timer); |
skb_queue_head_init(&clip_tbl.proxy_queue); |
|
clip_tbl_hook = &clip_tbl; |
atm_clip_ops_set(&__atm_clip_ops); |
|
return 0; |
} |
|
static void __exit atm_clip_exit(void) |
{ |
struct net_device *dev, *next; |
|
atm_clip_ops_set(NULL); |
|
neigh_ifdown(&clip_tbl, NULL); |
dev = clip_devs; |
while (dev) { |
next = PRIV(dev)->next; |
unregister_netdev(dev); |
kfree(dev); |
dev = next; |
} |
if (start_timer == 0) del_timer(&idle_timer); |
|
kmem_cache_destroy(clip_tbl.kmem_cachep); |
|
clip_tbl_hook = NULL; |
} |
|
module_init(atm_clip_init); |
module_exit(atm_clip_exit); |
|
MODULE_LICENSE("GPL"); |
/pvc.c
0,0 → 1,157
/* net/atm/pvc.c - ATM PVC sockets */ |
|
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#include <linux/config.h> |
#include <linux/net.h> /* struct socket, struct net_proto, |
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 *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 *optval, int *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 struct proto_ops pvc_proto_ops = { |
.family = PF_ATMPVC, |
|
.release = vcc_release, |
.bind = pvc_bind, |
.connect = pvc_connect, |
.socketpair = sock_no_socketpair, |
.accept = sock_no_accept, |
.getname = pvc_getname, |
.poll = atm_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 = { |
PF_ATMPVC, |
pvc_create, |
0, /* no authentication */ |
0, /* no encryption */ |
0 /* no encrypt_net */ |
}; |
|
|
/* |
* Initialize the ATM PVC protocol family |
*/ |
|
|
int atmpvc_init(void) |
{ |
return sock_register(&pvc_family_ops); |
} |
|
void atmpvc_exit(void) |
{ |
sock_unregister(PF_ATMPVC); |
} |
/common.c
0,0 → 1,1128
/* net/atm/common.c - ATM sockets (common part for PVC and SVC) */ |
|
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/kmod.h> |
#include <linux/net.h> /* struct socket, struct net_proto, 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/sonet.h> /* for ioctls */ |
#include <linux/socket.h> /* SOL_SOCKET */ |
#include <linux/errno.h> /* error codes */ |
#include <linux/capability.h> |
#include <linux/mm.h> /* verify_area */ |
#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 <asm/ioctls.h> |
|
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) |
#include <linux/atmlec.h> |
#include "lec.h" |
#include "lec_arpc.h" |
struct atm_lane_ops *atm_lane_ops; |
static DECLARE_MUTEX(atm_lane_ops_mutex); |
|
void atm_lane_ops_set(struct atm_lane_ops *hook) |
{ |
down(&atm_lane_ops_mutex); |
atm_lane_ops = hook; |
up(&atm_lane_ops_mutex); |
} |
|
int try_atm_lane_ops(void) |
{ |
down(&atm_lane_ops_mutex); |
if (atm_lane_ops && try_inc_mod_count(atm_lane_ops->owner)) { |
up(&atm_lane_ops_mutex); |
return 1; |
} |
up(&atm_lane_ops_mutex); |
return 0; |
} |
|
#if defined(CONFIG_ATM_LANE_MODULE) || defined(CONFIG_ATM_MPOA_MODULE) |
EXPORT_SYMBOL(atm_lane_ops); |
EXPORT_SYMBOL(try_atm_lane_ops); |
EXPORT_SYMBOL(atm_lane_ops_set); |
#endif |
#endif |
|
#if defined(CONFIG_ATM_MPOA) || defined(CONFIG_ATM_MPOA_MODULE) |
#include <linux/atmmpc.h> |
#include "mpc.h" |
struct atm_mpoa_ops *atm_mpoa_ops; |
static DECLARE_MUTEX(atm_mpoa_ops_mutex); |
|
void atm_mpoa_ops_set(struct atm_mpoa_ops *hook) |
{ |
down(&atm_mpoa_ops_mutex); |
atm_mpoa_ops = hook; |
up(&atm_mpoa_ops_mutex); |
} |
|
int try_atm_mpoa_ops(void) |
{ |
down(&atm_mpoa_ops_mutex); |
if (atm_mpoa_ops && try_inc_mod_count(atm_mpoa_ops->owner)) { |
up(&atm_mpoa_ops_mutex); |
return 1; |
} |
up(&atm_mpoa_ops_mutex); |
return 0; |
} |
#ifdef CONFIG_ATM_MPOA_MODULE |
EXPORT_SYMBOL(atm_mpoa_ops); |
EXPORT_SYMBOL(try_atm_mpoa_ops); |
EXPORT_SYMBOL(atm_mpoa_ops_set); |
#endif |
#endif |
|
#if defined(CONFIG_ATM_TCP) || defined(CONFIG_ATM_TCP_MODULE) |
#include <linux/atm_tcp.h> |
#ifdef CONFIG_ATM_TCP_MODULE |
struct atm_tcp_ops atm_tcp_ops; |
EXPORT_SYMBOL(atm_tcp_ops); |
#endif |
#endif |
|
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
#include <net/atmclip.h> |
struct atm_clip_ops *atm_clip_ops; |
static DECLARE_MUTEX(atm_clip_ops_mutex); |
|
void atm_clip_ops_set(struct atm_clip_ops *hook) |
{ |
down(&atm_clip_ops_mutex); |
atm_clip_ops = hook; |
up(&atm_clip_ops_mutex); |
} |
|
int try_atm_clip_ops(void) |
{ |
down(&atm_clip_ops_mutex); |
if (atm_clip_ops && try_inc_mod_count(atm_clip_ops->owner)) { |
up(&atm_clip_ops_mutex); |
return 1; |
} |
up(&atm_clip_ops_mutex); |
return 0; |
} |
|
#ifdef CONFIG_ATM_CLIP_MODULE |
EXPORT_SYMBOL(atm_clip_ops); |
EXPORT_SYMBOL(try_atm_clip_ops); |
EXPORT_SYMBOL(atm_clip_ops_set); |
#endif |
#endif |
|
#if defined(CONFIG_PPPOATM) || defined(CONFIG_PPPOATM_MODULE) |
static DECLARE_MUTEX(pppoatm_ioctl_mutex); |
|
static int (*pppoatm_ioctl_hook)(struct atm_vcc *, unsigned int, unsigned long); |
|
void pppoatm_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long)) |
{ |
down(&pppoatm_ioctl_mutex); |
pppoatm_ioctl_hook = hook; |
up(&pppoatm_ioctl_mutex); |
} |
#ifdef CONFIG_PPPOATM_MODULE |
EXPORT_SYMBOL(pppoatm_ioctl_set); |
#endif |
#endif |
|
#if defined(CONFIG_ATM_BR2684) || defined(CONFIG_ATM_BR2684_MODULE) |
static DECLARE_MUTEX(br2684_ioctl_mutex); |
|
static int (*br2684_ioctl_hook)(struct atm_vcc *, unsigned int, unsigned long); |
|
void br2684_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long)) |
{ |
down(&br2684_ioctl_mutex); |
br2684_ioctl_hook = hook; |
up(&br2684_ioctl_mutex); |
} |
#ifdef CONFIG_ATM_BR2684_MODULE |
EXPORT_SYMBOL(br2684_ioctl_set); |
#endif |
#endif |
|
#include "resources.h" /* atm_find_dev */ |
#include "common.h" /* prototypes */ |
#include "protocols.h" /* atm_init_<transport> */ |
#include "addr.h" /* address registry */ |
#ifdef CONFIG_ATM_CLIP |
#include <net/atmclip.h> /* for clip_create */ |
#endif |
#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 sock *vcc_sklist; |
rwlock_t vcc_sklist_lock = RW_LOCK_UNLOCKED; |
|
void __vcc_insert_socket(struct sock *sk) |
{ |
sk->next = vcc_sklist; |
if (sk->next) |
vcc_sklist->pprev = &sk->next; |
vcc_sklist = sk; |
sk->pprev = &vcc_sklist; |
} |
|
void vcc_insert_socket(struct sock *sk) |
{ |
write_lock_irq(&vcc_sklist_lock); |
__vcc_insert_socket(sk); |
write_unlock_irq(&vcc_sklist_lock); |
} |
|
void vcc_remove_socket(struct sock *sk) |
{ |
write_lock_irq(&vcc_sklist_lock); |
if (sk->pprev) { |
if (sk->next) |
sk->next->pprev = sk->pprev; |
*sk->pprev = sk->next; |
sk->pprev = NULL; |
} |
write_unlock_irq(&vcc_sklist_lock); |
} |
|
|
static struct sk_buff *alloc_tx(struct atm_vcc *vcc,unsigned int size) |
{ |
struct sk_buff *skb; |
|
if (atomic_read(&vcc->sk->wmem_alloc) && !atm_may_send(vcc,size)) { |
DPRINTK("Sorry: wmem_alloc = %d, size = %d, sndbuf = %d\n", |
atomic_read(&vcc->sk->wmem_alloc),size,vcc->sk->sndbuf); |
return NULL; |
} |
while (!(skb = alloc_skb(size,GFP_KERNEL))) schedule(); |
DPRINTK("AlTx %d += %d\n",atomic_read(&vcc->sk->wmem_alloc),skb->truesize); |
atomic_add(skb->truesize, &vcc->sk->wmem_alloc); |
return skb; |
} |
|
|
EXPORT_SYMBOL(vcc_sklist); |
EXPORT_SYMBOL(vcc_sklist_lock); |
EXPORT_SYMBOL(vcc_insert_socket); |
EXPORT_SYMBOL(vcc_remove_socket); |
|
static void vcc_sock_destruct(struct sock *sk) |
{ |
struct atm_vcc *vcc = sk->protinfo.af_atm; |
|
if (atomic_read(&vcc->sk->rmem_alloc)) |
printk(KERN_DEBUG "vcc_sock_destruct: rmem leakage (%d bytes) detected.\n", atomic_read(&sk->rmem_alloc)); |
|
if (atomic_read(&vcc->sk->wmem_alloc)) |
printk(KERN_DEBUG "vcc_sock_destruct: wmem leakage (%d bytes) detected.\n", atomic_read(&sk->wmem_alloc)); |
|
kfree(sk->protinfo.af_atm); |
|
MOD_DEC_USE_COUNT; |
} |
|
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, 1); |
if (!sk) |
return -ENOMEM; |
sock_init_data(NULL, sk); |
|
vcc = sk->protinfo.af_atm = kmalloc(sizeof(*vcc), GFP_KERNEL); |
if (!vcc) { |
sk_free(sk); |
return -ENOMEM; |
} |
|
memset(vcc, 0, sizeof(*vcc)); |
vcc->sk = sk; |
|
vcc->dev = NULL; |
vcc->callback = 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(&vcc->sk->wmem_alloc,0); |
atomic_set(&vcc->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; |
init_waitqueue_head(&vcc->sleep); |
sk->sleep = &vcc->sleep; |
sk->destruct = vcc_sock_destruct; |
sock->sk = sk; |
|
MOD_INC_USE_COUNT; |
|
return 0; |
} |
|
|
static void vcc_destroy_socket(struct sock *sk) |
{ |
struct atm_vcc *vcc; |
struct sk_buff *skb; |
|
vcc = sk->protinfo.af_atm; |
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 */ |
|
vcc_remove_socket(sk); /* no more receive */ |
|
while ((skb = skb_dequeue(&vcc->sk->receive_queue))) { |
atm_return(vcc,skb->truesize); |
kfree_skb(skb); |
} |
|
if (vcc->dev->ops->owner) |
__MOD_DEC_USE_COUNT(vcc->dev->ops->owner); |
atm_dev_put(vcc->dev); |
} |
} |
|
|
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) |
{ |
set_bit(ATM_VF_CLOSE, &vcc->flags); |
vcc->reply = reply; |
vcc->sk->err = -reply; |
wake_up(&vcc->sleep); |
} |
|
|
EXPORT_SYMBOL(vcc_release_async); |
|
|
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 __vcc_connect(struct atm_vcc *vcc, struct atm_dev *dev, int vpi, |
int vci) |
{ |
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 = 0; |
if (!try_inc_mod_count(dev->ops->owner)) |
return -ENODEV; |
vcc->dev = dev; |
vcc_insert_socket(vcc->sk); |
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,vpi,vci))) |
goto fail; |
} |
return 0; |
|
fail: |
vcc_remove_socket(vcc->sk); |
if (dev->ops->owner) |
__MOD_DEC_USE_COUNT(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 (itf != ATM_ITF_ANY) { |
dev = atm_dev_lookup(itf); |
if (!dev) |
return -ENODEV; |
error = __vcc_connect(vcc, dev, vpi, vci); |
if (error) { |
atm_dev_put(dev); |
return error; |
} |
} else { |
struct list_head *p, *next; |
|
dev = NULL; |
spin_lock(&atm_dev_lock); |
list_for_each_safe(p, next, &atm_devs) { |
dev = list_entry(p, struct atm_dev, dev_list); |
atm_dev_hold(dev); |
spin_unlock(&atm_dev_lock); |
if (!__vcc_connect(vcc, dev, vpi, vci)) |
break; |
atm_dev_put(dev); |
dev = NULL; |
spin_lock(&atm_dev_lock); |
} |
spin_unlock(&atm_dev_lock); |
if (!dev) |
return -ENODEV; |
} |
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 socket *sock, struct msghdr *msg, |
int size, int flags, struct scm_cookie *scm) |
{ |
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); |
if (vcc->dev->ops->feedback) |
vcc->dev->ops->feedback(vcc, skb, (unsigned long) skb->data, |
(unsigned long) msg->msg_iov->iov_base, copied); |
DPRINTK("RcvM %d -= %d\n", atomic_read(&vcc->sk->rmem_alloc), skb->truesize); |
atm_return(vcc, skb->truesize); |
skb_free_datagram(sk, skb); |
return copied; |
} |
|
|
|
int vcc_sendmsg(struct socket *sock, struct msghdr *m, int total_len, |
struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
DECLARE_WAITQUEUE(wait,current); |
struct atm_vcc *vcc; |
struct sk_buff *skb; |
int eff,error; |
const void *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; |
} |
/* verify_area is done by net/socket.c */ |
eff = (size+3) & ~3; /* align to word boundary */ |
add_wait_queue(&vcc->sleep,&wait); |
set_current_state(TASK_INTERRUPTIBLE); |
error = 0; |
while (!(skb = alloc_tx(vcc,eff))) { |
if (m->msg_flags & MSG_DONTWAIT) { |
error = -EAGAIN; |
break; |
} |
schedule(); |
set_current_state(TASK_INTERRUPTIBLE); |
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; |
} |
} |
set_current_state(TASK_RUNNING); |
remove_wait_queue(&vcc->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 atm_poll(struct file *file,struct socket *sock,poll_table *wait) |
{ |
struct atm_vcc *vcc; |
unsigned int mask; |
|
vcc = ATM_SD(sock); |
poll_wait(file,&vcc->sleep,wait); |
mask = 0; |
if (skb_peek(&vcc->sk->receive_queue)) |
mask |= POLLIN | POLLRDNORM; |
if (test_bit(ATM_VF_RELEASED,&vcc->flags) || |
test_bit(ATM_VF_CLOSE,&vcc->flags)) |
mask |= POLLHUP; |
if (sock->state != SS_CONNECTING) { |
if (vcc->qos.txtp.traffic_class != ATM_NONE && |
vcc->qos.txtp.max_sdu+atomic_read(&vcc->sk->wmem_alloc) <= vcc->sk->sndbuf) |
mask |= POLLOUT | POLLWRNORM; |
} |
else if (vcc->reply != WAITING) { |
mask |= POLLOUT | POLLWRNORM; |
if (vcc->reply) mask |= POLLERR; |
} |
return mask; |
} |
|
|
int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) |
{ |
struct atm_vcc *vcc; |
int error; |
|
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(vcc->sk->sndbuf- |
atomic_read(&vcc->sk->wmem_alloc), |
(int *) arg) ? -EFAULT : 0; |
goto done; |
case SIOCINQ: |
{ |
struct sk_buff *skb; |
|
if (sock->state != SS_CONNECTED) { |
error = -EINVAL; |
goto done; |
} |
skb = skb_peek(&vcc->sk->receive_queue); |
error = put_user(skb ? skb->len : 0, |
(int *) arg) ? -EFAULT : 0; |
goto done; |
} |
case SIOCGSTAMP: /* borrowed from IP */ |
if (!vcc->sk->stamp.tv_sec) { |
error = -ENOENT; |
goto done; |
} |
error = copy_to_user((void *) arg, &vcc->sk->stamp, |
sizeof(struct timeval)) ? -EFAULT : 0; |
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; |
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) |
case SIOCMKCLIP: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (try_atm_clip_ops()) { |
error = atm_clip_ops->clip_create(arg); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
} else |
error = -ENOSYS; |
goto done; |
case ATMARPD_CTRL: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
#if defined(CONFIG_ATM_CLIP_MODULE) |
if (!atm_clip_ops) |
request_module("clip"); |
#endif |
if (try_atm_clip_ops()) { |
error = atm_clip_ops->atm_init_atmarp(vcc); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
if (!error) |
sock->state = SS_CONNECTED; |
} else |
error = -ENOSYS; |
goto done; |
case ATMARP_MKIP: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (try_atm_clip_ops()) { |
error = atm_clip_ops->clip_mkip(vcc, arg); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
} else |
error = -ENOSYS; |
goto done; |
case ATMARP_SETENTRY: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (try_atm_clip_ops()) { |
error = atm_clip_ops->clip_setentry(vcc, arg); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
} else |
error = -ENOSYS; |
goto done; |
case ATMARP_ENCAP: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (try_atm_clip_ops()) { |
error = atm_clip_ops->clip_encap(vcc, arg); |
if (atm_clip_ops->owner) |
__MOD_DEC_USE_COUNT(atm_clip_ops->owner); |
} else |
error = -ENOSYS; |
goto done; |
#endif |
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) |
case ATMLEC_CTRL: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
#if defined(CONFIG_ATM_LANE_MODULE) |
if (!atm_lane_ops) |
request_module("lec"); |
#endif |
if (try_atm_lane_ops()) { |
error = atm_lane_ops->lecd_attach(vcc, (int) arg); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
if (error >= 0) |
sock->state = SS_CONNECTED; |
} else |
error = -ENOSYS; |
goto done; |
case ATMLEC_MCAST: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (try_atm_lane_ops()) { |
error = atm_lane_ops->mcast_attach(vcc, (int) arg); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
} else |
error = -ENOSYS; |
goto done; |
case ATMLEC_DATA: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (try_atm_lane_ops()) { |
error = atm_lane_ops->vcc_attach(vcc, (void *) arg); |
if (atm_lane_ops->owner) |
__MOD_DEC_USE_COUNT(atm_lane_ops->owner); |
} else |
error = -ENOSYS; |
goto done; |
#endif |
#if defined(CONFIG_ATM_MPOA) || defined(CONFIG_ATM_MPOA_MODULE) |
case ATMMPC_CTRL: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
#if defined(CONFIG_ATM_MPOA_MODULE) |
if (!atm_mpoa_ops) |
request_module("mpoa"); |
#endif |
if (try_atm_mpoa_ops()) { |
error = atm_mpoa_ops->mpoad_attach(vcc, (int) arg); |
if (atm_mpoa_ops->owner) |
__MOD_DEC_USE_COUNT(atm_mpoa_ops->owner); |
if (error >= 0) |
sock->state = SS_CONNECTED; |
} else |
error = -ENOSYS; |
goto done; |
case ATMMPC_DATA: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (try_atm_mpoa_ops()) { |
error = atm_mpoa_ops->vcc_attach(vcc, arg); |
if (atm_mpoa_ops->owner) |
__MOD_DEC_USE_COUNT(atm_mpoa_ops->owner); |
} else |
error = -ENOSYS; |
goto done; |
#endif |
#if defined(CONFIG_ATM_TCP) || defined(CONFIG_ATM_TCP_MODULE) |
case SIOCSIFATMTCP: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (!atm_tcp_ops.attach) { |
error = -ENOPKG; |
goto done; |
} |
fops_get(&atm_tcp_ops); |
error = atm_tcp_ops.attach(vcc, (int) arg); |
if (error >= 0) |
sock->state = SS_CONNECTED; |
else |
fops_put(&atm_tcp_ops); |
goto done; |
case ATMTCP_CREATE: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (!atm_tcp_ops.create_persistent) { |
error = -ENOPKG; |
goto done; |
} |
error = atm_tcp_ops.create_persistent((int) arg); |
if (error < 0) |
fops_put(&atm_tcp_ops); |
goto done; |
case ATMTCP_REMOVE: |
if (!capable(CAP_NET_ADMIN)) { |
error = -EPERM; |
goto done; |
} |
if (!atm_tcp_ops.remove_persistent) { |
error = -ENOPKG; |
goto done; |
} |
error = atm_tcp_ops.remove_persistent((int) arg); |
fops_put(&atm_tcp_ops); |
goto done; |
#endif |
default: |
break; |
} |
error = -ENOIOCTLCMD; |
#if defined(CONFIG_PPPOATM) || defined(CONFIG_PPPOATM_MODULE) |
down(&pppoatm_ioctl_mutex); |
if (pppoatm_ioctl_hook) |
error = pppoatm_ioctl_hook(vcc, cmd, arg); |
up(&pppoatm_ioctl_mutex); |
if (error != -ENOIOCTLCMD) |
goto done; |
#endif |
#if defined(CONFIG_ATM_BR2684) || defined(CONFIG_ATM_BR2684_MODULE) |
down(&br2684_ioctl_mutex); |
if (br2684_ioctl_hook) |
error = br2684_ioctl_hook(vcc, cmd, arg); |
up(&br2684_ioctl_mutex); |
if (error != -ENOIOCTLCMD) |
goto done; |
#endif |
|
error = atm_dev_ioctl(cmd, arg); |
|
done: |
return error; |
} |
|
|
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 (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 *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 *) 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 *optval, int *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 *) 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); |
} |
|
|
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) |
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
struct net_bridge_fdb_entry *(*br_fdb_get_hook)(struct net_bridge *br, |
unsigned char *addr) = NULL; |
void (*br_fdb_put_hook)(struct net_bridge_fdb_entry *ent) = NULL; |
#if defined(CONFIG_ATM_LANE_MODULE) || defined(CONFIG_BRIDGE_MODULE) |
EXPORT_SYMBOL(br_fdb_get_hook); |
EXPORT_SYMBOL(br_fdb_put_hook); |
#endif /* defined(CONFIG_ATM_LANE_MODULE) || defined(CONFIG_BRIDGE_MODULE) */ |
#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */ |
#endif /* defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) */ |
|
|
static int __init atm_init(void) |
{ |
int error; |
|
if ((error = atmpvc_init()) < 0) { |
printk(KERN_ERR "atmpvc_init() failed with %d\n", error); |
goto failure; |
} |
if ((error = atmsvc_init()) < 0) { |
printk(KERN_ERR "atmsvc_init() failed with %d\n", error); |
goto failure; |
} |
#ifdef CONFIG_PROC_FS |
if ((error = atm_proc_init()) < 0) { |
printk(KERN_ERR "atm_proc_init() failed with %d\n",error); |
goto failure; |
} |
#endif |
return 0; |
|
failure: |
atmsvc_exit(); |
atmpvc_exit(); |
return error; |
} |
|
static void __exit atm_exit(void) |
{ |
#ifdef CONFIG_PROC_FS |
atm_proc_exit(); |
#endif |
atmsvc_exit(); |
atmpvc_exit(); |
} |
|
module_init(atm_init); |
module_exit(atm_exit); |
|
MODULE_LICENSE("GPL"); |
/raw.c
0,0 → 1,93
/* net/atm/raw.c - Raw AAL0 and AAL5 transports */ |
|
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
|
|
#include <linux/module.h> |
#include <linux/sched.h> |
#include <linux/atmdev.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 |
*/ |
|
void atm_push_raw(struct atm_vcc *vcc,struct sk_buff *skb) |
{ |
if (skb) { |
skb_queue_tail(&vcc->sk->receive_queue,skb); |
wake_up(&vcc->sleep); |
} |
} |
|
|
static void atm_pop_raw(struct atm_vcc *vcc,struct sk_buff *skb) |
{ |
DPRINTK("APopR (%d) %d -= %d\n",vcc->vci,vcc->sk->wmem_alloc,skb->truesize); |
atomic_sub(skb->truesize, &vcc->sk->wmem_alloc); |
dev_kfree_skb_any(skb); |
wake_up(&vcc->sleep); |
} |
|
|
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); |
/protocols.h
0,0 → 1,16
/* 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 |
|
void atm_push_raw(struct atm_vcc *vcc,struct sk_buff *skb); |
|
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 */ |
int atm_init_atmarp(struct atm_vcc *vcc);/* ATM ARP */ |
|
#endif |
/svc.c
0,0 → 1,574
/* 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 net_proto, |
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. |
*/ |
|
|
void svc_callback(struct atm_vcc *vcc) |
{ |
wake_up(&vcc->sleep); |
} |
|
|
|
|
static int svc_shutdown(struct socket *sock,int how) |
{ |
return 0; |
} |
|
|
static void svc_disconnect(struct atm_vcc *vcc) |
{ |
DECLARE_WAITQUEUE(wait,current); |
struct sk_buff *skb; |
|
DPRINTK("svc_disconnect %p\n",vcc); |
if (test_bit(ATM_VF_REGIS,&vcc->flags)) { |
add_wait_queue(&vcc->sleep,&wait); |
sigd_enq(vcc,as_close,NULL,NULL,NULL); |
while (!test_bit(ATM_VF_RELEASED,&vcc->flags) && sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
schedule(); |
} |
remove_wait_queue(&vcc->sleep,&wait); |
} |
/* beware - socket is still in use by atmsigd until the last |
as_indicate has been answered */ |
while ((skb = skb_dequeue(&vcc->sk->receive_queue))) { |
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); |
clear_bit(ATM_VF_RELEASED,&vcc->flags); |
clear_bit(ATM_VF_CLOSE,&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 */ |
sock_hold(sk); |
vcc_release(sock); |
svc_disconnect(vcc); |
sock_put(sk); |
} |
return 0; |
} |
|
|
static int svc_bind(struct socket *sock,struct sockaddr *sockaddr, |
int sockaddr_len) |
{ |
DECLARE_WAITQUEUE(wait,current); |
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); |
if (test_bit(ATM_VF_SESSION, &vcc->flags)) { |
error = -EINVAL; |
goto out; |
} |
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; |
vcc->reply = WAITING; |
add_wait_queue(&vcc->sleep,&wait); |
sigd_enq(vcc,as_bind,NULL,NULL,&vcc->local); |
while (vcc->reply == WAITING && sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
schedule(); |
} |
remove_wait_queue(&vcc->sleep,&wait); |
clear_bit(ATM_VF_REGIS,&vcc->flags); /* doesn't count */ |
if (!sigd) { |
error = -EUNATCH; |
goto out; |
} |
if (!vcc->reply) |
set_bit(ATM_VF_BOUND,&vcc->flags); |
error = vcc->reply; |
out: |
release_sock(sk); |
return error; |
} |
|
|
static int svc_connect(struct socket *sock,struct sockaddr *sockaddr, |
int sockaddr_len,int flags) |
{ |
DECLARE_WAITQUEUE(wait,current); |
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 (vcc->reply == WAITING) { |
error = -EALREADY; |
goto out; |
} |
sock->state = SS_UNCONNECTED; |
if (vcc->reply) { |
error = vcc->reply; |
goto out; |
} |
break; |
case SS_UNCONNECTED: |
if (test_bit(ATM_VF_SESSION, &vcc->flags)) { |
error = -EINVAL; |
goto out; |
} |
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; |
vcc->reply = WAITING; |
add_wait_queue(&vcc->sleep,&wait); |
sigd_enq(vcc,as_connect,NULL,NULL,&vcc->remote); |
if (flags & O_NONBLOCK) { |
remove_wait_queue(&vcc->sleep,&wait); |
sock->state = SS_CONNECTING; |
error = -EINPROGRESS; |
goto out; |
} |
error = 0; |
while (vcc->reply == WAITING && sigd) { |
set_current_state(TASK_INTERRUPTIBLE); |
schedule(); |
if (!signal_pending(current)) 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 (vcc->reply == WAITING && sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
schedule(); |
} |
if (!vcc->reply) |
while (!test_bit(ATM_VF_RELEASED,&vcc->flags) |
&& sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
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; |
} |
remove_wait_queue(&vcc->sleep,&wait); |
if (error) |
goto out; |
if (!sigd) { |
error = -EUNATCH; |
goto out; |
} |
if (vcc->reply) { |
error = vcc->reply; |
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) |
{ |
DECLARE_WAITQUEUE(wait,current); |
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->reply = WAITING; |
add_wait_queue(&vcc->sleep,&wait); |
sigd_enq(vcc,as_listen,NULL,NULL,&vcc->local); |
while (vcc->reply == WAITING && sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
schedule(); |
} |
remove_wait_queue(&vcc->sleep,&wait); |
if (!sigd) { |
error = -EUNATCH; |
goto out; |
} |
set_bit(ATM_VF_LISTEN,&vcc->flags); |
vcc->sk->max_ack_backlog = backlog > 0 ? backlog : ATM_BACKLOG_DEFAULT; |
error = vcc->reply; |
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) { |
DECLARE_WAITQUEUE(wait,current); |
|
add_wait_queue(&old_vcc->sleep,&wait); |
while (!(skb = skb_dequeue(&old_vcc->sk->receive_queue)) && sigd) { |
if (test_bit(ATM_VF_RELEASED,&old_vcc->flags)) break; |
if (test_bit(ATM_VF_CLOSE,&old_vcc->flags)) { |
error = old_vcc->reply; |
break; |
} |
if (flags & O_NONBLOCK) { |
error = -EAGAIN; |
break; |
} |
release_sock(sk); |
schedule(); |
lock_sock(sk); |
if (signal_pending(current)) { |
error = -ERESTARTSYS; |
break; |
} |
} |
remove_wait_queue(&old_vcc->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); |
old_vcc->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 */ |
new_vcc->reply = WAITING; |
add_wait_queue(&new_vcc->sleep,&wait); |
sigd_enq(new_vcc,as_accept,old_vcc,NULL,NULL); |
while (new_vcc->reply == WAITING && sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
release_sock(sk); |
schedule(); |
lock_sock(sk); |
} |
remove_wait_queue(&new_vcc->sleep,&wait); |
if (!sigd) { |
error = -EUNATCH; |
goto out; |
} |
if (!new_vcc->reply) break; |
if (new_vcc->reply != -ERESTARTSYS) { |
error = new_vcc->reply; |
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) |
{ |
DECLARE_WAITQUEUE(wait,current); |
|
vcc->reply = WAITING; |
add_wait_queue(&vcc->sleep,&wait); |
sigd_enq2(vcc,as_modify,NULL,NULL,&vcc->local,qos,0); |
while (vcc->reply == WAITING && !test_bit(ATM_VF_RELEASED,&vcc->flags) |
&& sigd) { |
set_current_state(TASK_UNINTERRUPTIBLE); |
schedule(); |
} |
remove_wait_queue(&vcc->sleep,&wait); |
if (!sigd) return -EUNATCH; |
return vcc->reply; |
} |
|
|
static int svc_setsockopt(struct socket *sock,int level,int optname, |
char *optval,int optlen) |
{ |
struct sock *sk = sock->sk; |
struct atm_vcc *vcc; |
int error = 0; |
|
if (!__SO_LEVEL_MATCH(optname, level) || optname != SO_ATMSAP || |
optlen != sizeof(struct atm_sap)) { |
error = vcc_setsockopt(sock, level, optname, optval, optlen); |
goto out; |
} |
vcc = ATM_SD(sock); |
if (copy_from_user(&vcc->sap, optval, optlen)) { |
error = -EFAULT; |
goto out; |
} |
set_bit(ATM_VF_HASSAP, &vcc->flags); |
out: |
release_sock(sk); |
return error; |
} |
|
|
static int svc_getsockopt(struct socket *sock,int level,int optname, |
char *optval,int *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 struct proto_ops svc_proto_ops = { |
.family = PF_ATMSVC, |
|
.release = svc_release, |
.bind = svc_bind, |
.connect = svc_connect, |
.socketpair = sock_no_socketpair, |
.accept = svc_accept, |
.getname = svc_getname, |
.poll = atm_poll, |
.ioctl = vcc_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)->callback = svc_callback; |
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 = { |
PF_ATMSVC, |
svc_create, |
0, /* no authentication */ |
0, /* no encryption */ |
0 /* no encrypt_net */ |
}; |
|
|
/* |
* Initialize the ATM SVC protocol family |
*/ |
|
int atmsvc_init(void) |
{ |
return sock_register(&svc_family_ops); |
} |
|
void atmsvc_exit(void) |
{ |
sock_unregister(PF_ATMSVC); |
} |
/atm_misc.c
0,0 → 1,173
/* 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(&vcc->sk->rmem_alloc) <= 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, |
int gfp_flags) |
{ |
int guess = atm_guess_pdu2truesize(pdu_size); |
|
atm_force_charge(vcc,guess); |
if (atomic_read(&vcc->sk->rmem_alloc) <= vcc->sk->rcvbuf) { |
struct sk_buff *skb = alloc_skb(pdu_size,gfp_flags); |
|
if (skb) { |
atomic_add(skb->truesize-guess,&vcc->sk->rmem_alloc); |
return skb; |
} |
} |
atm_return(vcc,guess); |
atomic_inc(&vcc->stats->rx_drop); |
return NULL; |
} |
|
|
static int check_ci(struct atm_vcc *vcc,short vpi,int vci) |
{ |
struct sock *s; |
struct atm_vcc *walk; |
|
for (s = vcc_sklist; s; s = s->next) { |
walk = s->protinfo.af_atm; |
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; |
} |
|
|
int atm_find_ci(struct atm_vcc *vcc,short *vpi,int *vci) |
{ |
static short p = 0; /* poor man's per-device cache */ |
static int c = 0; |
short old_p; |
int old_c; |
int err; |
|
read_lock(&vcc_sklist_lock); |
if (*vpi != ATM_VPI_ANY && *vci != ATM_VCI_ANY) { |
err = check_ci(vcc,*vpi,*vci); |
read_unlock(&vcc_sklist_lock); |
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; |
read_unlock(&vcc_sklist_lock); |
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); |
read_unlock(&vcc_sklist_lock); |
return -EADDRINUSE; |
} |
|
|
/* |
* 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(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_find_ci); |
EXPORT_SYMBOL(atm_pcr_goal); |
EXPORT_SYMBOL(sonet_copy_stats); |
EXPORT_SYMBOL(sonet_subtract_stats); |
/Makefile
0,0 → 1,53
# |
# Makefile for the ATM Protocol Families. |
# |
# Note! Dependencies are done automagically by 'make dep', which also |
# removes any old dependencies. DON'T put your own dependencies here |
# unless it's something special (ie not a .c file). |
# |
# Note 2! The CFLAGS definition is now in the main makefile... |
|
O_TARGET := atm.o |
|
export-objs := common.o atm_misc.o raw.o resources.o ipcommon.o proc.o |
|
list-multi := mpoa.o |
mpoa-objs := mpc.o mpoa_caches.o mpoa_proc.o |
|
obj-y := addr.o pvc.o signaling.o svc.o common.o atm_misc.o raw.o resources.o |
ifeq ($(CONFIG_ATM),m) |
obj-m += $(O_TARGET) |
endif |
|
ifneq ($(CONFIG_ATM_CLIP),n) |
NEED_IPCOM = ipcommon.o |
endif |
obj-$(CONFIG_ATM_CLIP) += clip.o |
|
ifeq ($(CONFIG_ATM_BR2684),y) |
NEED_IPCOM = ipcommon.o |
else |
ifeq ($(CONFIG_ATM_BR2684),m) |
NEED_IPCOM = ipcommon.o |
endif |
endif |
obj-$(CONFIG_ATM_BR2684) += br2684.o |
|
ifeq ($(CONFIG_NET_SCH_ATM),y) |
NEED_IPCOM = ipcommon.o |
endif |
|
obj-y += $(NEED_IPCOM) |
|
ifeq ($(CONFIG_PROC_FS),y) |
obj-y += proc.o |
endif |
|
obj-$(CONFIG_ATM_LANE) += lec.o |
obj-$(CONFIG_ATM_MPOA) += mpoa.o |
obj-$(CONFIG_PPPOATM) += pppoatm.o |
|
include $(TOPDIR)/Rules.make |
|
mpoa.o: $(mpoa-objs) |
$(LD) -r -o mpoa.o $(mpoa-objs) |
/common.h
0,0 → 1,48
/* 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 socket *sock, struct msghdr *msg, |
int size, int flags, struct scm_cookie *scm); |
int vcc_sendmsg(struct socket *sock, struct msghdr *m, int total_len, |
struct scm_cookie *scm); |
unsigned int atm_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 *optval, |
int optlen); |
int vcc_getsockopt(struct socket *sock, int level, int optname, char *optval, |
int *optlen); |
|
void atm_shutdown_dev(struct atm_dev *dev); |
|
void pppoatm_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long)); |
void br2684_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long)); |
|
int atmpvc_init(void); |
void atmpvc_exit(void); |
int atmsvc_init(void); |
void atmsvc_exit(void); |
int atm_proc_init(void); |
void atm_proc_exit(void); |
|
/* SVC */ |
|
void svc_callback(struct atm_vcc *vcc); |
int svc_change_qos(struct atm_vcc *vcc,struct atm_qos *qos); |
|
/* p2mp */ |
|
int create_leaf(struct socket *leaf,struct socket *session); |
|
#endif |