This patch exposes switchdev API using generic Netlink.
Example userspace utility is here:
https://github.com/jpirko/switchdev

Signed-off-by: Jiri Pirko <j...@resnulli.us>
---
 MAINTAINERS                       |   1 +
 include/uapi/linux/switchdev.h    | 119 +++++++++
 net/switchdev/Kconfig             |  11 +
 net/switchdev/Makefile            |   1 +
 net/switchdev/switchdev_netlink.c | 493 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 625 insertions(+)
 create mode 100644 include/uapi/linux/switchdev.h
 create mode 100644 net/switchdev/switchdev_netlink.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 9797bda..83c4f43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8820,6 +8820,7 @@ L:        net...@vger.kernel.org
 S:     Supported
 F:     net/switchdev/
 F:     include/net/switchdev.h
+F:     include/uapi/linux/switchdev.h
 
 SYNOPSYS ARC ARCHITECTURE
 M:     Vineet Gupta <vgu...@synopsys.com>
diff --git a/include/uapi/linux/switchdev.h b/include/uapi/linux/switchdev.h
new file mode 100644
index 0000000..83692e2
--- /dev/null
+++ b/include/uapi/linux/switchdev.h
@@ -0,0 +1,119 @@
+/*
+ * include/uapi/linux/switchdev.h - Netlink interface to Switch device
+ * Copyright (c) 2014 Jiri Pirko <j...@resnulli.us>
+ *
+ * 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.
+ */
+
+#ifndef _UAPI_LINUX_SWITCHDEV_H_
+#define _UAPI_LINUX_SWITCHDEV_H_
+
+enum {
+       SWDEV_CMD_NOOP,
+       SWDEV_CMD_FLOW_INSERT,
+       SWDEV_CMD_FLOW_REMOVE,
+};
+
+enum {
+       SWDEV_ATTR_UNSPEC,
+       SWDEV_ATTR_IFINDEX,                     /* u32 */
+       SWDEV_ATTR_FLOW,                        /* nest */
+
+       __SWDEV_ATTR_MAX,
+       SWDEV_ATTR_MAX = (__SWDEV_ATTR_MAX - 1),
+};
+
+enum {
+       SWDEV_ATTR_FLOW_KEY_UNSPEC,
+       SWDEV_ATTR_FLOW_KEY_TUN_ID,             /* be64 */
+       SWDEV_ATTR_FLOW_KEY_TUN_IPV4_SRC,       /* be32 */
+       SWDEV_ATTR_FLOW_KEY_TUN_IPV4_DST,       /* be32 */
+       SWDEV_ATTR_FLOW_KEY_TUN_FLAGS,          /* be16 */
+       SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TOS,       /* u8 */
+       SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TTL,       /* u8 */
+       SWDEV_ATTR_FLOW_KEY_PHY_PRIORITY,       /* u32 */
+       SWDEV_ATTR_FLOW_KEY_PHY_IN_PORT,        /* u32 (ifindex) */
+       SWDEV_ATTR_FLOW_KEY_ETH_SRC,            /* ETH_ALEN */
+       SWDEV_ATTR_FLOW_KEY_ETH_DST,            /* ETH_ALEN */
+       SWDEV_ATTR_FLOW_KEY_ETH_TCI,            /* be16 */
+       SWDEV_ATTR_FLOW_KEY_ETH_TYPE,           /* be16 */
+       SWDEV_ATTR_FLOW_KEY_IP_PROTO,           /* u8 */
+       SWDEV_ATTR_FLOW_KEY_IP_TOS,             /* u8 */
+       SWDEV_ATTR_FLOW_KEY_IP_TTL,             /* u8 */
+       SWDEV_ATTR_FLOW_KEY_IP_FRAG,            /* u8 */
+       SWDEV_ATTR_FLOW_KEY_TP_SRC,             /* be16 */
+       SWDEV_ATTR_FLOW_KEY_TP_DST,             /* be16 */
+       SWDEV_ATTR_FLOW_KEY_TP_FLAGS,           /* be16 */
+       SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_SRC,      /* be32 */
+       SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_DST,      /* be32 */
+       SWDEV_ATTR_FLOW_KEY_IPV4_ARP_SHA,       /* ETH_ALEN */
+       SWDEV_ATTR_FLOW_KEY_IPV4_ARP_THA,       /* ETH_ALEN */
+       SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_SRC,      /* struct in6_addr */
+       SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_DST,      /* struct in6_addr */
+       SWDEV_ATTR_FLOW_KEY_IPV6_LABEL,         /* be32 */
+       SWDEV_ATTR_FLOW_KEY_IPV6_ND_TARGET,     /* struct in6_addr */
+       SWDEV_ATTR_FLOW_KEY_IPV6_ND_SLL,        /* ETH_ALEN */
+       SWDEV_ATTR_FLOW_KEY_IPV6_ND_TLL,        /* ETH_ALEN */
+
+       __SWDEV_ATTR_FLOW_KEY_MAX,
+       SWDEV_ATTR_FLOW_KEY_MAX = (__SWDEV_ATTR_FLOW_KEY_MAX - 1),
+};
+
+enum {
+       SWDEV_FLOW_ACTION_TYPE_OUTPUT,
+       SWDEV_FLOW_ACTION_TYPE_VLAN_PUSH,
+       SWDEV_FLOW_ACTION_TYPE_VLAN_POP,
+};
+
+enum {
+       SWDEV_ATTR_FLOW_ACTION_UNSPEC,
+       SWDEV_ATTR_FLOW_ACTION_TYPE,            /* u32 */
+       SWDEV_ATTR_FLOW_ACTION_OUT_PORT,        /* u32 (ifindex) */
+       SWDEV_ATTR_FLOW_ACTION_VLAN_PROTO,      /* be16 */
+       SWDEV_ATTR_FLOW_ACTION_VLAN_TCI,        /* u16 */
+
+       __SWDEV_ATTR_FLOW_ACTION_MAX,
+       SWDEV_ATTR_FLOW_ACTION_MAX = (__SWDEV_ATTR_FLOW_ACTION_MAX - 1),
+};
+
+enum {
+       SWDEV_ATTR_FLOW_ITEM_UNSPEC,
+       SWDEV_ATTR_FLOW_ITEM_ACTION,            /* nest */
+
+       __SWDEV_ATTR_FLOW_ITEM_MAX,
+       SWDEV_ATTR_FLOW_ITEM_MAX = (__SWDEV_ATTR_FLOW_ITEM_MAX - 1),
+};
+
+enum {
+       SWDEV_ATTR_FLOW_UNSPEC,
+       SWDEV_ATTR_FLOW_KEY,                    /* nest */
+       SWDEV_ATTR_FLOW_MASK,                   /* nest */
+       SWDEV_ATTR_FLOW_LIST_ACTION,            /* nest */
+
+       __SWDEV_ATTR_FLOW_MAX,
+       SWDEV_ATTR_FLOW_MAX = (__SWDEV_ATTR_FLOW_MAX - 1),
+};
+
+/* Nested layout of flow add/remove command message:
+ *
+ *     [SWDEV_ATTR_IFINDEX]
+ *     [SWDEV_ATTR_FLOW]
+ *             [SWDEV_ATTR_FLOW_KEY]
+ *                     [SWDEV_ATTR_FLOW_KEY_*], ...
+ *             [SWDEV_ATTR_FLOW_MASK]
+ *                     [SWDEV_ATTR_FLOW_KEY_*], ...
+ *             [SWDEV_ATTR_FLOW_LIST_ACTION]
+ *                     [SWDEV_ATTR_FLOW_ITEM_ACTION]
+ *                             [SWDEV_ATTR_FLOW_ACTION_*], ...
+ *                     [SWDEV_ATTR_FLOW_ITEM_ACTION]
+ *                             [SWDEV_ATTR_FLOW_ACTION_*], ...
+ *                     ...
+ */
+
+#define SWITCHDEV_GENL_NAME "switchdev"
+#define SWITCHDEV_GENL_VERSION 0x1
+
+#endif /* _UAPI_LINUX_SWITCHDEV_H_ */
diff --git a/net/switchdev/Kconfig b/net/switchdev/Kconfig
index 20e8ed2..4470d6e 100644
--- a/net/switchdev/Kconfig
+++ b/net/switchdev/Kconfig
@@ -7,3 +7,14 @@ config NET_SWITCHDEV
        depends on INET
        ---help---
          This module provides support for hardware switch chips.
+
+config NET_SWITCHDEV_NETLINK
+       tristate "Netlink interface to Switch device"
+       depends on NET_SWITCHDEV
+       default m
+       ---help---
+         This module provides Generic Netlink intercace to hardware switch
+         chips.
+
+         To compile this code as a module, choose M here: the
+         module will be called switchdev_netlink.
diff --git a/net/switchdev/Makefile b/net/switchdev/Makefile
index 5ed63ed..0695b53 100644
--- a/net/switchdev/Makefile
+++ b/net/switchdev/Makefile
@@ -3,3 +3,4 @@
 #
 
 obj-$(CONFIG_NET_SWITCHDEV) += switchdev.o
+obj-$(CONFIG_NET_SWITCHDEV_NETLINK) += switchdev_netlink.o
diff --git a/net/switchdev/switchdev_netlink.c 
b/net/switchdev/switchdev_netlink.c
new file mode 100644
index 0000000..14a3dd1
--- /dev/null
+++ b/net/switchdev/switchdev_netlink.c
@@ -0,0 +1,493 @@
+/*
+ * net/switchdev/switchdev_netlink.c - Netlink interface to Switch device
+ * Copyright (c) 2014 Jiri Pirko <j...@resnulli.us>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/sw_flow.h>
+#include <net/switchdev.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#include <net/netlink.h>
+#include <uapi/linux/switchdev.h>
+
+static struct genl_family swdev_nl_family = {
+       .id             = GENL_ID_GENERATE,
+       .name           = SWITCHDEV_GENL_NAME,
+       .version        = SWITCHDEV_GENL_VERSION,
+       .maxattr        = SWDEV_ATTR_MAX,
+       .netnsok        = true,
+};
+
+static const struct nla_policy swdev_nl_flow_policy[SWDEV_ATTR_FLOW_MAX + 1] = 
{
+       [SWDEV_ATTR_FLOW_UNSPEC]                = { .type = NLA_UNSPEC, },
+       [SWDEV_ATTR_FLOW_KEY]                   = { .type = NLA_NESTED },
+       [SWDEV_ATTR_FLOW_MASK]                  = { .type = NLA_NESTED },
+       [SWDEV_ATTR_FLOW_LIST_ACTION]           = { .type = NLA_NESTED },
+};
+
+#define __IN6_ALEN sizeof(struct in6_addr)
+
+static const struct nla_policy
+swdev_nl_flow_key_policy[SWDEV_ATTR_FLOW_KEY_MAX + 1] = {
+       [SWDEV_ATTR_FLOW_KEY_UNSPEC]            = { .type = NLA_UNSPEC, },
+       [SWDEV_ATTR_FLOW_KEY_TUN_ID]            = { .type = NLA_U64, },
+       [SWDEV_ATTR_FLOW_KEY_TUN_IPV4_SRC]      = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_KEY_TUN_IPV4_DST]      = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_KEY_TUN_FLAGS]         = { .type = NLA_U16, },
+       [SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TOS]      = { .type = NLA_U8, },
+       [SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TTL]      = { .type = NLA_U8, },
+       [SWDEV_ATTR_FLOW_KEY_PHY_PRIORITY]      = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_KEY_PHY_IN_PORT]       = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_KEY_ETH_SRC]           = { .len  = ETH_ALEN, },
+       [SWDEV_ATTR_FLOW_KEY_ETH_DST]           = { .len  = ETH_ALEN, },
+       [SWDEV_ATTR_FLOW_KEY_ETH_TCI]           = { .type = NLA_U16, },
+       [SWDEV_ATTR_FLOW_KEY_ETH_TYPE]          = { .type = NLA_U16, },
+       [SWDEV_ATTR_FLOW_KEY_IP_PROTO]          = { .type = NLA_U8, },
+       [SWDEV_ATTR_FLOW_KEY_IP_TOS]            = { .type = NLA_U8, },
+       [SWDEV_ATTR_FLOW_KEY_IP_TTL]            = { .type = NLA_U8, },
+       [SWDEV_ATTR_FLOW_KEY_IP_FRAG]           = { .type = NLA_U8, },
+       [SWDEV_ATTR_FLOW_KEY_TP_SRC]            = { .type = NLA_U16, },
+       [SWDEV_ATTR_FLOW_KEY_TP_DST]            = { .type = NLA_U16, },
+       [SWDEV_ATTR_FLOW_KEY_TP_FLAGS]          = { .type = NLA_U16, },
+       [SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_SRC]     = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_DST]     = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_KEY_IPV4_ARP_SHA]      = { .len  = ETH_ALEN, },
+       [SWDEV_ATTR_FLOW_KEY_IPV4_ARP_THA]      = { .len  = ETH_ALEN, },
+       [SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_SRC]     = { .len  = __IN6_ALEN, },
+       [SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_DST]     = { .len  = __IN6_ALEN, },
+       [SWDEV_ATTR_FLOW_KEY_IPV6_LABEL]        = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_KEY_IPV6_ND_TARGET]    = { .len  = __IN6_ALEN, },
+       [SWDEV_ATTR_FLOW_KEY_IPV6_ND_SLL]       = { .len  = ETH_ALEN },
+       [SWDEV_ATTR_FLOW_KEY_IPV6_ND_TLL]       = { .len  = ETH_ALEN },
+};
+
+static const struct nla_policy
+swdev_nl_flow_action_policy[SWDEV_ATTR_FLOW_ACTION_MAX + 1] = {
+       [SWDEV_ATTR_FLOW_ACTION_UNSPEC]         = { .type = NLA_UNSPEC, },
+       [SWDEV_ATTR_FLOW_ACTION_TYPE]           = { .type = NLA_U32, },
+       [SWDEV_ATTR_FLOW_ACTION_VLAN_PROTO]     = { .type = NLA_U16, },
+       [SWDEV_ATTR_FLOW_ACTION_VLAN_TCI]       = { .type = NLA_U16, },
+};
+
+static int swdev_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info)
+{
+       struct sk_buff *msg;
+       void *hdr;
+       int err;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+                         &swdev_nl_family, 0, SWDEV_CMD_NOOP);
+       if (!hdr) {
+               err = -EMSGSIZE;
+               goto err_msg_put;
+       }
+
+       genlmsg_end(msg, hdr);
+
+       return genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
+
+err_msg_put:
+       nlmsg_free(msg);
+
+       return err;
+}
+
+static int swdev_nl_parse_flow_key(struct nlattr *key_attr,
+                                  struct sw_flow_key *flow_key)
+{
+       struct nlattr *attrs[SWDEV_ATTR_FLOW_KEY_MAX + 1];
+       int err;
+
+       err = nla_parse_nested(attrs, SWDEV_ATTR_FLOW_KEY_MAX,
+                              key_attr, swdev_nl_flow_key_policy);
+       if (err)
+               return err;
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TUN_ID])
+               flow_key->tun_key.tun_id =
+                       nla_get_be64(attrs[SWDEV_ATTR_FLOW_KEY_TUN_ID]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_SRC])
+               flow_key->tun_key.ipv4_src =
+                       nla_get_be32(attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_SRC]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_DST])
+               flow_key->tun_key.ipv4_dst =
+                       nla_get_be32(attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_DST]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TUN_FLAGS])
+               flow_key->tun_key.tun_flags =
+                       nla_get_be16(attrs[SWDEV_ATTR_FLOW_KEY_TUN_FLAGS]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TOS])
+               flow_key->tun_key.ipv4_tos =
+                       nla_get_u8(attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TOS]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TTL])
+               flow_key->tun_key.ipv4_ttl =
+                       nla_get_u8(attrs[SWDEV_ATTR_FLOW_KEY_TUN_IPV4_TTL]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_PHY_PRIORITY])
+               flow_key->phy.priority =
+                       nla_get_u32(attrs[SWDEV_ATTR_FLOW_KEY_PHY_PRIORITY]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_PHY_IN_PORT])
+               flow_key->misc.in_port_ifindex =
+                       nla_get_u32(attrs[SWDEV_ATTR_FLOW_KEY_PHY_IN_PORT]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_ETH_SRC])
+               ether_addr_copy(flow_key->eth.src,
+                               nla_data(attrs[SWDEV_ATTR_FLOW_KEY_ETH_SRC]));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_ETH_DST])
+               ether_addr_copy(flow_key->eth.dst,
+                               nla_data(attrs[SWDEV_ATTR_FLOW_KEY_ETH_DST]));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_ETH_TCI])
+               flow_key->eth.tci =
+                       nla_get_be16(attrs[SWDEV_ATTR_FLOW_KEY_ETH_TCI]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_ETH_TYPE])
+               flow_key->eth.type =
+                       nla_get_be16(attrs[SWDEV_ATTR_FLOW_KEY_ETH_TYPE]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IP_PROTO])
+               flow_key->ip.proto =
+                       nla_get_u8(attrs[SWDEV_ATTR_FLOW_KEY_IP_PROTO]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IP_TOS])
+               flow_key->ip.tos =
+                       nla_get_u8(attrs[SWDEV_ATTR_FLOW_KEY_IP_TOS]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IP_TTL])
+               flow_key->ip.ttl =
+                       nla_get_u8(attrs[SWDEV_ATTR_FLOW_KEY_IP_TTL]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IP_FRAG])
+               flow_key->ip.frag =
+                       nla_get_u8(attrs[SWDEV_ATTR_FLOW_KEY_IP_FRAG]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TP_SRC])
+               flow_key->tp.src =
+                       nla_get_be16(attrs[SWDEV_ATTR_FLOW_KEY_TP_SRC]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TP_DST])
+               flow_key->tp.dst =
+                       nla_get_be16(attrs[SWDEV_ATTR_FLOW_KEY_TP_DST]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_TP_FLAGS])
+               flow_key->tp.flags =
+                       nla_get_be16(attrs[SWDEV_ATTR_FLOW_KEY_TP_FLAGS]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_SRC])
+               flow_key->ipv4.addr.src =
+                       nla_get_be32(attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_SRC]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_DST])
+               flow_key->ipv4.addr.dst =
+                       nla_get_be32(attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ADDR_DST]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ARP_SHA])
+               ether_addr_copy(flow_key->ipv4.arp.sha,
+                               
nla_data(attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ARP_SHA]));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ARP_THA])
+               ether_addr_copy(flow_key->ipv4.arp.tha,
+                               
nla_data(attrs[SWDEV_ATTR_FLOW_KEY_IPV4_ARP_THA]));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_SRC])
+               memcpy(&flow_key->ipv6.addr.src,
+                      nla_data(attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_SRC]),
+                      sizeof(flow_key->ipv6.addr.src));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_DST])
+               memcpy(&flow_key->ipv6.addr.dst,
+                      nla_data(attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ADDR_DST]),
+                      sizeof(flow_key->ipv6.addr.dst));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV6_LABEL])
+               flow_key->ipv6.label =
+                       nla_get_be32(attrs[SWDEV_ATTR_FLOW_KEY_IPV6_LABEL]);
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ND_TARGET])
+               memcpy(&flow_key->ipv6.nd.target,
+                      nla_data(attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ND_TARGET]),
+                      sizeof(flow_key->ipv6.nd.target));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ND_SLL])
+               ether_addr_copy(flow_key->ipv6.nd.sll,
+                               
nla_data(attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ND_SLL]));
+
+       if (attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ND_TLL])
+               ether_addr_copy(flow_key->ipv6.nd.tll,
+                               
nla_data(attrs[SWDEV_ATTR_FLOW_KEY_IPV6_ND_TLL]));
+
+       return 0;
+}
+
+static int swdev_nl_parse_flow_mask(struct nlattr *mask_attr,
+                                   struct sw_flow_mask **p_flow_mask)
+{
+       struct sw_flow_mask *flow_mask;
+       int err;
+
+       flow_mask = kzalloc(sizeof(*flow_mask), GFP_KERNEL);
+       if (!flow_mask)
+               return -ENOMEM;
+
+       err = swdev_nl_parse_flow_key(mask_attr, &flow_mask->key);
+       if (err)
+               goto out;
+       flow_mask->range.start = 0;
+       flow_mask->range.end = sizeof(flow_mask->key);
+
+       *p_flow_mask = flow_mask;
+       return 0;
+out:
+       kfree(flow_mask);
+       return err;
+}
+
+static int swdev_nl_parse_flow_action(struct nlattr *action_attr,
+                                     struct sw_flow_action *flow_action)
+{
+       struct nlattr *attrs[SWDEV_ATTR_FLOW_ACTION_MAX + 1];
+       int err;
+
+       err = nla_parse_nested(attrs, SWDEV_ATTR_FLOW_ACTION_MAX,
+                              action_attr, swdev_nl_flow_action_policy);
+       if (err)
+               return err;
+
+       if (!attrs[SWDEV_ATTR_FLOW_ACTION_TYPE])
+               return -EINVAL;
+
+       switch (nla_get_u32(attrs[SWDEV_ATTR_FLOW_ACTION_TYPE])) {
+       case SWDEV_FLOW_ACTION_TYPE_OUTPUT:
+               if (!attrs[SWDEV_ATTR_FLOW_ACTION_OUT_PORT])
+                       return -EINVAL;
+               flow_action->out_port_ifindex =
+                       nla_get_u32(attrs[SWDEV_ATTR_FLOW_ACTION_OUT_PORT]);
+               flow_action->type = SW_FLOW_ACTION_TYPE_OUTPUT;
+               break;
+       case SWDEV_FLOW_ACTION_TYPE_VLAN_PUSH:
+               if (!attrs[SWDEV_ATTR_FLOW_ACTION_VLAN_PROTO] ||
+                   !attrs[SWDEV_ATTR_FLOW_ACTION_VLAN_TCI])
+                       return -EINVAL;
+               flow_action->vlan.vlan_proto =
+                       nla_get_be16(attrs[SWDEV_ATTR_FLOW_ACTION_VLAN_PROTO]);
+               flow_action->vlan.vlan_tci =
+                       nla_get_u16(attrs[SWDEV_ATTR_FLOW_ACTION_VLAN_TCI]);
+               flow_action->type = SW_FLOW_ACTION_TYPE_VLAN_PUSH;
+               break;
+       case SWDEV_FLOW_ACTION_TYPE_VLAN_POP:
+               flow_action->type = SW_FLOW_ACTION_TYPE_VLAN_POP;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int swdev_nl_parse_flow_actions(struct nlattr *actions_attr,
+                                      struct sw_flow_actions **p_flow_actions)
+{
+       struct sw_flow_actions *flow_actions;
+       struct sw_flow_action *cur;
+       struct nlattr *action_attr;
+       int rem;
+       int count = 0;
+       int err;
+
+       nla_for_each_nested(action_attr, actions_attr, rem) {
+               if (nla_type(action_attr) != SWDEV_ATTR_FLOW_ITEM_ACTION)
+                       return -EINVAL;
+               count++;
+       }
+
+       flow_actions = kzalloc(sizeof(struct sw_flow_actions) +
+                              sizeof(struct sw_flow_action) * count,
+                              GFP_KERNEL);
+       if (!flow_actions)
+               return -ENOMEM;
+
+       cur = flow_actions->actions;
+       nla_for_each_nested(action_attr, actions_attr, rem) {
+               err = swdev_nl_parse_flow_action(action_attr, cur);
+               if (err)
+                       goto out;
+               cur++;
+       }
+
+       flow_actions->count = count;
+       *p_flow_actions = flow_actions;
+       return 0;
+out:
+       kfree(flow_actions);
+       return err;
+}
+
+static void swdev_nl_free_flow(struct sw_flow *flow)
+{
+       kfree(flow->actions);
+       kfree(flow->mask);
+       kfree(flow);
+}
+
+static int swdev_nl_parse_flow(struct nlattr *flow_attr, struct sw_flow 
**p_flow)
+{
+       struct sw_flow *flow;
+       struct nlattr *attrs[SWDEV_ATTR_FLOW_MAX + 1];
+       int err;
+
+       err = nla_parse_nested(attrs, SWDEV_ATTR_FLOW_MAX,
+                              flow_attr, swdev_nl_flow_policy);
+       if (err)
+               return err;
+
+       if (!attrs[SWDEV_ATTR_FLOW_KEY] || !attrs[SWDEV_ATTR_FLOW_MASK] ||
+           !attrs[SWDEV_ATTR_FLOW_LIST_ACTION])
+               return -EINVAL;
+
+       flow = kzalloc(sizeof(*flow), GFP_KERNEL);
+       if (!flow)
+               return -ENOMEM;
+
+       err = swdev_nl_parse_flow_key(attrs[SWDEV_ATTR_FLOW_KEY], &flow->key);
+       if (err)
+               goto out;
+
+       err = swdev_nl_parse_flow_mask(attrs[SWDEV_ATTR_FLOW_MASK], 
&flow->mask);
+       if (err)
+               goto out;
+
+       err = swdev_nl_parse_flow_actions(attrs[SWDEV_ATTR_FLOW_LIST_ACTION],
+                                         &flow->actions);
+       if (err)
+               goto out;
+
+       *p_flow = flow;
+       return 0;
+
+out:
+       kfree(flow);
+       return err;
+}
+
+static struct net_device *swdev_nl_dev_get(struct genl_info *info)
+{
+       struct net *net = genl_info_net(info);
+       int ifindex;
+
+       if (!info->attrs[SWDEV_ATTR_IFINDEX])
+               return NULL;
+
+       ifindex = nla_get_u32(info->attrs[SWDEV_ATTR_IFINDEX]);
+       return dev_get_by_index(net, ifindex);
+}
+
+static void swdev_nl_dev_put(struct net_device *dev)
+{
+       dev_put(dev);
+}
+
+static int swdev_nl_cmd_flow_insert(struct sk_buff *skb, struct genl_info 
*info)
+{
+       struct net_device *dev;
+       struct sw_flow *flow;
+       int err;
+
+       if (!info->attrs[SWDEV_ATTR_FLOW])
+               return -EINVAL;
+
+       dev = swdev_nl_dev_get(info);
+       if (!dev)
+               return -EINVAL;
+
+       err = swdev_nl_parse_flow(info->attrs[SWDEV_ATTR_FLOW], &flow);
+       if (err)
+               goto dev_put;
+
+       err = swdev_flow_insert(dev, flow);
+       swdev_nl_free_flow(flow);
+dev_put:
+       swdev_nl_dev_put(dev);
+       return err;
+}
+
+static int swdev_nl_cmd_flow_remove(struct sk_buff *skb, struct genl_info 
*info)
+{
+       struct net_device *dev;
+       struct sw_flow *flow;
+       int err;
+
+       if (!info->attrs[SWDEV_ATTR_FLOW])
+               return -EINVAL;
+
+       dev = swdev_nl_dev_get(info);
+       if (!dev)
+               return -EINVAL;
+
+       err = swdev_nl_parse_flow(info->attrs[SWDEV_ATTR_FLOW], &flow);
+       if (err)
+               goto dev_put;
+
+       err = swdev_flow_remove(dev, flow);
+       swdev_nl_free_flow(flow);
+dev_put:
+       swdev_nl_dev_put(dev);
+       return err;
+}
+
+static const struct genl_ops swdev_nl_ops[] = {
+       {
+               .cmd = SWDEV_CMD_NOOP,
+               .doit = swdev_nl_cmd_noop,
+       },
+       {
+               .cmd = SWDEV_CMD_FLOW_INSERT,
+               .doit = swdev_nl_cmd_flow_insert,
+               .policy = swdev_nl_flow_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = SWDEV_CMD_FLOW_REMOVE,
+               .doit = swdev_nl_cmd_flow_remove,
+               .policy = swdev_nl_flow_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+};
+
+static int __init swdev_nl_module_init(void)
+{
+       return genl_register_family_with_ops(&swdev_nl_family, swdev_nl_ops);
+}
+
+static void swdev_nl_module_fini(void)
+{
+       genl_unregister_family(&swdev_nl_family);
+}
+
+module_init(swdev_nl_module_init);
+module_exit(swdev_nl_module_fini);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Jiri Pirko <j...@resnulli.us>");
+MODULE_DESCRIPTION("Netlink interface to Switch device");
+MODULE_ALIAS_GENL_FAMILY(SWITCHDEV_GENL_NAME);
-- 
1.9.3

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to