mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-30 13:38:40 +00:00
5a0e3ad6af
percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
1572 lines
38 KiB
C
1572 lines
38 KiB
C
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/init.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/capability.h>
|
|
#include <linux/seq_file.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 <linux/uaccess.h>
|
|
#include <asm/byteorder.h>
|
|
#include <net/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/module.h>
|
|
|
|
#include "lec.h"
|
|
#include "mpc.h"
|
|
#include "resources.h"
|
|
|
|
/*
|
|
* mpc.c: Implementation of MPOA client kernel part
|
|
*/
|
|
|
|
#if 0
|
|
#define dprintk(format, args...) \
|
|
printk(KERN_DEBUG "mpoa:%s: " format, __func__, ##args)
|
|
#define dprintk_cont(format, args...) printk(KERN_CONT format, ##args)
|
|
#else
|
|
#define dprintk(format, args...) \
|
|
do { if (0) \
|
|
printk(KERN_DEBUG "mpoa:%s: " format, __func__, ##args);\
|
|
} while (0)
|
|
#define dprintk_cont(format, args...) \
|
|
do { if (0) printk(KERN_CONT format, ##args); } while (0)
|
|
#endif
|
|
|
|
#if 0
|
|
#define ddprintk(format, args...) \
|
|
printk(KERN_DEBUG "mpoa:%s: " format, __func__, ##args)
|
|
#define ddprintk_cont(format, args...) printk(KERN_CONT format, ##args)
|
|
#else
|
|
#define ddprintk(format, args...) \
|
|
do { if (0) \
|
|
printk(KERN_DEBUG "mpoa:%s: " format, __func__, ##args);\
|
|
} while (0)
|
|
#define ddprintk_cont(format, args...) \
|
|
do { if (0) printk(KERN_CONT format, ##args); } while (0)
|
|
#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 const uint8_t *copy_macs(struct mpoa_client *mpc,
|
|
const uint8_t *router_mac,
|
|
const 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(const 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 netdev_tx_t 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
|
|
};
|
|
|
|
struct mpoa_client *mpcs = NULL; /* FIXME */
|
|
static struct atm_mpoa_qos *qos_head = NULL;
|
|
static DEFINE_TIMER(mpc_timer, NULL, 0, 0);
|
|
|
|
|
|
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(__be32 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) {
|
|
pr_info("mpoa: 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(__be32 dst_ip)
|
|
{
|
|
struct atm_mpoa_qos *qos;
|
|
|
|
qos = qos_head;
|
|
while (qos) {
|
|
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;
|
|
}
|
|
|
|
/* this is buggered - we need locking for qos_head */
|
|
void atm_mpoa_disp_qos(struct seq_file *m)
|
|
{
|
|
struct atm_mpoa_qos *qos;
|
|
|
|
qos = qos_head;
|
|
seq_printf(m, "QoS entries for shortcuts:\n");
|
|
seq_printf(m, "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");
|
|
|
|
while (qos != NULL) {
|
|
seq_printf(m, "%pI4\n %-7d %-7d %-7d %-7d %-7d\n %-7d %-7d %-7d %-7d %-7d\n",
|
|
&qos->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;
|
|
}
|
|
}
|
|
|
|
static struct net_device *find_lec_by_itfnum(int itf)
|
|
{
|
|
struct net_device *dev;
|
|
char name[IFNAMSIZ];
|
|
|
|
sprintf(name, "lec%d", itf);
|
|
dev = dev_get_by_name(&init_net, name);
|
|
|
|
return dev;
|
|
}
|
|
|
|
static struct mpoa_client *alloc_mpc(void)
|
|
{
|
|
struct mpoa_client *mpc;
|
|
|
|
mpc = kzalloc(sizeof(struct mpoa_client), GFP_KERNEL);
|
|
if (mpc == NULL)
|
|
return NULL;
|
|
rwlock_init(&mpc->ingress_lock);
|
|
rwlock_init(&mpc->egress_lock);
|
|
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("(%s)\n", mpc->dev->name);
|
|
if (!dev->netdev_ops)
|
|
pr_info("(%s) not starting\n", dev->name);
|
|
else {
|
|
mpc->old_ops = dev->netdev_ops;
|
|
mpc->new_ops = *mpc->old_ops;
|
|
mpc->new_ops.ndo_start_xmit = mpc_send_packet;
|
|
dev->netdev_ops = &mpc->new_ops;
|
|
}
|
|
}
|
|
|
|
static void stop_mpc(struct mpoa_client *mpc)
|
|
{
|
|
struct net_device *dev = mpc->dev;
|
|
dprintk("(%s)", mpc->dev->name);
|
|
|
|
/* Lets not nullify lec device's dev->hard_start_xmit */
|
|
if (dev->netdev_ops != &mpc->new_ops) {
|
|
dprintk_cont(" mpc already stopped, not fatal\n");
|
|
return;
|
|
}
|
|
dprintk_cont("\n");
|
|
|
|
dev->netdev_ops = mpc->old_ops;
|
|
mpc->old_ops = NULL;
|
|
|
|
/* close_shortcuts(mpc); ??? FIXME */
|
|
}
|
|
|
|
static const char *mpoa_device_type_string(char type) __attribute__ ((unused));
|
|
|
|
static const char *mpoa_device_type_string(char type)
|
|
{
|
|
switch (type) {
|
|
case NON_MPOA:
|
|
return "non-MPOA device";
|
|
case MPS:
|
|
return "MPS";
|
|
case MPC:
|
|
return "MPC";
|
|
case MPS_AND_MPC:
|
|
return "both MPS and MPC";
|
|
}
|
|
|
|
return "unspecified (non-MPOA) device";
|
|
}
|
|
|
|
/*
|
|
* lec device calls this via its netdev_priv(dev)->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, const u8 *mac_addr,
|
|
const u8 *tlvs, u32 sizeoftlvs)
|
|
{
|
|
uint32_t type;
|
|
uint8_t length, mpoa_device_type, number_of_mps_macs;
|
|
const uint8_t *end_of_tlvs;
|
|
struct mpoa_client *mpc;
|
|
|
|
mpoa_device_type = number_of_mps_macs = 0; /* silence gcc */
|
|
dprintk("(%s) 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) {
|
|
pr_info("(%s) 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) {
|
|
pr_info("TLV value extends past its buffer, aborting parse\n");
|
|
return;
|
|
}
|
|
|
|
if (type == 0) {
|
|
pr_info("mpoa: (%s) 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("(%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)) { /* :) */
|
|
pr_info("(%s) 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) {
|
|
pr_info("(%s) 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 ");
|
|
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) {
|
|
pr_info("(%s) MPS_AND_MPC has zero MACs\n", dev->name);
|
|
continue; /* someone should read the spec */
|
|
}
|
|
dprintk_cont("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)
|
|
pr_info("(%s) ignoring %Zd bytes of trailing TLV garbage\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 const uint8_t *copy_macs(struct mpoa_client *mpc,
|
|
const uint8_t *router_mac,
|
|
const 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) {
|
|
pr_info("(%s) 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;
|
|
__be32 ipaddr = 0;
|
|
|
|
static struct {
|
|
struct llc_snap_hdr hdr;
|
|
__be32 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("(%s) 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;
|
|
}
|
|
/* threshold not exceeded or VCC not ready */
|
|
if (mpc->in_ops->cache_hit(entry, mpc) != OPEN) {
|
|
ddprintk("(%s) cache_hit: returns != OPEN\n",
|
|
mpc->dev->name);
|
|
mpc->in_ops->put(entry);
|
|
return 1;
|
|
}
|
|
|
|
ddprintk("(%s) 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("(%s) 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("(%s) 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 */
|
|
skb_copy_to_linear_data(skb, &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 */
|
|
skb_copy_to_linear_data(skb, &llc_snap_mpoa_data,
|
|
sizeof(struct llc_snap_hdr));
|
|
}
|
|
|
|
atomic_add(skb->truesize, &sk_atm(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 netdev_tx_t mpc_send_packet(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
{
|
|
struct mpoa_client *mpc;
|
|
struct ethhdr *eth;
|
|
int i = 0;
|
|
|
|
mpc = find_mpc_by_lec(dev); /* this should NEVER fail */
|
|
if (mpc == NULL) {
|
|
pr_info("(%s) 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 :-) */
|
|
|
|
/* Weed out funny packets (e.g., AF_PACKET or raw). */
|
|
if (skb->len < ETH_HLEN + sizeof(struct iphdr))
|
|
goto non_ip;
|
|
skb_set_network_header(skb, ETH_HLEN);
|
|
if (skb->len < ETH_HLEN + ip_hdr(skb)->ihl * 4 || ip_hdr(skb)->ihl < 5)
|
|
goto non_ip;
|
|
|
|
while (i < mpc->number_of_mps_macs) {
|
|
if (!compare_ether_addr(eth->h_dest,
|
|
(mpc->mps_macs + i*ETH_ALEN)))
|
|
if (send_via_shortcut(skb, mpc) == 0) /* try shortcut */
|
|
return NETDEV_TX_OK;
|
|
i++;
|
|
}
|
|
|
|
non_ip:
|
|
return mpc->old_ops->ndo_start_xmit(skb, dev);
|
|
}
|
|
|
|
static int atm_mpoa_vcc_attach(struct atm_vcc *vcc, void __user *arg)
|
|
{
|
|
int bytes_left;
|
|
struct mpoa_client *mpc;
|
|
struct atmmpc_ioc ioc_data;
|
|
in_cache_entry *in_entry;
|
|
__be32 ipaddr;
|
|
|
|
bytes_left = copy_from_user(&ioc_data, arg, sizeof(struct atmmpc_ioc));
|
|
if (bytes_left != 0) {
|
|
pr_info("mpoa: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) {
|
|
pr_info("(%s) did not find RESOLVED entry from ingress cache\n",
|
|
mpc->dev->name);
|
|
if (in_entry != NULL)
|
|
mpc->in_ops->put(in_entry);
|
|
return -EINVAL;
|
|
}
|
|
pr_info("(%s) attaching ingress SVC, entry = %pI4\n",
|
|
mpc->dev->name, &in_entry->ctrl_info.in_dst_ip);
|
|
in_entry->shortcut = vcc;
|
|
mpc->in_ops->put(in_entry);
|
|
} else {
|
|
pr_info("(%s) 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) {
|
|
pr_info("(%s) close for unknown MPC\n", dev->name);
|
|
return;
|
|
}
|
|
|
|
dprintk("(%s)\n", dev->name);
|
|
in_entry = mpc->in_ops->get_by_vcc(vcc, mpc);
|
|
if (in_entry) {
|
|
dprintk("(%s) ingress SVC closed ip = %pI4\n",
|
|
mpc->dev->name, &in_entry->ctrl_info.in_dst_ip);
|
|
in_entry->shortcut = NULL;
|
|
mpc->in_ops->put(in_entry);
|
|
}
|
|
eg_entry = mpc->eg_ops->get_by_vcc(vcc, mpc);
|
|
if (eg_entry) {
|
|
dprintk("(%s) 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("(%s) 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;
|
|
__be32 tag;
|
|
char *tmp;
|
|
|
|
ddprintk("(%s)\n", dev->name);
|
|
if (skb == NULL) {
|
|
dprintk("(%s) 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) {
|
|
struct sock *sk = sk_atm(vcc);
|
|
|
|
dprintk("(%s) control packet arrived\n", dev->name);
|
|
/* Pass control packets to daemon */
|
|
skb_queue_tail(&sk->sk_receive_queue, skb);
|
|
sk->sk_data_ready(sk, skb->len);
|
|
return;
|
|
}
|
|
|
|
/* data coming over the shortcut */
|
|
atm_return(vcc, skb->truesize);
|
|
|
|
mpc = find_mpc_by_lec(dev);
|
|
if (mpc == NULL) {
|
|
pr_info("(%s) 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("(%s) tagged data packet arrived\n", dev->name);
|
|
|
|
} else if (memcmp(skb->data, &llc_snap_mpoa_data,
|
|
sizeof(struct llc_snap_hdr)) == 0) { /* MPOA data */
|
|
pr_info("(%s) Unsupported non-tagged data packet arrived. Purging\n",
|
|
dev->name);
|
|
dev_kfree_skb_any(skb);
|
|
return;
|
|
} else {
|
|
pr_info("(%s) garbage arrived, purging\n", dev->name);
|
|
dev_kfree_skb_any(skb);
|
|
return;
|
|
}
|
|
|
|
tmp = skb->data + sizeof(struct llc_snap_hdr);
|
|
tag = *(__be32 *)tmp;
|
|
|
|
eg = mpc->eg_ops->get_by_tag(tag, mpc);
|
|
if (eg == NULL) {
|
|
pr_info("mpoa: (%s) 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;
|
|
pr_info("(%s) 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 */
|
|
skb_copy_to_linear_data(new_skb, eg->ctrl_info.DLL_header,
|
|
eg->ctrl_info.DH_length);
|
|
new_skb->protocol = eth_type_trans(new_skb, dev);
|
|
skb_reset_network_header(new_skb);
|
|
|
|
eg->latest_ip_addr = ip_hdr(new_skb)->saddr;
|
|
eg->packets_rcvd++;
|
|
mpc->eg_ops->put(eg);
|
|
|
|
memset(ATM_SKB(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(mpc_dev.lock)
|
|
/* members not explicitly initialised will be 0 */
|
|
};
|
|
|
|
static int atm_mpoa_mpoad_attach(struct atm_vcc *vcc, int arg)
|
|
{
|
|
struct mpoa_client *mpc;
|
|
struct lec_priv *priv;
|
|
int err;
|
|
|
|
if (mpcs == NULL) {
|
|
init_timer(&mpc_timer);
|
|
mpc_timer_refresh();
|
|
|
|
/* This lets us now how our LECs are doing */
|
|
err = register_netdevice_notifier(&mpoa_notifier);
|
|
if (err < 0) {
|
|
del_timer(&mpc_timer);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
mpc = find_mpc_by_itfnum(arg);
|
|
if (mpc == NULL) {
|
|
dprintk("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) {
|
|
pr_info("mpoad is already present for itf %d\n", arg);
|
|
return -EADDRINUSE;
|
|
}
|
|
|
|
if (mpc->dev) { /* check if the lec is LANE2 capable */
|
|
priv = netdev_priv(mpc->dev);
|
|
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(sk_atm(vcc));
|
|
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);
|
|
}
|
|
|
|
__module_get(THIS_MODULE);
|
|
return arg;
|
|
}
|
|
|
|
static void send_set_mps_ctrl_addr(const 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) {
|
|
pr_info("did not find MPC\n");
|
|
return;
|
|
}
|
|
if (!mpc->mpoad_vcc) {
|
|
pr_info("close for non-present mpoad\n");
|
|
return;
|
|
}
|
|
|
|
mpc->mpoad_vcc = NULL;
|
|
if (mpc->dev) {
|
|
struct lec_priv *priv = netdev_priv(mpc->dev);
|
|
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(&sk_atm(vcc)->sk_receive_queue))) {
|
|
atm_return(vcc, skb->truesize);
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
pr_info("(%s) going down\n",
|
|
(mpc->dev) ? mpc->dev->name : "<unknown>");
|
|
module_put(THIS_MODULE);
|
|
|
|
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, &sk_atm(vcc)->sk_wmem_alloc);
|
|
|
|
if (mpc == NULL) {
|
|
pr_info("no mpc found\n");
|
|
return 0;
|
|
}
|
|
dprintk("(%s)", mpc->dev ? mpc->dev->name : "<unknown>");
|
|
switch (mesg->type) {
|
|
case MPOA_RES_REPLY_RCVD:
|
|
dprintk_cont("mpoa_res_reply_rcvd\n");
|
|
MPOA_res_reply_rcvd(mesg, mpc);
|
|
break;
|
|
case MPOA_TRIGGER_RCVD:
|
|
dprintk_cont("mpoa_trigger_rcvd\n");
|
|
MPOA_trigger_rcvd(mesg, mpc);
|
|
break;
|
|
case INGRESS_PURGE_RCVD:
|
|
dprintk_cont("nhrp_purge_rcvd\n");
|
|
ingress_purge_rcvd(mesg, mpc);
|
|
break;
|
|
case EGRESS_PURGE_RCVD:
|
|
dprintk_cont("egress_purge_reply_rcvd\n");
|
|
egress_purge_rcvd(mesg, mpc);
|
|
break;
|
|
case MPS_DEATH:
|
|
dprintk_cont("mps_death\n");
|
|
mps_death(mesg, mpc);
|
|
break;
|
|
case CACHE_IMPOS_RCVD:
|
|
dprintk_cont("cache_impos_rcvd\n");
|
|
MPOA_cache_impos_rcvd(mesg, mpc);
|
|
break;
|
|
case SET_MPC_CTRL_ADDR:
|
|
dprintk_cont("set_mpc_ctrl_addr\n");
|
|
set_mpc_ctrl_addr_rcvd(mesg, mpc);
|
|
break;
|
|
case SET_MPS_MAC_ADDR:
|
|
dprintk_cont("set_mps_mac_addr\n");
|
|
set_mps_mac_addr_rcvd(mesg, mpc);
|
|
break;
|
|
case CLEAN_UP_AND_EXIT:
|
|
dprintk_cont("clean_up_and_exit\n");
|
|
clean_up(mesg, mpc, DIE);
|
|
break;
|
|
case RELOAD:
|
|
dprintk_cont("reload\n");
|
|
clean_up(mesg, mpc, RELOAD);
|
|
break;
|
|
case SET_MPC_PARAMS:
|
|
dprintk_cont("set_mpc_params\n");
|
|
mpc->parameters = mesg->content.params;
|
|
break;
|
|
default:
|
|
dprintk_cont("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;
|
|
struct sock *sk;
|
|
|
|
if (mpc == NULL || !mpc->mpoad_vcc) {
|
|
pr_info("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));
|
|
skb_copy_to_linear_data(skb, mesg, sizeof(*mesg));
|
|
atm_force_charge(mpc->mpoad_vcc, skb->truesize);
|
|
|
|
sk = sk_atm(mpc->mpoad_vcc);
|
|
skb_queue_tail(&sk->sk_receive_queue, skb);
|
|
sk->sk_data_ready(sk, skb->len);
|
|
|
|
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 (!net_eq(dev_net(dev), &init_net))
|
|
return NOTIFY_DONE;
|
|
|
|
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 = netdev_priv(dev);
|
|
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("allocating new mpc for %s\n", dev->name);
|
|
mpc = alloc_mpc();
|
|
if (mpc == NULL) {
|
|
pr_info("no new mpc");
|
|
break;
|
|
}
|
|
}
|
|
mpc->dev_num = priv->itfnum;
|
|
mpc->dev = dev;
|
|
dev_hold(dev);
|
|
dprintk("(%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("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)
|
|
{
|
|
__be32 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;
|
|
}
|
|
|
|
pr_info("(%s) 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)
|
|
{
|
|
__be32 dst_ip = msg->content.in_info.in_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("(%s) using egress SVC to reach %pI4\n",
|
|
client->dev->name, &dst_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;
|
|
pr_info("(%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)
|
|
{
|
|
__be32 dst_ip = msg->content.in_info.in_dst_ip;
|
|
in_cache_entry *entry = mpc->in_ops->get(dst_ip, mpc);
|
|
|
|
dprintk("(%s) ip %pI4\n",
|
|
mpc->dev->name, &dst_ip);
|
|
ddprintk("(%s) entry = %p",
|
|
mpc->dev->name, entry);
|
|
if (entry == NULL) {
|
|
pr_info("(%s) ARGH, received res. reply for an entry that doesn't exist.\n",
|
|
mpc->dev->name);
|
|
return;
|
|
}
|
|
ddprintk_cont(" entry_state = %d ", entry->entry_state);
|
|
|
|
if (entry->entry_state == INGRESS_RESOLVED) {
|
|
pr_info("(%s) 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_cont("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) {
|
|
pr_info("(%s) 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)
|
|
{
|
|
__be32 dst_ip = msg->content.in_info.in_dst_ip;
|
|
__be32 mask = msg->ip_mask;
|
|
in_cache_entry *entry = mpc->in_ops->get_with_mask(dst_ip, mpc, mask);
|
|
|
|
if (entry == NULL) {
|
|
pr_info("(%s) purge for a non-existing entry, ip = %pI4\n",
|
|
mpc->dev->name, &dst_ip);
|
|
return;
|
|
}
|
|
|
|
do {
|
|
dprintk("(%s) removing an ingress entry, ip = %pI4\n",
|
|
mpc->dev->name, &dst_ip);
|
|
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)
|
|
{
|
|
__be32 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("(%s) 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 sock *sk;
|
|
struct k_message *purge_msg;
|
|
struct sk_buff *skb;
|
|
|
|
dprintk("entering\n");
|
|
if (vcc == NULL) {
|
|
pr_info("vcc == NULL\n");
|
|
return;
|
|
}
|
|
|
|
skb = alloc_skb(sizeof(struct k_message), GFP_ATOMIC);
|
|
if (skb == NULL) {
|
|
pr_info("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);
|
|
|
|
sk = sk_atm(vcc);
|
|
skb_queue_tail(&sk->sk_receive_queue, skb);
|
|
sk->sk_data_ready(sk, skb->len);
|
|
dprintk("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("(%s)\n", mpc->dev->name);
|
|
|
|
if (memcmp(msg->MPS_ctrl, mpc->mps_ctrl_addr, ATM_ESA_LEN)) {
|
|
pr_info("(%s) 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("(%s) 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("(%s) setting MPC ctrl ATM address to",
|
|
mpc->dev ? mpc->dev->name : "<unknown>");
|
|
for (i = 7; i < sizeof(tlv); i++)
|
|
dprintk_cont(" %02x", tlv[i]);
|
|
dprintk_cont("\n");
|
|
|
|
if (mpc->dev) {
|
|
priv = netdev_priv(mpc->dev);
|
|
retval = priv->lane2_ops->associate_req(mpc->dev,
|
|
mpc->dev->dev_addr,
|
|
tlv, sizeof(tlv));
|
|
if (retval == 0)
|
|
pr_info("(%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)
|
|
pr_info("(%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 = kmemdup(msg->MPS_ctrl, ETH_ALEN, GFP_KERNEL);
|
|
if (client->mps_macs == NULL) {
|
|
pr_info("out of memory\n");
|
|
return;
|
|
}
|
|
client->number_of_mps_macs = 1;
|
|
|
|
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("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(void)
|
|
{
|
|
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;
|
|
static unsigned long previous_refresh_time;
|
|
|
|
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 int atm_mpoa_ioctl(struct socket *sock, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int err = 0;
|
|
struct atm_vcc *vcc = ATM_SD(sock);
|
|
|
|
if (cmd != ATMMPC_CTRL && cmd != ATMMPC_DATA)
|
|
return -ENOIOCTLCMD;
|
|
|
|
if (!capable(CAP_NET_ADMIN))
|
|
return -EPERM;
|
|
|
|
switch (cmd) {
|
|
case ATMMPC_CTRL:
|
|
err = atm_mpoa_mpoad_attach(vcc, (int)arg);
|
|
if (err >= 0)
|
|
sock->state = SS_CONNECTED;
|
|
break;
|
|
case ATMMPC_DATA:
|
|
err = atm_mpoa_vcc_attach(vcc, (void __user *)arg);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static struct atm_ioctl atm_ioctl_ops = {
|
|
.owner = THIS_MODULE,
|
|
.ioctl = atm_mpoa_ioctl,
|
|
};
|
|
|
|
static __init int atm_mpoa_init(void)
|
|
{
|
|
register_atm_ioctl(&atm_ioctl_ops);
|
|
|
|
if (mpc_proc_init() != 0)
|
|
pr_info("failed to initialize /proc/mpoa\n");
|
|
|
|
pr_info("mpc.c: " __DATE__ " " __TIME__ " initialized\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit atm_mpoa_cleanup(void)
|
|
{
|
|
struct mpoa_client *mpc, *tmp;
|
|
struct atm_mpoa_qos *qos, *nextqos;
|
|
struct lec_priv *priv;
|
|
|
|
mpc_proc_clean();
|
|
|
|
del_timer(&mpc_timer);
|
|
unregister_netdevice_notifier(&mpoa_notifier);
|
|
deregister_atm_ioctl(&atm_ioctl_ops);
|
|
|
|
mpc = mpcs;
|
|
mpcs = NULL;
|
|
while (mpc != NULL) {
|
|
tmp = mpc->next;
|
|
if (mpc->dev != NULL) {
|
|
stop_mpc(mpc);
|
|
priv = netdev_priv(mpc->dev);
|
|
if (priv->lane2_ops != NULL)
|
|
priv->lane2_ops->associate_indicator = NULL;
|
|
}
|
|
ddprintk("about to clear caches\n");
|
|
mpc->in_ops->destroy_cache(mpc);
|
|
mpc->eg_ops->destroy_cache(mpc);
|
|
ddprintk("caches cleared\n");
|
|
kfree(mpc->mps_macs);
|
|
memset(mpc, 0, sizeof(struct mpoa_client));
|
|
ddprintk("about to kfree %p\n", mpc);
|
|
kfree(mpc);
|
|
ddprintk("next mpc is at %p\n", tmp);
|
|
mpc = tmp;
|
|
}
|
|
|
|
qos = qos_head;
|
|
qos_head = NULL;
|
|
while (qos != NULL) {
|
|
nextqos = qos->next;
|
|
dprintk("freeing qos entry %p\n", qos);
|
|
kfree(qos);
|
|
qos = nextqos;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
module_init(atm_mpoa_init);
|
|
module_exit(atm_mpoa_cleanup);
|
|
|
|
MODULE_LICENSE("GPL");
|