/*
* gw.c - CAN frame Gateway/Router/Bridge with netlink interface
*
- * Copyright (c) 2002-2010 Volkswagen Group Electronic Research
+ * Copyright (c) 2011 Volkswagen Group Electronic Research
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
#include <socketcan/can/gw.h>
#include <net/rtnetlink.h>
#include <net/net_namespace.h>
+#include <net/sock.h>
#include <socketcan/can/version.h> /* for RCSID. Removed by mkpatch script */
RCSID("$Id$");
-#define CAN_GW_VERSION "20100410"
+#define CAN_GW_VERSION "20101209"
static __initdata const char banner[] =
KERN_INFO "can: netlink gateway (rev " CAN_GW_VERSION ")\n";
MODULE_ALIAS("can-gw");
HLIST_HEAD(cgw_list);
-static DEFINE_SPINLOCK(cgw_list_lock);
static struct notifier_block notifier;
static struct kmem_cache *cgw_cache __read_mostly;
-#define CGW_SK_MAGIC ((void *)(¬ifier))
-#define CGW_CS_DISABLED 42
-
/* structure that contains the (on-the-fly) CAN frame modifications */
struct cf_mod {
struct {
struct cgw_csum_xor xor;
struct cgw_csum_crc8 crc8;
} csum;
+ struct {
+ void (*xor)(struct can_frame *cf, struct cgw_csum_xor *xor);
+ void (*crc8)(struct can_frame *cf, struct cgw_csum_crc8 *crc8);
+ } csumfunc;
};
/* modification functions that are invoked in the hot path in can_can_gw_rcv */
-#define MODFUNC(func, op) static void func (struct can_frame *cf, \
- struct cf_mod *mod) { op ; }
+#define MODFUNC(func, op) static void func(struct can_frame *cf, \
+ struct cf_mod *mod) { op ; }
MODFUNC(mod_and_id, cf->can_id &= mod->modframe.and.can_id)
MODFUNC(mod_and_dlc, cf->can_dlc &= mod->modframe.and.can_dlc)
static int cgw_chk_csum_parms(s8 fr, s8 to, s8 re)
{
- /*
+ /*
* absolute dlc values 0 .. 7 => 0 .. 7, e.g. data [0]
* relative to received dlc -1 .. -8 :
- * e.g. for received dlc = 8
+ * e.g. for received dlc = 8
* -1 => index = 7 (data[7])
* -3 => index = 5 (data[5])
* -8 => index = 0 (data[0])
return 0;
else
return -EINVAL;
-}
+}
-static void cgw_csum_do_xor(struct can_frame *cf, struct cgw_csum_xor *xor)
+static inline int calc_idx(int idx, int rx_dlc)
{
- /* TODO: perform checksum update */
+ if (idx < 0)
+ return rx_dlc + idx;
+ else
+ return idx;
}
-static void cgw_csum_do_crc8(struct can_frame *cf, struct cgw_csum_crc8 *crc8)
+static void cgw_csum_xor_rel(struct can_frame *cf, struct cgw_csum_xor *xor)
{
- /* TODO: perform checksum update */
+ int from = calc_idx(xor->from_idx, cf->can_dlc);
+ int to = calc_idx(xor->to_idx, cf->can_dlc);
+ int res = calc_idx(xor->result_idx, cf->can_dlc);
+ u8 val = xor->init_xor_val;
+ int i;
+
+ if (from < 0 || to < 0 || res < 0)
+ return;
+
+ if (from <= to) {
+ for (i = from; i <= to; i++)
+ val ^= cf->data[i];
+ } else {
+ for (i = from; i >= to; i--)
+ val ^= cf->data[i];
+ }
+
+ cf->data[res] = val;
+}
+
+static void cgw_csum_xor_pos(struct can_frame *cf, struct cgw_csum_xor *xor)
+{
+ u8 val = xor->init_xor_val;
+ int i;
+
+ for (i = xor->from_idx; i <= xor->to_idx; i++)
+ val ^= cf->data[i];
+
+ cf->data[xor->result_idx] = val;
+}
+
+static void cgw_csum_xor_neg(struct can_frame *cf, struct cgw_csum_xor *xor)
+{
+ u8 val = xor->init_xor_val;
+ int i;
+
+ for (i = xor->from_idx; i >= xor->to_idx; i--)
+ val ^= cf->data[i];
+
+ cf->data[xor->result_idx] = val;
+}
+
+static void cgw_csum_crc8_rel(struct can_frame *cf, struct cgw_csum_crc8 *crc8)
+{
+ int from = calc_idx(crc8->from_idx, cf->can_dlc);
+ int to = calc_idx(crc8->to_idx, cf->can_dlc);
+ int res = calc_idx(crc8->result_idx, cf->can_dlc);
+ u8 crc = crc8->init_crc_val;
+ int i;
+
+ if (from < 0 || to < 0 || res < 0)
+ return;
+
+ if (from <= to) {
+ for (i = crc8->from_idx; i <= crc8->to_idx; i++)
+ crc = crc8->crctab[crc^cf->data[i]];
+ } else {
+ for (i = crc8->from_idx; i >= crc8->to_idx; i--)
+ crc = crc8->crctab[crc^cf->data[i]];
+ }
+
+ switch (crc8->profile) {
+
+ case CGW_CRC8PRF_1U8:
+ crc = crc8->crctab[crc^crc8->profile_data[0]];
+ break;
+
+ case CGW_CRC8PRF_16U8:
+ crc = crc8->crctab[crc^crc8->profile_data[cf->data[1] & 0xF]];
+ break;
+
+ case CGW_CRC8PRF_SFFID_XOR:
+ crc = crc8->crctab[crc^(cf->can_id & 0xFF)^
+ (cf->can_id >> 8 & 0xFF)];
+ break;
+
+ }
+
+ cf->data[crc8->result_idx] = crc^crc8->final_xor_val;
+}
+
+static void cgw_csum_crc8_pos(struct can_frame *cf, struct cgw_csum_crc8 *crc8)
+{
+ u8 crc = crc8->init_crc_val;
+ int i;
+
+ for (i = crc8->from_idx; i <= crc8->to_idx; i++)
+ crc = crc8->crctab[crc^cf->data[i]];
+
+ switch (crc8->profile) {
+
+ case CGW_CRC8PRF_1U8:
+ crc = crc8->crctab[crc^crc8->profile_data[0]];
+ break;
+
+ case CGW_CRC8PRF_16U8:
+ crc = crc8->crctab[crc^crc8->profile_data[cf->data[1] & 0xF]];
+ break;
+
+ case CGW_CRC8PRF_SFFID_XOR:
+ crc = crc8->crctab[crc^(cf->can_id & 0xFF)^
+ (cf->can_id >> 8 & 0xFF)];
+ break;
+ }
+
+ cf->data[crc8->result_idx] = crc^crc8->final_xor_val;
+}
+
+static void cgw_csum_crc8_neg(struct can_frame *cf, struct cgw_csum_crc8 *crc8)
+{
+ u8 crc = crc8->init_crc_val;
+ int i;
+
+ for (i = crc8->from_idx; i >= crc8->to_idx; i--)
+ crc = crc8->crctab[crc^cf->data[i]];
+
+ switch (crc8->profile) {
+
+ case CGW_CRC8PRF_1U8:
+ crc = crc8->crctab[crc^crc8->profile_data[0]];
+ break;
+
+ case CGW_CRC8PRF_16U8:
+ crc = crc8->crctab[crc^crc8->profile_data[cf->data[1] & 0xF]];
+ break;
+
+ case CGW_CRC8PRF_SFFID_XOR:
+ crc = crc8->crctab[crc^(cf->can_id & 0xFF)^
+ (cf->can_id >> 8 & 0xFF)];
+ break;
+ }
+
+ cf->data[crc8->result_idx] = crc^crc8->final_xor_val;
}
/* the receive & process & send function */
struct sk_buff *nskb;
int modidx = 0;
- /* do not handle already routed frames */
- if (skb->sk == CGW_SK_MAGIC)
+ /* do not handle already routed frames - see comment below */
+ if (skb_mac_header_was_set(skb))
return;
if (!(gwj->dst.dev->flags & IFF_UP)) {
return;
}
- /* mark routed frames with a 'special' sk value */
- nskb->sk = CGW_SK_MAGIC;
+ /*
+ * Mark routed frames by setting some mac header length which is
+ * not relevant for the CAN frames located in the skb->data section.
+ *
+ * As dev->header_ops is not set in CAN netdevices no one is ever
+ * accessing the various header offsets in the CAN skbuffs anyway.
+ * E.g. using the packet socket to read CAN frames is still working.
+ */
+ skb_set_mac_header(nskb, 8);
nskb->dev = gwj->dst.dev;
/* pointer to modifiable CAN frame */
/* check for checksum updates when the CAN frame has been modified */
if (modidx) {
- if (gwj->mod.csum.xor.from_idx != CGW_CS_DISABLED)
- cgw_csum_do_xor(cf, &gwj->mod.csum.xor);
+ if (gwj->mod.csumfunc.crc8)
+ (*gwj->mod.csumfunc.crc8)(cf, &gwj->mod.csum.crc8);
- if (gwj->mod.csum.crc8.from_idx != CGW_CS_DISABLED)
- cgw_csum_do_crc8(cf, &gwj->mod.csum.crc8);
+ if (gwj->mod.csumfunc.xor)
+ (*gwj->mod.csumfunc.xor)(cf, &gwj->mod.csum.xor);
}
/* clear the skb timestamp if not configured the other way */
struct cgw_job *gwj = NULL;
struct hlist_node *n, *nx;
- spin_lock(&cgw_list_lock);
+ ASSERT_RTNL();
hlist_for_each_entry_safe(gwj, n, nx, &cgw_list, list) {
- if (gwj->src.dev == dev || gwj->dst.dev == dev) {
+ if (gwj->src.dev == dev || gwj->dst.dev == dev) {
hlist_del(&gwj->list);
cgw_unregister_filter(gwj);
kfree(gwj);
}
}
-
- spin_unlock(&cgw_list_lock);
}
return NOTIFY_DONE;
nlh->nlmsg_len += NLA_HDRLEN + NLA_ALIGN(sizeof(mb));
}
- if (gwj->mod.csum.xor.from_idx != CGW_CS_DISABLED) {
- if (nla_put(skb, CGW_CS_XOR, CGW_CS_XOR_LEN,
- &gwj->mod.csum.xor) < 0)
+ if (gwj->mod.csumfunc.crc8) {
+ if (nla_put(skb, CGW_CS_CRC8, CGW_CS_CRC8_LEN,
+ &gwj->mod.csum.crc8) < 0)
goto cancel;
else
nlh->nlmsg_len += NLA_HDRLEN + \
- NLA_ALIGN(CGW_CS_XOR_LEN);
+ NLA_ALIGN(CGW_CS_CRC8_LEN);
}
- if (gwj->mod.csum.crc8.from_idx != CGW_CS_DISABLED) {
- if (nla_put(skb, CGW_CS_CRC8, CGW_CS_CRC8_LEN,
- &gwj->mod.csum.crc8) < 0)
+ if (gwj->mod.csumfunc.xor) {
+ if (nla_put(skb, CGW_CS_XOR, CGW_CS_XOR_LEN,
+ &gwj->mod.csum.xor) < 0)
goto cancel;
else
nlh->nlmsg_len += NLA_HDRLEN + \
- NLA_ALIGN(CGW_CS_CRC8_LEN);
+ NLA_ALIGN(CGW_CS_XOR_LEN);
}
if (gwj->gwtype == CGW_TYPE_CAN_CAN) {
}
/* check for common and gwtype specific attributes */
-static int cgw_parse_attr(struct nlmsghdr *nlh, struct cf_mod *mod,
+static int cgw_parse_attr(struct nlmsghdr *nlh, struct cf_mod *mod,
u8 gwtype, void *gwtypeattr)
{
struct nlattr *tb[CGW_MAX+1];
int err = 0;
/* initialize modification & checksum data space */
- memset(mod, 0, sizeof(*mod));
- mod->csum.xor.from_idx = CGW_CS_DISABLED;
- mod->csum.crc8.from_idx = CGW_CS_DISABLED;
+ memset(mod, 0, sizeof(*mod));
err = nlmsg_parse(nlh, sizeof(struct rtcanmsg), tb, CGW_MAX, NULL);
if (err < 0)
/* check for checksum operations after CAN frame modifications */
if (modidx) {
- if (tb[CGW_CS_XOR] &&
- nla_len(tb[CGW_CS_XOR]) == CGW_CS_XOR_LEN) {
- nla_memcpy(&mod->csum.xor, tb[CGW_CS_XOR],
- CGW_CS_XOR_LEN);
- err = cgw_chk_csum_parms(mod->csum.xor.from_idx,
- mod->csum.xor.to_idx,
- mod->csum.xor.result_idx);
+ if (tb[CGW_CS_CRC8] &&
+ nla_len(tb[CGW_CS_CRC8]) == CGW_CS_CRC8_LEN) {
+
+ struct cgw_csum_crc8 *c = (struct cgw_csum_crc8 *)\
+ nla_data(tb[CGW_CS_CRC8]);
+
+ err = cgw_chk_csum_parms(c->from_idx, c->to_idx,
+ c->result_idx);
if (err)
return err;
- }
- if (tb[CGW_CS_CRC8] &&
- nla_len(tb[CGW_CS_CRC8]) == CGW_CS_CRC8_LEN) {
nla_memcpy(&mod->csum.crc8, tb[CGW_CS_CRC8],
CGW_CS_CRC8_LEN);
- err = cgw_chk_csum_parms(mod->csum.crc8.from_idx,
- mod->csum.crc8.to_idx,
- mod->csum.crc8.result_idx);
+
+ /*
+ * select dedicated processing function to reduce
+ * runtime operations in receive hot path.
+ */
+ if (c->from_idx < 0 || c->to_idx < 0 ||
+ c->result_idx < 0)
+ mod->csumfunc.crc8 = cgw_csum_crc8_rel;
+ else if (c->from_idx <= c->to_idx)
+ mod->csumfunc.crc8 = cgw_csum_crc8_pos;
+ else
+ mod->csumfunc.crc8 = cgw_csum_crc8_neg;
+ }
+
+ if (tb[CGW_CS_XOR] &&
+ nla_len(tb[CGW_CS_XOR]) == CGW_CS_XOR_LEN) {
+
+ struct cgw_csum_xor *c = (struct cgw_csum_xor *)\
+ nla_data(tb[CGW_CS_XOR]);
+
+ err = cgw_chk_csum_parms(c->from_idx, c->to_idx,
+ c->result_idx);
if (err)
return err;
+
+ nla_memcpy(&mod->csum.xor, tb[CGW_CS_XOR],
+ CGW_CS_XOR_LEN);
+
+ /*
+ * select dedicated processing function to reduce
+ * runtime operations in receive hot path.
+ */
+ if (c->from_idx < 0 || c->to_idx < 0 ||
+ c->result_idx < 0)
+ mod->csumfunc.xor = cgw_csum_xor_rel;
+ else if (c->from_idx <= c->to_idx)
+ mod->csumfunc.xor = cgw_csum_xor_pos;
+ else
+ mod->csumfunc.xor = cgw_csum_xor_neg;
}
}
/* check CGW_TYPE_CAN_CAN specific attributes */
struct can_can_gw *ccgw = (struct can_can_gw *)gwtypeattr;
- memset(ccgw, 0, sizeof(*ccgw));
+ memset(ccgw, 0, sizeof(*ccgw));
/* check for can_filter in attributes */
if (tb[CGW_FILTER] &&
if (!gwj->src.dev)
goto out;
- if (gwj->src.dev->type != ARPHRD_CAN)
+ /* check for CAN netdev not using header_ops - see gw_rcv() */
+ if (gwj->src.dev->type != ARPHRD_CAN || gwj->src.dev->header_ops)
goto put_src_out;
gwj->dst.dev = dev_get_by_index(&init_net, gwj->ccgw.dst_idx);
if (!gwj->dst.dev)
goto put_src_out;
- if (gwj->dst.dev->type != ARPHRD_CAN)
+ /* check for CAN netdev not using header_ops - see gw_rcv() */
+ if (gwj->dst.dev->type != ARPHRD_CAN || gwj->dst.dev->header_ops)
goto put_src_dst_out;
-
- spin_lock(&cgw_list_lock);
+
+ ASSERT_RTNL();
err = cgw_register_filter(gwj);
if (!err)
hlist_add_head_rcu(&gwj->list, &cgw_list);
- spin_unlock(&cgw_list_lock);
-
put_src_dst_out:
dev_put(gwj->dst.dev);
put_src_out:
struct cgw_job *gwj = NULL;
struct hlist_node *n, *nx;
- spin_lock(&cgw_list_lock);
+ ASSERT_RTNL();
hlist_for_each_entry_safe(gwj, n, nx, &cgw_list, list) {
hlist_del(&gwj->list);
cgw_unregister_filter(gwj);
kfree(gwj);
}
-
- spin_unlock(&cgw_list_lock);
}
static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
err = -EINVAL;
- spin_lock(&cgw_list_lock);
+ ASSERT_RTNL();
/* remove only the first matching entry */
hlist_for_each_entry_safe(gwj, n, nx, &cgw_list, list) {
break;
}
- spin_unlock(&cgw_list_lock);
-
return err;
}
unregister_netdevice_notifier(¬ifier);
+ rtnl_lock();
cgw_remove_all_jobs();
+ rtnl_unlock();
rcu_barrier(); /* Wait for completion of call_rcu()'s */