]> rtime.felk.cvut.cz Git - socketcan-devel.git/blobdiff - kernel/2.6/net/can/gw.c
Added funtionality to flush all GW jobs in one netlink message.
[socketcan-devel.git] / kernel / 2.6 / net / can / gw.c
index dafd0790d7d4d6f969a424a38107feeb5df67dab..42f7fe7a0ec91f48e7a320af50d5e904589be213 100644 (file)
@@ -48,6 +48,7 @@
 #include <linux/list.h>
 #include <linux/spinlock.h>
 #include <linux/rcupdate.h>
+#include <linux/rculist.h>
 #include <linux/net.h>
 #include <linux/netdevice.h>
 #include <linux/if_arp.h>
@@ -61,7 +62,7 @@
 #include <socketcan/can/version.h> /* for RCSID. Removed by mkpatch script */
 RCSID("$Id$");
 
-#define CAN_GW_VERSION "20100218"
+#define CAN_GW_VERSION "20100222"
 static __initdata const char banner[] =
        KERN_INFO "can: netlink gateway (rev " CAN_GW_VERSION ")\n";
 
@@ -91,6 +92,12 @@ struct can_can_gw {
                struct can_frame xor;
                struct can_frame set;
        } modframe;
+       struct {
+               u8 and;
+               u8 or;
+               u8 xor;
+               u8 set;
+       } modtype;
        void (*modfunc[MAX_MODFUNCTIONS])(struct can_frame *cf,
                                          struct can_can_gw *mod);
 };
@@ -111,7 +118,8 @@ struct gw_job {
 };
 
 /* content of u32 gwjob.flags */
-#define CAN_TX_LOOPBACK 0x00000001
+#define CAN_TX_ECHO 0x00000001
+#define CAN_TX_SRC_TSTAMP 0x00000002
 
 /* modification functions that are invoked in the hot path in gw_rcv */
 void mod_and_id (struct can_frame *cf, struct can_can_gw *mod) {
@@ -151,6 +159,18 @@ void mod_set_data (struct can_frame *cf, struct can_can_gw *mod) {
        *(u64 *)cf->data = *(u64 *)mod->modframe.set.data;
 }
 
+static inline void canframecpy(struct can_frame *dst, struct can_frame *src)
+{
+       /*
+        * Copy the struct members separately to ensure that no uninitialized
+        * data are copied in the 3 bytes hole of the struct. This is needed
+        * to make easy compares of the data in the struct can_can_gw.
+        */
+
+       dst->can_id = src->can_id;
+       dst->can_dlc = src->can_dlc;
+       *(u64 *)dst->data = *(u64 *)src->data;
+}
 
 /* the receive & process & send function */
 static void gw_rcv(struct sk_buff *skb, void *data)
@@ -164,13 +184,13 @@ static void gw_rcv(struct sk_buff *skb, void *data)
        if (skb->sk == GW_SK_MAGIC)
                return;
 
-       if (!netif_running(gwj->dst_dev)) {
+       if (!(gwj->dst_dev->flags & IFF_UP)) {
                gwj->dropped_frames++;
                return;
        }
 
        /*
-        * clone the given skb, which has not been done in can_rv()
+        * clone the given skb, which has not been done in can_rcv()
         *
         * When there is at least one modification function activated,
         * we need to copy the skb as we want to modify skb->data.
@@ -196,8 +216,12 @@ static void gw_rcv(struct sk_buff *skb, void *data)
        while (modidx < MAX_MODFUNCTIONS && gwj->ccgw.modfunc[modidx])
                (*gwj->ccgw.modfunc[modidx++])(cf, &gwj->ccgw);
 
+       /* clear the skb timestamp if not configured the other way */
+       if (!(gwj->flags & CAN_TX_SRC_TSTAMP))
+               nskb->tstamp.tv64 = 0;
+
        /* send to netdevice */
-       if (can_send(nskb, gwj->flags & CAN_TX_LOOPBACK))
+       if (can_send(nskb, gwj->flags & CAN_TX_ECHO))
                gwj->dropped_frames++;
        else
                gwj->handled_frames++;
@@ -252,152 +276,273 @@ static int gw_notifier(struct notifier_block *nb,
        return NOTIFY_DONE;
 }
 
-/*
- * Dump information about all ports, in response to GETROUTE
- */
-static int gw_dump_jobs(struct sk_buff *skb, struct netlink_callback *cb)
+static int gw_put_job(struct sk_buff *skb, struct gw_job *gwj)
 {
-       printk(KERN_INFO "%s (TODO)\n", __FUNCTION__);
-
-       return 0;
-}
-
-static int gw_create_job(struct sk_buff *skb,  struct nlmsghdr *nlh, void *arg)
-{
-
-       struct rtcanmsg *r;
-       struct nlattr *tb[CGW_MAX+1];
-       struct gw_job *gwj;
-       u8 buf[CGW_MODATTR_LEN];
-       int modidx = 0;
-       int err = 0;
-
-       printk(KERN_INFO "%s: len %d attrlen %d\n", __FUNCTION__,
-              nlmsg_len(nlh), nlmsg_attrlen(nlh, sizeof(*r)));
+       struct {
+               struct can_frame cf;
+               u8 modtype;
+       } __attribute__((packed)) mb;
+
+       struct rtcanmsg *rtcan;
+       struct nlmsghdr *nlh = nlmsg_put(skb, 0, 0, 0, sizeof(*rtcan), 0);
+       if (!nlh)
+               return -EMSGSIZE;
+
+       rtcan = nlmsg_data(nlh);
+       rtcan->can_family = AF_CAN;
+       rtcan->src_ifindex = gwj->src_dev->ifindex;
+       rtcan->dst_ifindex = gwj->dst_dev->ifindex;
+       rtcan->can_txflags = 0;
+
+       if (gwj->flags & CAN_TX_ECHO)
+               rtcan->can_txflags |= CAN_GW_TXFLAGS_ECHO;
+
+       if (gwj->flags & CAN_TX_SRC_TSTAMP)
+               rtcan->can_txflags |= CAN_GW_TXFLAGS_SRC_TSTAMP;
+
+       /* check non default settings of attributes */
+       if (gwj->handled_frames) {
+               if (nla_put_u32(skb, CGW_HANDLED, gwj->handled_frames) < 0)
+                       goto cancel;
+               else
+                       nlh->nlmsg_len += NLA_HDRLEN + NLA_ALIGN(sizeof(u32));
+       }
 
-       if (nlmsg_len(nlh) < sizeof(*r))
-                return -EINVAL;
+       if (gwj->dropped_frames) {
+               if (nla_put_u32(skb, CGW_DROPPED, gwj->dropped_frames) < 0)
+                       goto cancel;
+               else
+                       nlh->nlmsg_len += NLA_HDRLEN + NLA_ALIGN(sizeof(u32));
+       }
 
-        r = nlmsg_data(nlh);
-        if (r->can_family != AF_CAN)
-                return -EPFNOSUPPORT;
+       if (gwj->ccgw.filter.can_id || gwj->ccgw.filter.can_mask) {
+               if (nla_put(skb, CGW_FILTER, sizeof(struct can_filter),
+                           &gwj->ccgw.filter) < 0)
+                       goto cancel;
+               else
+                       nlh->nlmsg_len += NLA_HDRLEN +
+                               NLA_ALIGN(sizeof(struct can_filter));
+       }
 
-       gwj = kmem_cache_alloc(gw_cache, GFP_KERNEL);
-       if (!gwj)
-               return -ENOMEM;
+       if (gwj->ccgw.modtype.and) {
+               memcpy(&mb.cf, &gwj->ccgw.modframe.and, sizeof(mb.cf));
+               mb.modtype = gwj->ccgw.modtype.and;
+               if (nla_put(skb, CGW_MOD_AND, sizeof(mb), &mb) < 0)
+                       goto cancel;
+               else
+                       nlh->nlmsg_len += NLA_HDRLEN + NLA_ALIGN(sizeof(mb));
+       }
 
-       gwj->src_dev = dev_get_by_index(&init_net, r->src_ifindex);
-       if (!gwj->src_dev) {
-               err = -ENODEV;
-               goto fail;
+       if (gwj->ccgw.modtype.or) {
+               memcpy(&mb.cf, &gwj->ccgw.modframe.or, sizeof(mb.cf));
+               mb.modtype = gwj->ccgw.modtype.or;
+               if (nla_put(skb, CGW_MOD_OR, sizeof(mb), &mb) < 0)
+                       goto cancel;
+               else
+                       nlh->nlmsg_len += NLA_HDRLEN + NLA_ALIGN(sizeof(mb));
        }
 
-       /* for now the source device needs to be a CAN device */
-       if (gwj->src_dev->type != ARPHRD_CAN) {
-               err = -ENODEV;
-               goto put_src_fail;
+       if (gwj->ccgw.modtype.xor) {
+               memcpy(&mb.cf, &gwj->ccgw.modframe.xor, sizeof(mb.cf));
+               mb.modtype = gwj->ccgw.modtype.xor;
+               if (nla_put(skb, CGW_MOD_XOR, sizeof(mb), &mb) < 0)
+                       goto cancel;
+               else
+                       nlh->nlmsg_len += NLA_HDRLEN + NLA_ALIGN(sizeof(mb));
        }
 
-       gwj->dst_dev = dev_get_by_index(&init_net, r->dst_ifindex);
-       if (!gwj->dst_dev) {
-               err = -ENODEV;
-               goto put_src_fail;
+       if (gwj->ccgw.modtype.set) {
+               memcpy(&mb.cf, &gwj->ccgw.modframe.set, sizeof(mb.cf));
+               mb.modtype = gwj->ccgw.modtype.set;
+               if (nla_put(skb, CGW_MOD_SET, sizeof(mb), &mb) < 0)
+                       goto cancel;
+               else
+                       nlh->nlmsg_len += NLA_HDRLEN + NLA_ALIGN(sizeof(mb));
        }
 
-       /* for now the destination device needs to be a CAN device */
-       if (gwj->dst_dev->type != ARPHRD_CAN) {
-               err = -ENODEV;
-               goto put_src_dst_fail;
+       return skb->len;
+
+cancel:
+       nlmsg_cancel(skb, nlh);
+       return -EMSGSIZE;
+}
+
+/* Dump information about all CAN gateway jobs, in response to RTM_GETROUTE */
+static int gw_dump_jobs(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       struct gw_job *gwj = NULL;
+       struct hlist_node *n;
+       int idx = 0;
+       int ret = 0;
+
+       rcu_read_lock();
+       hlist_for_each_entry_rcu(gwj, n, &can_gw_list, list) {
+               if (idx >= cb->args[0]) {
+                       ret = gw_put_job(skb, gwj);
+                       if (ret > 0)
+                               cb->args[0]++;
+                       break;
+               }
+               idx++;
        }
+       rcu_read_unlock();
 
-       gwj->flags = 0;
+       return ret;
+}
 
-       if (r->can_txflags & CAN_GW_TXFLAGS_LOOPBACK)
-               gwj->flags |= CAN_TX_LOOPBACK;
+/* check for attributes / filters for the CAN->CAN gateway */
+static int can_can_parse_attr(struct nlmsghdr *nlh, struct can_can_gw *ccgw)
+{
+       struct nlattr *tb[CGW_MAX+1];
+       int modidx = 0;
+       int err = 0;
+
+       struct {
+               struct can_frame cf;
+               u8 modtype;
+       } __attribute__((packed)) mb;
 
-       memset(&gwj->ccgw, 0, sizeof(gwj->ccgw)); 
+       BUILD_BUG_ON(sizeof(mb) != CGW_MODATTR_LEN);
 
-       /* check for additional attributes / filters here */
+       memset(ccgw, 0, sizeof(*ccgw)); 
 
-       err = nlmsg_parse(nlh, sizeof(*r), tb, CGW_MAX, NULL);
+       err = nlmsg_parse(nlh, sizeof(struct rtcanmsg), tb, CGW_MAX, NULL);
        if (err < 0)
-               goto put_src_dst_fail;
+               return err;
 
        /* check for can_filter in attributes */
        if (tb[CGW_FILTER] &&
            nla_len(tb[CGW_FILTER]) == sizeof(struct can_filter))
-               nla_memcpy(&gwj->ccgw.filter, tb[CGW_FILTER],
+               nla_memcpy(&ccgw->filter, tb[CGW_FILTER],
                           sizeof(struct can_filter));
 
        /* check for AND/OR/XOR/SET modifications */
        if (tb[CGW_MOD_AND] &&
            nla_len(tb[CGW_MOD_AND]) == CGW_MODATTR_LEN) {
-               nla_memcpy(&buf, tb[CGW_MOD_AND], CGW_MODATTR_LEN);
+               nla_memcpy(&mb, tb[CGW_MOD_AND], CGW_MODATTR_LEN);
 
-               memcpy(&gwj->ccgw.modframe.and, &buf[1],
-                      sizeof(struct can_frame));
+               canframecpy(&ccgw->modframe.and, &mb.cf);
+               ccgw->modtype.and = mb.modtype;
 
-               if (buf[0] & CGW_MOD_ID)
-                       gwj->ccgw.modfunc[modidx++] = mod_and_id;
+               if (mb.modtype & CGW_MOD_ID)
+                       ccgw->modfunc[modidx++] = mod_and_id;
 
-               if (buf[0] & CGW_MOD_DLC)
-                       gwj->ccgw.modfunc[modidx++] = mod_and_dlc;
+               if (mb.modtype & CGW_MOD_DLC)
+                       ccgw->modfunc[modidx++] = mod_and_dlc;
 
-               if (buf[0] & CGW_MOD_DATA)
-                       gwj->ccgw.modfunc[modidx++] = mod_and_data;
+               if (mb.modtype & CGW_MOD_DATA)
+                       ccgw->modfunc[modidx++] = mod_and_data;
        }
 
        if (tb[CGW_MOD_OR] &&
            nla_len(tb[CGW_MOD_OR]) == CGW_MODATTR_LEN) {
-               nla_memcpy(&buf, tb[CGW_MOD_OR], CGW_MODATTR_LEN);
+               nla_memcpy(&mb, tb[CGW_MOD_OR], CGW_MODATTR_LEN);
 
-               memcpy(&gwj->ccgw.modframe.or, &buf[1],
-                      sizeof(struct can_frame));
+               canframecpy(&ccgw->modframe.or, &mb.cf);
+               ccgw->modtype.or = mb.modtype;
 
-               if (buf[0] & CGW_MOD_ID)
-                       gwj->ccgw.modfunc[modidx++] = mod_or_id;
+               if (mb.modtype & CGW_MOD_ID)
+                       ccgw->modfunc[modidx++] = mod_or_id;
 
-               if (buf[0] & CGW_MOD_DLC)
-                       gwj->ccgw.modfunc[modidx++] = mod_or_dlc;
+               if (mb.modtype & CGW_MOD_DLC)
+                       ccgw->modfunc[modidx++] = mod_or_dlc;
 
-               if (buf[0] & CGW_MOD_DATA)
-                       gwj->ccgw.modfunc[modidx++] = mod_or_data;
+               if (mb.modtype & CGW_MOD_DATA)
+                       ccgw->modfunc[modidx++] = mod_or_data;
        }
 
        if (tb[CGW_MOD_XOR] &&
            nla_len(tb[CGW_MOD_XOR]) == CGW_MODATTR_LEN) {
-               nla_memcpy(&buf, tb[CGW_MOD_XOR], CGW_MODATTR_LEN);
+               nla_memcpy(&mb, tb[CGW_MOD_XOR], CGW_MODATTR_LEN);
 
-               memcpy(&gwj->ccgw.modframe.xor, &buf[1],
-                      sizeof(struct can_frame));
+               canframecpy(&ccgw->modframe.xor, &mb.cf);
+               ccgw->modtype.xor = mb.modtype;
 
-               if (buf[0] & CGW_MOD_ID)
-                       gwj->ccgw.modfunc[modidx++] = mod_xor_id;
+               if (mb.modtype & CGW_MOD_ID)
+                       ccgw->modfunc[modidx++] = mod_xor_id;
 
-               if (buf[0] & CGW_MOD_DLC)
-                       gwj->ccgw.modfunc[modidx++] = mod_xor_dlc;
+               if (mb.modtype & CGW_MOD_DLC)
+                       ccgw->modfunc[modidx++] = mod_xor_dlc;
 
-               if (buf[0] & CGW_MOD_DATA)
-                       gwj->ccgw.modfunc[modidx++] = mod_xor_data;
+               if (mb.modtype & CGW_MOD_DATA)
+                       ccgw->modfunc[modidx++] = mod_xor_data;
        }
 
        if (tb[CGW_MOD_SET] &&
            nla_len(tb[CGW_MOD_SET]) == CGW_MODATTR_LEN) {
-               nla_memcpy(&buf, tb[CGW_MOD_SET], CGW_MODATTR_LEN);
+               nla_memcpy(&mb, tb[CGW_MOD_SET], CGW_MODATTR_LEN);
 
-               memcpy(&gwj->ccgw.modframe.set, &buf[1],
-                      sizeof(struct can_frame));
+               canframecpy(&ccgw->modframe.set, &mb.cf);
+               ccgw->modtype.set = mb.modtype;
 
-               if (buf[0] & CGW_MOD_ID)
-                       gwj->ccgw.modfunc[modidx++] = mod_set_id;
+               if (mb.modtype & CGW_MOD_ID)
+                       ccgw->modfunc[modidx++] = mod_set_id;
 
-               if (buf[0] & CGW_MOD_DLC)
-                       gwj->ccgw.modfunc[modidx++] = mod_set_dlc;
+               if (mb.modtype & CGW_MOD_DLC)
+                       ccgw->modfunc[modidx++] = mod_set_dlc;
 
-               if (buf[0] & CGW_MOD_DATA)
-                       gwj->ccgw.modfunc[modidx++] = mod_set_data;
+               if (mb.modtype & CGW_MOD_DATA)
+                       ccgw->modfunc[modidx++] = mod_set_data;
        }
 
+       return 0;
+}
+
+static int gw_create_job(struct sk_buff *skb,  struct nlmsghdr *nlh, void *arg)
+{
+       struct rtcanmsg *r;
+       struct gw_job *gwj;
+       int err = 0;
+
+       if (nlmsg_len(nlh) < sizeof(*r))
+                return -EINVAL;
+
+        r = nlmsg_data(nlh);
+        if (r->can_family != AF_CAN)
+                return -EPFNOSUPPORT;
+
+       gwj = kmem_cache_alloc(gw_cache, GFP_KERNEL);
+       if (!gwj)
+               return -ENOMEM;
+
+       gwj->src_dev = dev_get_by_index(&init_net, r->src_ifindex);
+       if (!gwj->src_dev) {
+               err = -ENODEV;
+               goto fail;
+       }
+
+       /* for now the source device needs to be a CAN device */
+       if (gwj->src_dev->type != ARPHRD_CAN) {
+               err = -ENODEV;
+               goto put_src_fail;
+       }
+
+       gwj->dst_dev = dev_get_by_index(&init_net, r->dst_ifindex);
+       if (!gwj->dst_dev) {
+               err = -ENODEV;
+               goto put_src_fail;
+       }
+
+       /* for now the destination device needs to be a CAN device */
+       if (gwj->dst_dev->type != ARPHRD_CAN) {
+               err = -ENODEV;
+               goto put_src_dst_fail;
+       }
+
+       gwj->handled_frames = 0;
+       gwj->dropped_frames = 0;
+       gwj->flags = 0;
+
+       if (r->can_txflags & CAN_GW_TXFLAGS_ECHO)
+               gwj->flags |= CAN_TX_ECHO;
+
+       if (r->can_txflags & CAN_GW_TXFLAGS_SRC_TSTAMP)
+               gwj->flags |= CAN_TX_SRC_TSTAMP;
+
+       err = can_can_parse_attr(nlh, &gwj->ccgw);
+       if (err < 0)
+               goto put_src_dst_fail;
+
        spin_lock(&can_gw_list_lock);
 
        err = can_gw_register_filter(gwj);
@@ -423,11 +568,83 @@ fail:
        return err;
 }
 
+static void gw_remove_all_jobs(void)
+{
+       struct gw_job *gwj = NULL;
+       struct hlist_node *n, *nx;
+
+       spin_lock(&can_gw_list_lock);
+
+       hlist_for_each_entry_safe(gwj, n, nx, &can_gw_list, list) {
+               hlist_del(&gwj->list);
+               can_gw_unregister_filter(gwj);
+               kfree(gwj);
+       }
+
+       spin_unlock(&can_gw_list_lock);
+}
+
 static int gw_remove_job(struct sk_buff *skb,  struct nlmsghdr *nlh, void *arg)
 {
-       printk(KERN_INFO "%s (TODO)\n", __FUNCTION__);
+       struct gw_job *gwj = NULL;
+       struct hlist_node *n, *nx;
+       struct rtcanmsg *r;
+       struct can_can_gw ccgw;
+       u32 flags = 0;
+       int err = 0;
 
-       return 0;
+       if (nlmsg_len(nlh) < sizeof(*r))
+                return -EINVAL;
+
+        r = nlmsg_data(nlh);
+        if (r->can_family != AF_CAN)
+                return -EPFNOSUPPORT;
+
+       /* if_index set to 0 => remove all entries */
+       if (!r->src_ifindex && !r->dst_ifindex) {
+               gw_remove_all_jobs();
+               return 0;
+       }
+
+       if (r->can_txflags & CAN_GW_TXFLAGS_ECHO)
+               flags |= CAN_TX_ECHO;
+
+       if (r->can_txflags & CAN_GW_TXFLAGS_SRC_TSTAMP)
+               flags |= CAN_TX_SRC_TSTAMP;
+
+       err = can_can_parse_attr(nlh, &ccgw);
+       if (err < 0)
+               return err;
+
+       err = -EINVAL;
+
+       spin_lock(&can_gw_list_lock);
+
+       /* remove only the first matching entry */
+       hlist_for_each_entry_safe(gwj, n, nx, &can_gw_list, list) {
+
+               if (gwj->dst_dev->ifindex != r->dst_ifindex)
+                       continue;
+
+               if (gwj->src_dev->ifindex != r->src_ifindex)
+                       continue;
+
+               if (gwj->flags != flags)
+                       continue;
+
+               if (memcmp(&gwj->ccgw, &ccgw, sizeof(ccgw)))
+                       continue;
+
+               hlist_del(&gwj->list);
+               can_gw_unregister_filter(gwj);
+               kfree(gwj);
+               err = 0;
+               break;
+       }
+
+       spin_unlock(&can_gw_list_lock);
+       
+       return err;
 }
 
 static __init int gw_module_init(void)
@@ -459,22 +676,11 @@ static __init int gw_module_init(void)
 
 static __exit void gw_module_exit(void)
 {
-       struct gw_job *gwj = NULL;
-       struct hlist_node *n, *nx;
-
        rtnl_unregister_all(PF_CAN);
 
        unregister_netdevice_notifier(&notifier);
 
-       spin_lock(&can_gw_list_lock);
-
-       hlist_for_each_entry_safe(gwj, n, nx, &can_gw_list, list) {
-               hlist_del(&gwj->list);
-               can_gw_unregister_filter(gwj);
-               kfree(gwj);
-       }
-
-       spin_unlock(&can_gw_list_lock);
+       gw_remove_all_jobs();
 
        rcu_barrier(); /* Wait for completion of call_rcu()'s */