From mboxrd@z Thu Jan 1 00:00:00 1970 From: Jiri Pirko Subject: Re: [patch net-next V3] net: introduce ethernet teaming device Date: Sun, 23 Oct 2011 19:21:31 +0200 Message-ID: <20111023172128.GA2134@minipsycho> References: <1319359253-1328-1-git-send-email-jpirko@redhat.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Cc: davem@davemloft.net To: netdev@vger.kernel.org Return-path: Received: from mx1.redhat.com ([209.132.183.28]:54888 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752918Ab1JWRVf (ORCPT ); Sun, 23 Oct 2011 13:21:35 -0400 Content-Disposition: inline In-Reply-To: <1319359253-1328-1-git-send-email-jpirko@redhat.com> Sender: netdev-owner@vger.kernel.org List-ID: Please scratch this. V4 will follow up. Sun, Oct 23, 2011 at 10:40:53AM CEST, jpirko@redhat.com wrote: >This patch introduces new network device called team. It supposes to be >very fast, simple, userspace-driven alternative to existing bonding >driver. > >Userspace library called libteam with couple of demo apps is available >here: >https://github.com/jpirko/libteam >Note it's still in its dipers atm. > >team<->libteam use generic netlink for communication. That and rtnl >suppose to be the only way to configure team device, no sysfs etc. > >Python binding basis for libteam was recently introduced (some need >still need to be done on it though). Daemon providing arpmon/miimon >active-backup functionality will be introduced shortly. >All what's necessary is already implemented in kernel team driver. > >Signed-off-by: Jiri Pirko > >v2->v3: > - team_change_mtu() user rcu version of list traversal to unwind > - set and clear of mode_ops happens per pointer, not per byte > - port hashlist changed to be embedded into team structure > - error branch in team_port_enter() does cleanup now > - fixed rtln->rtnl > >v1->v2: > - modes are made as modules. Makes team more modular and > extendable. > - several commenters' nitpicks found on v1 were fixed > - several other bugs were fixed. > - note I ignored Eric's comment about roundrobin port selector > as Eric's way may be easily implemented as another mode (mode > "random") in future. >--- > Documentation/networking/team.txt | 2 + > MAINTAINERS | 7 + > drivers/net/Kconfig | 2 + > drivers/net/Makefile | 1 + > drivers/net/team/Kconfig | 38 + > drivers/net/team/Makefile | 7 + > drivers/net/team/team.c | 1574 +++++++++++++++++++++++++++++ > drivers/net/team/team_mode_activebackup.c | 152 +++ > drivers/net/team/team_mode_roundrobin.c | 107 ++ > include/linux/Kbuild | 1 + > include/linux/if.h | 1 + > include/linux/if_team.h | 254 +++++ > include/linux/rculist.h | 14 + > 13 files changed, 2160 insertions(+), 0 deletions(-) > create mode 100644 Documentation/networking/team.txt > create mode 100644 drivers/net/team/Kconfig > create mode 100644 drivers/net/team/Makefile > create mode 100644 drivers/net/team/team.c > create mode 100644 drivers/net/team/team_mode_activebackup.c > create mode 100644 drivers/net/team/team_mode_roundrobin.c > create mode 100644 include/linux/if_team.h > >diff --git a/Documentation/networking/team.txt b/Documentation/networking/team.txt >new file mode 100644 >index 0000000..5a01368 >--- /dev/null >+++ b/Documentation/networking/team.txt >@@ -0,0 +1,2 @@ >+Team devices are driven from userspace via libteam library which is here: >+ https://github.com/jpirko/libteam >diff --git a/MAINTAINERS b/MAINTAINERS >index 5008b08..c33400d 100644 >--- a/MAINTAINERS >+++ b/MAINTAINERS >@@ -6372,6 +6372,13 @@ W: http://tcp-lp-mod.sourceforge.net/ > S: Maintained > F: net/ipv4/tcp_lp.c > >+TEAM DRIVER >+M: Jiri Pirko >+L: netdev@vger.kernel.org >+S: Supported >+F: drivers/net/team/ >+F: include/linux/if_team.h >+ > TEGRA SUPPORT > M: Colin Cross > M: Erik Gilling >diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig >index 583f66c..b3020be 100644 >--- a/drivers/net/Kconfig >+++ b/drivers/net/Kconfig >@@ -125,6 +125,8 @@ config IFB > 'ifb1' etc. > Look at the iproute2 documentation directory for usage etc > >+source "drivers/net/team/Kconfig" >+ > config MACVLAN > tristate "MAC-VLAN support (EXPERIMENTAL)" > depends on EXPERIMENTAL >diff --git a/drivers/net/Makefile b/drivers/net/Makefile >index fa877cd..4e4ebfe 100644 >--- a/drivers/net/Makefile >+++ b/drivers/net/Makefile >@@ -17,6 +17,7 @@ obj-$(CONFIG_NET) += Space.o loopback.o > obj-$(CONFIG_NETCONSOLE) += netconsole.o > obj-$(CONFIG_PHYLIB) += phy/ > obj-$(CONFIG_RIONET) += rionet.o >+obj-$(CONFIG_NET_TEAM) += team/ > obj-$(CONFIG_TUN) += tun.o > obj-$(CONFIG_VETH) += veth.o > obj-$(CONFIG_VIRTIO_NET) += virtio_net.o >diff --git a/drivers/net/team/Kconfig b/drivers/net/team/Kconfig >new file mode 100644 >index 0000000..70a43a6 >--- /dev/null >+++ b/drivers/net/team/Kconfig >@@ -0,0 +1,38 @@ >+menuconfig NET_TEAM >+ tristate "Ethernet team driver support (EXPERIMENTAL)" >+ depends on EXPERIMENTAL >+ ---help--- >+ This allows one to create virtual interfaces that teams together >+ multiple ethernet devices. >+ >+ Team devices can be added using the "ip" command from the >+ iproute2 package: >+ >+ "ip link add link [ address MAC ] [ NAME ] type team" >+ >+ To compile this driver as a module, choose M here: the module >+ will be called team. >+ >+if NET_TEAM >+ >+config NET_TEAM_MODE_ROUNDROBIN >+ tristate "Round-robin mode support" >+ depends on NET_TEAM >+ ---help--- >+ Basic mode where port used for transmitting packets is selected in >+ round-robin fashion using packet counter. >+ >+ To compile this team mode as a module, choose M here: the module >+ will be called team_mode_roundrobin. >+ >+config NET_TEAM_MODE_ACTIVEBACKUP >+ tristate "Active-backup mode support" >+ depends on NET_TEAM >+ ---help--- >+ Only one port is active at a time and the rest of ports are used >+ for backup. >+ >+ To compile this team mode as a module, choose M here: the module >+ will be called team_mode_activebackup. >+ >+endif # NET_TEAM >diff --git a/drivers/net/team/Makefile b/drivers/net/team/Makefile >new file mode 100644 >index 0000000..85f2028 >--- /dev/null >+++ b/drivers/net/team/Makefile >@@ -0,0 +1,7 @@ >+# >+# Makefile for the network team driver >+# >+ >+obj-$(CONFIG_NET_TEAM) += team.o >+obj-$(CONFIG_NET_TEAM_MODE_ROUNDROBIN) += team_mode_roundrobin.o >+obj-$(CONFIG_NET_TEAM_MODE_ACTIVEBACKUP) += team_mode_activebackup.o >diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c >new file mode 100644 >index 0000000..8004916 >--- /dev/null >+++ b/drivers/net/team/team.c >@@ -0,0 +1,1574 @@ >+/* >+ * net/drivers/team/team.c - Network team device driver >+ * Copyright (c) 2011 Jiri Pirko >+ * >+ * 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 >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+ >+#define DRV_NAME "team" >+ >+ >+/********** >+ * Helpers >+ **********/ >+ >+#define team_port_exists(dev) (dev->priv_flags & IFF_TEAM_PORT) >+ >+static struct team_port *team_port_get_rcu(const struct net_device *dev) >+{ >+ struct team_port *port = rcu_dereference(dev->rx_handler_data); >+ >+ return team_port_exists(dev) ? port : NULL; >+} >+ >+static struct team_port *team_port_get_rtnl(const struct net_device *dev) >+{ >+ struct team_port *port = rtnl_dereference(dev->rx_handler_data); >+ >+ return team_port_exists(dev) ? port : NULL; >+} >+ >+/* >+ * Since the ability to change mac address for open port device is tested in >+ * team_port_add, this function can be called without control of return value >+ */ >+static int __set_port_mac(struct net_device *port_dev, >+ const unsigned char *dev_addr) >+{ >+ struct sockaddr addr; >+ >+ memcpy(addr.sa_data, dev_addr, ETH_ALEN); >+ addr.sa_family = ARPHRD_ETHER; >+ return dev_set_mac_address(port_dev, &addr); >+} >+ >+int team_port_set_orig_mac(struct team_port *port) >+{ >+ return __set_port_mac(port->dev, port->orig.dev_addr); >+} >+EXPORT_SYMBOL(team_port_set_orig_mac); >+ >+int team_port_set_team_mac(struct team_port *port) >+{ >+ return __set_port_mac(port->dev, port->team->dev->dev_addr); >+} >+EXPORT_SYMBOL(team_port_set_team_mac); >+ >+ >+/******************* >+ * Options handling >+ *******************/ >+ >+void team_options_register(struct team *team, struct team_option *option, >+ size_t option_count) >+{ >+ int i; >+ >+ for (i = 0; i < option_count; i++, option++) >+ list_add_tail(&option->list, &team->option_list); >+} >+EXPORT_SYMBOL(team_options_register); >+ >+static void __team_options_change_check(struct team *team, >+ struct team_option *changed_option); >+ >+static void __team_options_unregister(struct team *team, >+ struct team_option *option, >+ size_t option_count) >+{ >+ int i; >+ >+ for (i = 0; i < option_count; i++, option++) >+ list_del(&option->list); >+} >+ >+void team_options_unregister(struct team *team, struct team_option *option, >+ size_t option_count) >+{ >+ __team_options_unregister(team, option, option_count); >+ __team_options_change_check(team, NULL); >+} >+EXPORT_SYMBOL(team_options_unregister); >+ >+static int team_option_get(struct team *team, struct team_option *option, >+ void *arg) >+{ >+ return option->getter(team, arg); >+} >+ >+static int team_option_set(struct team *team, struct team_option *option, >+ void *arg) >+{ >+ int err; >+ >+ err = option->setter(team, arg); >+ if (err) >+ return err; >+ >+ __team_options_change_check(team, option); >+ return err; >+} >+ >+/**************** >+ * Mode handling >+ ****************/ >+ >+static LIST_HEAD(mode_list); >+static DEFINE_SPINLOCK(mode_list_lock); >+ >+static struct team_mode *__find_mode(const char *kind) >+{ >+ struct team_mode *mode; >+ >+ list_for_each_entry(mode, &mode_list, list) { >+ if (strcmp(mode->kind, kind) == 0) >+ return mode; >+ } >+ return NULL; >+} >+ >+static bool is_good_mode_name(const char *name) >+{ >+ while (*name != '\0') { >+ if (!isalpha(*name) && !isdigit(*name) && *name != '_') >+ return false; >+ name++; >+ } >+ return true; >+} >+ >+int team_mode_register(struct team_mode *mode) >+{ >+ int err = 0; >+ >+ if (!is_good_mode_name(mode->kind) || >+ mode->priv_size > TEAM_MODE_PRIV_SIZE) >+ return -EINVAL; >+ spin_lock(&mode_list_lock); >+ if (__find_mode(mode->kind)) { >+ err = -EEXIST; >+ goto unlock; >+ } >+ list_add_tail(&mode->list, &mode_list); >+unlock: >+ spin_unlock(&mode_list_lock); >+ return err; >+} >+EXPORT_SYMBOL(team_mode_register); >+ >+int team_mode_unregister(struct team_mode *mode) >+{ >+ spin_lock(&mode_list_lock); >+ list_del_init(&mode->list); >+ spin_unlock(&mode_list_lock); >+ return 0; >+} >+EXPORT_SYMBOL(team_mode_unregister); >+ >+static struct team_mode *team_mode_get(const char *kind) >+{ >+ struct team_mode *mode; >+ >+ spin_lock(&mode_list_lock); >+ mode = __find_mode(kind); >+ if (!mode) { >+ spin_unlock(&mode_list_lock); >+ request_module("team-mode-%s", kind); >+ spin_lock(&mode_list_lock); >+ mode = __find_mode(kind); >+ } >+ if (mode) >+ if (!try_module_get(mode->owner)) >+ mode = NULL; >+ >+ spin_unlock(&mode_list_lock); >+ return mode; >+} >+ >+static void team_mode_put(const char *kind) >+{ >+ struct team_mode *mode; >+ >+ spin_lock(&mode_list_lock); >+ mode = __find_mode(kind); >+ BUG_ON(!mode); >+ module_put(mode->owner); >+ spin_unlock(&mode_list_lock); >+} >+ >+/* >+ * We can benefit from the fact that it's ensured no port is present >+ * at the time of mode change. >+ */ >+static int __team_change_mode(struct team *team, >+ const struct team_mode *new_mode) >+{ >+ /* Check if mode was previously set and do cleanup if so */ >+ if (team->mode_kind) { >+ void (*exit_op)(struct team *team) = team->mode_ops.exit; >+ >+ /* Clear ops area so no callback is called any longer */ >+ team_mode_ops_clear(&team->mode_ops); >+ >+ synchronize_rcu(); >+ >+ if (exit_op) >+ exit_op(team); >+ team_mode_put(team->mode_kind); >+ team->mode_kind = NULL; >+ /* zero private data area */ >+ memset(&team->mode_priv, 0, >+ sizeof(struct team) - offsetof(struct team, mode_priv)); >+ } >+ >+ if (!new_mode) >+ return 0; >+ >+ if (new_mode->ops->init) { >+ int err; >+ >+ err = new_mode->ops->init(team); >+ if (err) >+ return err; >+ } >+ >+ team->mode_kind = new_mode->kind; >+ team_mode_ops_copy(&team->mode_ops, new_mode->ops); >+ >+ return 0; >+} >+ >+static int team_change_mode(struct team *team, const char *kind) >+{ >+ struct team_mode *new_mode; >+ struct net_device *dev = team->dev; >+ int err; >+ >+ if (!list_empty(&team->port_list)) { >+ netdev_err(dev, "No ports can be present during mode change\n"); >+ return -EBUSY; >+ } >+ >+ if (team->mode_kind && strcmp(team->mode_kind, kind) == 0) { >+ netdev_err(dev, "Unable to change to the same mode the team is in\n"); >+ return -EINVAL; >+ } >+ >+ new_mode = team_mode_get(kind); >+ if (!new_mode) { >+ netdev_err(dev, "Mode \"%s\" not found\n", kind); >+ return -EINVAL; >+ } >+ >+ err = __team_change_mode(team, new_mode); >+ if (err) { >+ netdev_err(dev, "Failed to change to mode \"%s\"\n", kind); >+ team_mode_put(kind); >+ return err; >+ } >+ >+ netdev_info(dev, "Mode changed to \"%s\"\n", kind); >+ return 0; >+} >+ >+ >+/************************ >+ * Rx path frame handler >+ ************************/ >+ >+/* note: already called with rcu_read_lock */ >+static rx_handler_result_t team_handle_frame(struct sk_buff **pskb) >+{ >+ struct sk_buff *skb = *pskb; >+ struct team_port *port; >+ struct team *team; >+ rx_handler_result_t res = RX_HANDLER_ANOTHER; >+ >+ skb = skb_share_check(skb, GFP_ATOMIC); >+ if (!skb) >+ return RX_HANDLER_CONSUMED; >+ >+ *pskb = skb; >+ >+ port = team_port_get_rcu(skb->dev); >+ team = port->team; >+ >+ if (team->mode_ops.receive) >+ res = team->mode_ops.receive(team, port, skb); >+ >+ if (res == RX_HANDLER_ANOTHER) { >+ struct team_pcpu_stats *pcpu_stats; >+ >+ pcpu_stats = this_cpu_ptr(team->pcpu_stats); >+ u64_stats_update_begin(&pcpu_stats->syncp); >+ pcpu_stats->rx_packets++; >+ pcpu_stats->rx_bytes += skb->len; >+ if (skb->pkt_type == PACKET_MULTICAST) >+ pcpu_stats->rx_multicast++; >+ u64_stats_update_end(&pcpu_stats->syncp); >+ >+ skb->dev = team->dev; >+ } else { >+ this_cpu_inc(team->pcpu_stats->rx_dropped); >+ } >+ >+ return res; >+} >+ >+ >+/**************** >+ * Port handling >+ ****************/ >+ >+static bool team_port_find(const struct team *team, >+ const struct team_port *port) >+{ >+ struct team_port *cur; >+ >+ list_for_each_entry(cur, &team->port_list, list) >+ if (cur == port) >+ return true; >+ return false; >+} >+ >+/* >+ * Add/delete port to the team port list. Write guarded by rtnl_lock. >+ * Takes care of correct port->index setup (might be racy). >+ */ >+static void team_port_list_add_port(struct team *team, >+ struct team_port *port) >+{ >+ port->index = team->port_count++; >+ hlist_add_head_rcu(&port->hlist, >+ team_port_index_hash(team, port->index)); >+ list_add_tail_rcu(&port->list, &team->port_list); >+} >+ >+static void __reconstruct_port_hlist(struct team *team, int rm_index) >+{ >+ int i; >+ struct team_port *port; >+ >+ for (i = rm_index + 1; i < team->port_count; i++) { >+ port = team_get_port_by_index_rcu(team, i); >+ hlist_del_rcu(&port->hlist); >+ port->index--; >+ hlist_add_head_rcu(&port->hlist, >+ team_port_index_hash(team, port->index)); >+ } >+} >+ >+static void team_port_list_del_port(struct team *team, >+ struct team_port *port) >+{ >+ int rm_index = port->index; >+ >+ hlist_del_rcu(&port->hlist); >+ list_del_rcu(&port->list); >+ __reconstruct_port_hlist(team, rm_index); >+ team->port_count--; >+} >+ >+#define TEAM_VLAN_FEATURES (NETIF_F_ALL_CSUM | NETIF_F_SG | \ >+ NETIF_F_FRAGLIST | NETIF_F_ALL_TSO | \ >+ NETIF_F_HIGHDMA | NETIF_F_LRO) >+ >+static void __team_compute_features(struct team *team) >+{ >+ struct team_port *port; >+ u32 vlan_features = TEAM_VLAN_FEATURES; >+ unsigned short max_hard_header_len = ETH_HLEN; >+ >+ list_for_each_entry(port, &team->port_list, list) { >+ vlan_features = netdev_increment_features(vlan_features, >+ port->dev->vlan_features, >+ TEAM_VLAN_FEATURES); >+ >+ if (port->dev->hard_header_len > max_hard_header_len) >+ max_hard_header_len = port->dev->hard_header_len; >+ } >+ >+ team->dev->vlan_features = vlan_features; >+ team->dev->hard_header_len = max_hard_header_len; >+ >+ netdev_change_features(team->dev); >+} >+ >+static void team_compute_features(struct team *team) >+{ >+ spin_lock(&team->lock); >+ __team_compute_features(team); >+ spin_unlock(&team->lock); >+} >+ >+static int team_port_enter(struct team *team, struct team_port *port) >+{ >+ int err = 0; >+ >+ dev_hold(team->dev); >+ port->dev->priv_flags |= IFF_TEAM_PORT; >+ if (team->mode_ops.port_enter) { >+ err = team->mode_ops.port_enter(team, port); >+ if (err) { >+ netdev_err(team->dev, "Device %s failed to enter team mode\n", >+ port->dev->name); >+ goto err_port_enter; >+ } >+ } >+ >+ return 0; >+ >+err_port_enter: >+ port->dev->priv_flags &= ~IFF_TEAM_PORT; >+ dev_put(team->dev); >+ >+ return err; >+} >+ >+static void team_port_leave(struct team *team, struct team_port *port) >+{ >+ if (team->mode_ops.port_leave) >+ team->mode_ops.port_leave(team, port); >+ port->dev->priv_flags &= ~IFF_TEAM_PORT; >+ dev_put(team->dev); >+} >+ >+static void __team_port_change_check(struct team_port *port, bool linkup); >+ >+static int team_port_add(struct team *team, struct net_device *port_dev) >+{ >+ struct net_device *dev = team->dev; >+ struct team_port *port; >+ char *portname = port_dev->name; >+ char tmp_addr[ETH_ALEN]; >+ int err; >+ >+ if (port_dev->flags & IFF_LOOPBACK || >+ port_dev->type != ARPHRD_ETHER) { >+ netdev_err(dev, "Device %s is of an unsupported type\n", >+ portname); >+ return -EINVAL; >+ } >+ >+ if (team_port_exists(port_dev)) { >+ netdev_err(dev, "Device %s is already a port " >+ "of a team device\n", portname); >+ return -EBUSY; >+ } >+ >+ if (port_dev->flags & IFF_UP) { >+ netdev_err(dev, "Device %s is up. Set it down before adding it as a team port\n", >+ portname); >+ return -EBUSY; >+ } >+ >+ port = kzalloc(sizeof(struct team_port), GFP_KERNEL); >+ if (!port) >+ return -ENOMEM; >+ >+ port->dev = port_dev; >+ port->team = team; >+ >+ port->orig.mtu = port_dev->mtu; >+ err = dev_set_mtu(port_dev, dev->mtu); >+ if (err) { >+ netdev_dbg(dev, "Error %d calling dev_set_mtu\n", err); >+ goto err_set_mtu; >+ } >+ >+ memcpy(port->orig.dev_addr, port_dev->dev_addr, ETH_ALEN); >+ random_ether_addr(tmp_addr); >+ err = __set_port_mac(port_dev, tmp_addr); >+ if (err) { >+ netdev_dbg(dev, "Device %s mac addr set failed\n", >+ portname); >+ goto err_set_mac_rand; >+ } >+ >+ err = dev_open(port_dev); >+ if (err) { >+ netdev_dbg(dev, "Device %s opening failed\n", >+ portname); >+ goto err_dev_open; >+ } >+ >+ err = team_port_set_orig_mac(port); >+ if (err) { >+ netdev_dbg(dev, "Device %s mac addr set failed - Device does not support addr change when it's opened\n", >+ portname); >+ goto err_set_mac_opened; >+ } >+ >+ err = team_port_enter(team, port); >+ if (err) { >+ netdev_err(dev, "Device %s failed to enter team mode\n", >+ portname); >+ goto err_port_enter; >+ } >+ >+ err = netdev_set_master(port_dev, dev); >+ if (err) { >+ netdev_err(dev, "Device %s failed to set master\n", portname); >+ goto err_set_master; >+ } >+ >+ err = netdev_rx_handler_register(port_dev, team_handle_frame, >+ port); >+ if (err) { >+ netdev_err(dev, "Device %s failed to register rx_handler\n", >+ portname); >+ goto err_handler_register; >+ } >+ >+ team_port_list_add_port(team, port); >+ __team_compute_features(team); >+ __team_port_change_check(port, !!netif_carrier_ok(port_dev)); >+ >+ netdev_info(dev, "Port device %s added\n", portname); >+ >+ return 0; >+ >+err_handler_register: >+ netdev_set_master(port_dev, NULL); >+ >+err_set_master: >+ team_port_leave(team, port); >+ >+err_port_enter: >+err_set_mac_opened: >+ dev_close(port_dev); >+ >+err_dev_open: >+ team_port_set_orig_mac(port); >+ >+err_set_mac_rand: >+ dev_set_mtu(port_dev, port->orig.mtu); >+ >+err_set_mtu: >+ kfree(port); >+ >+ return err; >+} >+ >+static int team_port_del(struct team *team, struct net_device *port_dev) >+{ >+ struct net_device *dev = team->dev; >+ struct team_port *port; >+ char *portname = port_dev->name; >+ >+ port = team_port_get_rtnl(port_dev); >+ if (!port || !team_port_find(team, port)) { >+ netdev_err(dev, "Device %s does not act as a port of this team\n", >+ portname); >+ return -ENOENT; >+ } >+ >+ __team_port_change_check(port, false); >+ team_port_list_del_port(team, port); >+ netdev_rx_handler_unregister(port_dev); >+ netdev_set_master(port_dev, NULL); >+ team_port_leave(team, port); >+ dev_close(port_dev); >+ team_port_set_orig_mac(port); >+ dev_set_mtu(port_dev, port->orig.mtu); >+ synchronize_rcu(); >+ kfree(port); >+ netdev_info(dev, "Port device %s removed\n", portname); >+ __team_compute_features(team); >+ >+ return 0; >+} >+ >+ >+/***************** >+ * Net device ops >+ *****************/ >+ >+static const char team_no_mode_kind[] = "*NOMODE*"; >+ >+static int team_mode_option_get(struct team *team, void *arg) >+{ >+ const char **str = arg; >+ >+ *str = team->mode_kind ? team->mode_kind : team_no_mode_kind; >+ return 0; >+} >+ >+static int team_mode_option_set(struct team *team, void *arg) >+{ >+ const char **str = arg; >+ >+ return team_change_mode(team, *str); >+} >+ >+static struct team_option team_options[] = { >+ { >+ .name = "mode", >+ .type = TEAM_OPTION_TYPE_STRING, >+ .getter = team_mode_option_get, >+ .setter = team_mode_option_set, >+ }, >+}; >+ >+static int team_init(struct net_device *dev) >+{ >+ struct team *team = netdev_priv(dev); >+ int i; >+ >+ team->dev = dev; >+ spin_lock_init(&team->lock); >+ >+ team->pcpu_stats = alloc_percpu(struct team_pcpu_stats); >+ if (!team->pcpu_stats) >+ return -ENOMEM; >+ >+ for (i = 0; i < TEAM_PORT_HASHENTRIES; i++) >+ INIT_HLIST_HEAD(&team->port_hlist[i]); >+ INIT_LIST_HEAD(&team->port_list); >+ >+ INIT_LIST_HEAD(&team->option_list); >+ team_options_register(team, team_options, ARRAY_SIZE(team_options)); >+ netif_carrier_off(dev); >+ >+ return 0; >+} >+ >+static void team_uninit(struct net_device *dev) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_port *port; >+ struct team_port *tmp; >+ >+ spin_lock(&team->lock); >+ list_for_each_entry_safe(port, tmp, &team->port_list, list) >+ team_port_del(team, port->dev); >+ >+ __team_change_mode(team, NULL); /* cleanup */ >+ __team_options_unregister(team, team_options, ARRAY_SIZE(team_options)); >+ spin_unlock(&team->lock); >+} >+ >+static void team_destructor(struct net_device *dev) >+{ >+ struct team *team = netdev_priv(dev); >+ >+ free_percpu(team->pcpu_stats); >+ free_netdev(dev); >+} >+ >+static int team_open(struct net_device *dev) >+{ >+ netif_carrier_on(dev); >+ return 0; >+} >+ >+static int team_close(struct net_device *dev) >+{ >+ netif_carrier_off(dev); >+ return 0; >+} >+ >+/* >+ * note: already called with rcu_read_lock >+ */ >+static netdev_tx_t team_xmit(struct sk_buff *skb, struct net_device *dev) >+{ >+ struct team *team = netdev_priv(dev); >+ bool tx_success = false; >+ unsigned int len = skb->len; >+ >+ /* >+ * Ensure transmit function is called only in case there is at least >+ * one port present. >+ */ >+ if (likely(!list_empty(&team->port_list) && team->mode_ops.transmit)) >+ tx_success = team->mode_ops.transmit(team, skb); >+ if (tx_success) { >+ struct team_pcpu_stats *pcpu_stats; >+ >+ pcpu_stats = this_cpu_ptr(team->pcpu_stats); >+ u64_stats_update_begin(&pcpu_stats->syncp); >+ pcpu_stats->tx_packets++; >+ pcpu_stats->tx_bytes += len; >+ u64_stats_update_end(&pcpu_stats->syncp); >+ } else { >+ this_cpu_inc(team->pcpu_stats->tx_dropped); >+ } >+ >+ return NETDEV_TX_OK; >+} >+ >+static void team_change_rx_flags(struct net_device *dev, int change) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_port *port; >+ int inc; >+ >+ rcu_read_lock(); >+ list_for_each_entry_rcu(port, &team->port_list, list) { >+ if (change & IFF_PROMISC) { >+ inc = dev->flags & IFF_PROMISC ? 1 : -1; >+ dev_set_promiscuity(port->dev, inc); >+ } >+ if (change & IFF_ALLMULTI) { >+ inc = dev->flags & IFF_ALLMULTI ? 1 : -1; >+ dev_set_allmulti(port->dev, inc); >+ } >+ } >+ rcu_read_unlock(); >+} >+ >+static void team_set_rx_mode(struct net_device *dev) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_port *port; >+ >+ rcu_read_lock(); >+ list_for_each_entry_rcu(port, &team->port_list, list) { >+ dev_uc_sync(port->dev, dev); >+ dev_mc_sync(port->dev, dev); >+ } >+ rcu_read_unlock(); >+} >+ >+static int team_set_mac_address(struct net_device *dev, void *p) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_port *port; >+ struct sockaddr *addr = p; >+ >+ memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN); >+ rcu_read_lock(); >+ list_for_each_entry_rcu(port, &team->port_list, list) >+ if (team->mode_ops.port_change_mac) >+ team->mode_ops.port_change_mac(team, port); >+ rcu_read_unlock(); >+ return 0; >+} >+ >+static int team_change_mtu(struct net_device *dev, int new_mtu) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_port *port; >+ int err; >+ >+ rcu_read_lock(); >+ list_for_each_entry_rcu(port, &team->port_list, list) { >+ err = dev_set_mtu(port->dev, new_mtu); >+ if (err) { >+ netdev_err(dev, "Device %s failed to change mtu", >+ port->dev->name); >+ goto unwind; >+ } >+ } >+ rcu_read_unlock(); >+ >+ dev->mtu = new_mtu; >+ >+ return 0; >+ >+unwind: >+ list_for_each_entry_continue_reverse_rcu(port, &team->port_list, list) >+ dev_set_mtu(port->dev, dev->mtu); >+ >+ rcu_read_unlock(); >+ return err; >+} >+ >+static struct rtnl_link_stats64 * >+team_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_pcpu_stats *p; >+ u64 rx_packets, rx_bytes, rx_multicast, tx_packets, tx_bytes; >+ u32 rx_dropped = 0, tx_dropped = 0; >+ unsigned int start; >+ int i; >+ >+ for_each_possible_cpu(i) { >+ p = per_cpu_ptr(team->pcpu_stats, i); >+ do { >+ start = u64_stats_fetch_begin_bh(&p->syncp); >+ rx_packets = p->rx_packets; >+ rx_bytes = p->rx_bytes; >+ rx_multicast = p->rx_multicast; >+ tx_packets = p->tx_packets; >+ tx_bytes = p->tx_bytes; >+ } while (u64_stats_fetch_retry_bh(&p->syncp, start)); >+ >+ stats->rx_packets += rx_packets; >+ stats->rx_bytes += rx_bytes; >+ stats->multicast += rx_multicast; >+ stats->tx_packets += tx_packets; >+ stats->tx_bytes += tx_bytes; >+ /* >+ * rx_dropped & tx_dropped are u32, updated >+ * without syncp protection. >+ */ >+ rx_dropped += p->rx_dropped; >+ tx_dropped += p->tx_dropped; >+ } >+ stats->rx_dropped = rx_dropped; >+ stats->tx_dropped = tx_dropped; >+ return stats; >+} >+ >+static void team_vlan_rx_add_vid(struct net_device *dev, uint16_t vid) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_port *port; >+ >+ rcu_read_lock(); >+ list_for_each_entry_rcu(port, &team->port_list, list) { >+ const struct net_device_ops *ops = port->dev->netdev_ops; >+ >+ ops->ndo_vlan_rx_add_vid(port->dev, vid); >+ } >+ rcu_read_unlock(); >+} >+ >+static void team_vlan_rx_kill_vid(struct net_device *dev, uint16_t vid) >+{ >+ struct team *team = netdev_priv(dev); >+ struct team_port *port; >+ >+ rcu_read_lock(); >+ list_for_each_entry_rcu(port, &team->port_list, list) { >+ const struct net_device_ops *ops = port->dev->netdev_ops; >+ >+ ops->ndo_vlan_rx_kill_vid(port->dev, vid); >+ } >+ rcu_read_unlock(); >+} >+ >+static int team_add_slave(struct net_device *dev, struct net_device *port_dev) >+{ >+ struct team *team = netdev_priv(dev); >+ int err; >+ >+ spin_lock(&team->lock); >+ err = team_port_add(team, port_dev); >+ spin_unlock(&team->lock); >+ return err; >+} >+ >+static int team_del_slave(struct net_device *dev, struct net_device *port_dev) >+{ >+ struct team *team = netdev_priv(dev); >+ int err; >+ >+ spin_lock(&team->lock); >+ err = team_port_del(team, port_dev); >+ spin_unlock(&team->lock); >+ return err; >+} >+ >+static const struct net_device_ops team_netdev_ops = { >+ .ndo_init = team_init, >+ .ndo_uninit = team_uninit, >+ .ndo_open = team_open, >+ .ndo_stop = team_close, >+ .ndo_start_xmit = team_xmit, >+ .ndo_change_rx_flags = team_change_rx_flags, >+ .ndo_set_rx_mode = team_set_rx_mode, >+ .ndo_set_mac_address = team_set_mac_address, >+ .ndo_change_mtu = team_change_mtu, >+ .ndo_get_stats64 = team_get_stats64, >+ .ndo_vlan_rx_add_vid = team_vlan_rx_add_vid, >+ .ndo_vlan_rx_kill_vid = team_vlan_rx_kill_vid, >+ .ndo_add_slave = team_add_slave, >+ .ndo_del_slave = team_del_slave, >+}; >+ >+ >+/*********************** >+ * rt netlink interface >+ ***********************/ >+ >+static void team_setup(struct net_device *dev) >+{ >+ ether_setup(dev); >+ >+ dev->netdev_ops = &team_netdev_ops; >+ dev->destructor = team_destructor; >+ dev->tx_queue_len = 0; >+ dev->flags |= IFF_MULTICAST; >+ dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_TX_SKB_SHARING); >+ >+ /* >+ * Indicate we support unicast address filtering. That way core won't >+ * bring us to promisc mode in case a unicast addr is added. >+ * Let this up to underlay drivers. >+ */ >+ dev->priv_flags |= IFF_UNICAST_FLT; >+ >+ dev->features |= NETIF_F_LLTX; >+ dev->features |= NETIF_F_GRO; >+ dev->hw_features = NETIF_F_HW_VLAN_TX | >+ NETIF_F_HW_VLAN_RX | >+ NETIF_F_HW_VLAN_FILTER; >+ >+ dev->features |= dev->hw_features; >+} >+ >+static int team_newlink(struct net *src_net, struct net_device *dev, >+ struct nlattr *tb[], struct nlattr *data[]) >+{ >+ int err; >+ >+ if (tb[IFLA_ADDRESS] == NULL) >+ random_ether_addr(dev->dev_addr); >+ >+ err = register_netdevice(dev); >+ if (err) >+ return err; >+ >+ return 0; >+} >+ >+static int team_validate(struct nlattr *tb[], struct nlattr *data[]) >+{ >+ if (tb[IFLA_ADDRESS]) { >+ if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) >+ return -EINVAL; >+ if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) >+ return -EADDRNOTAVAIL; >+ } >+ return 0; >+} >+ >+static struct rtnl_link_ops team_link_ops __read_mostly = { >+ .kind = DRV_NAME, >+ .priv_size = sizeof(struct team), >+ .setup = team_setup, >+ .newlink = team_newlink, >+ .validate = team_validate, >+}; >+ >+ >+/*********************************** >+ * Generic netlink custom interface >+ ***********************************/ >+ >+static struct genl_family team_nl_family = { >+ .id = GENL_ID_GENERATE, >+ .name = TEAM_GENL_NAME, >+ .version = TEAM_GENL_VERSION, >+ .maxattr = TEAM_ATTR_MAX, >+ .netnsok = true, >+}; >+ >+static const struct nla_policy team_nl_policy[TEAM_ATTR_MAX + 1] = { >+ [TEAM_ATTR_UNSPEC] = { .type = NLA_UNSPEC, }, >+ [TEAM_ATTR_TEAM_IFINDEX] = { .type = NLA_U32 }, >+ [TEAM_ATTR_LIST_OPTION] = { .type = NLA_NESTED }, >+ [TEAM_ATTR_LIST_PORT] = { .type = NLA_NESTED }, >+}; >+ >+static const struct nla_policy >+team_nl_option_policy[TEAM_ATTR_OPTION_MAX + 1] = { >+ [TEAM_ATTR_OPTION_UNSPEC] = { .type = NLA_UNSPEC, }, >+ [TEAM_ATTR_OPTION_NAME] = { >+ .type = NLA_STRING, >+ .len = TEAM_STRING_MAX_LEN, >+ }, >+ [TEAM_ATTR_OPTION_CHANGED] = { .type = NLA_FLAG }, >+ [TEAM_ATTR_OPTION_TYPE] = { .type = NLA_U8 }, >+ [TEAM_ATTR_OPTION_DATA] = { >+ .type = NLA_BINARY, >+ .len = TEAM_STRING_MAX_LEN, >+ }, >+}; >+ >+static int team_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info) >+{ >+ struct sk_buff *msg; >+ void *hdr; >+ int err; >+ >+ msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); >+ if (!msg) >+ return -ENOMEM; >+ >+ hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, >+ &team_nl_family, 0, TEAM_CMD_NOOP); >+ if (IS_ERR(hdr)) { >+ err = PTR_ERR(hdr); >+ goto err_msg_put; >+ } >+ >+ genlmsg_end(msg, hdr); >+ >+ return genlmsg_unicast(genl_info_net(info), msg, info->snd_pid); >+ >+err_msg_put: >+ nlmsg_free(msg); >+ >+ return err; >+} >+ >+/* >+ * Netlink cmd functions should be locked by following two functions. >+ * To ensure team_uninit would not be called in between, hold rcu_read_lock >+ * all the time. >+ */ >+static struct team *team_nl_team_get(struct genl_info *info) >+{ >+ struct net *net = genl_info_net(info); >+ int ifindex; >+ struct net_device *dev; >+ struct team *team; >+ >+ if (!info->attrs[TEAM_ATTR_TEAM_IFINDEX]) >+ return NULL; >+ >+ ifindex = nla_get_u32(info->attrs[TEAM_ATTR_TEAM_IFINDEX]); >+ rcu_read_lock(); >+ dev = dev_get_by_index_rcu(net, ifindex); >+ if (!dev || dev->netdev_ops != &team_netdev_ops) { >+ rcu_read_unlock(); >+ return NULL; >+ } >+ >+ team = netdev_priv(dev); >+ spin_lock(&team->lock); >+ return team; >+} >+ >+static void team_nl_team_put(struct team *team) >+{ >+ spin_unlock(&team->lock); >+ rcu_read_unlock(); >+} >+ >+static int team_nl_send_generic(struct genl_info *info, struct team *team, >+ int (*fill_func)(struct sk_buff *skb, >+ struct genl_info *info, >+ int flags, struct team *team)) >+{ >+ struct sk_buff *skb; >+ int err; >+ >+ skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); >+ if (!skb) >+ return -ENOMEM; >+ >+ err = fill_func(skb, info, NLM_F_ACK, team); >+ if (err < 0) >+ goto err_fill; >+ >+ err = genlmsg_unicast(genl_info_net(info), skb, info->snd_pid); >+ return err; >+ >+err_fill: >+ nlmsg_free(skb); >+ return err; >+} >+ >+static int team_nl_fill_options_get_changed(struct sk_buff *skb, >+ u32 pid, u32 seq, int flags, >+ struct team *team, >+ struct team_option *changed_option) >+{ >+ struct nlattr *option_list; >+ void *hdr; >+ struct team_option *option; >+ >+ hdr = genlmsg_put(skb, pid, seq, &team_nl_family, flags, >+ TEAM_CMD_OPTIONS_GET); >+ if (IS_ERR(hdr)) >+ return PTR_ERR(hdr); >+ >+ NLA_PUT_U32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex); >+ option_list = nla_nest_start(skb, TEAM_ATTR_LIST_OPTION); >+ if (!option_list) >+ return -EMSGSIZE; >+ >+ list_for_each_entry(option, &team->option_list, list) { >+ struct nlattr *option_item; >+ long arg; >+ >+ option_item = nla_nest_start(skb, TEAM_ATTR_ITEM_OPTION); >+ if (!option_item) >+ goto nla_put_failure; >+ NLA_PUT_STRING(skb, TEAM_ATTR_OPTION_NAME, option->name); >+ if (option == changed_option) >+ NLA_PUT_FLAG(skb, TEAM_ATTR_OPTION_CHANGED); >+ switch (option->type) { >+ case TEAM_OPTION_TYPE_U32: >+ NLA_PUT_U8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32); >+ team_option_get(team, option, &arg); >+ NLA_PUT_U32(skb, TEAM_ATTR_OPTION_DATA, arg); >+ break; >+ case TEAM_OPTION_TYPE_STRING: >+ NLA_PUT_U8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING); >+ team_option_get(team, option, &arg); >+ NLA_PUT_STRING(skb, TEAM_ATTR_OPTION_DATA, >+ (char *) arg); >+ break; >+ default: >+ BUG(); >+ } >+ nla_nest_end(skb, option_item); >+ } >+ >+ nla_nest_end(skb, option_list); >+ return genlmsg_end(skb, hdr); >+ >+nla_put_failure: >+ genlmsg_cancel(skb, hdr); >+ return -EMSGSIZE; >+} >+ >+static int team_nl_fill_options_get(struct sk_buff *skb, >+ struct genl_info *info, int flags, >+ struct team *team) >+{ >+ return team_nl_fill_options_get_changed(skb, info->snd_pid, >+ info->snd_seq, NLM_F_ACK, >+ team, NULL); >+} >+ >+static int team_nl_cmd_options_get(struct sk_buff *skb, struct genl_info *info) >+{ >+ struct team *team; >+ int err; >+ >+ team = team_nl_team_get(info); >+ if (!team) >+ return -EINVAL; >+ >+ err = team_nl_send_generic(info, team, team_nl_fill_options_get); >+ >+ team_nl_team_put(team); >+ >+ return err; >+} >+ >+static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) >+{ >+ struct team *team; >+ int err = 0; >+ int i; >+ struct nlattr *nl_option; >+ >+ team = team_nl_team_get(info); >+ if (!team) >+ return -EINVAL; >+ >+ err = -EINVAL; >+ if (!info->attrs[TEAM_ATTR_LIST_OPTION]) { >+ err = -EINVAL; >+ goto team_put; >+ } >+ >+ nla_for_each_nested(nl_option, info->attrs[TEAM_ATTR_LIST_OPTION], i) { >+ struct nlattr *mode_attrs[TEAM_ATTR_OPTION_MAX + 1]; >+ enum team_option_type opt_type; >+ struct team_option *option; >+ char *opt_name; >+ bool opt_found = false; >+ >+ if (nla_type(nl_option) != TEAM_ATTR_ITEM_OPTION) { >+ err = -EINVAL; >+ goto team_put; >+ } >+ err = nla_parse_nested(mode_attrs, TEAM_ATTR_OPTION_MAX, >+ nl_option, team_nl_option_policy); >+ if (err) >+ goto team_put; >+ if (!mode_attrs[TEAM_ATTR_OPTION_NAME] || >+ !mode_attrs[TEAM_ATTR_OPTION_TYPE] || >+ !mode_attrs[TEAM_ATTR_OPTION_DATA]) { >+ err = -EINVAL; >+ goto team_put; >+ } >+ switch (nla_get_u8(mode_attrs[TEAM_ATTR_OPTION_TYPE])) { >+ case NLA_U32: >+ opt_type = TEAM_OPTION_TYPE_U32; >+ break; >+ case NLA_STRING: >+ opt_type = TEAM_OPTION_TYPE_STRING; >+ break; >+ default: >+ goto team_put; >+ } >+ >+ opt_name = nla_data(mode_attrs[TEAM_ATTR_OPTION_NAME]); >+ list_for_each_entry(option, &team->option_list, list) { >+ long arg; >+ struct nlattr *opt_data_attr; >+ >+ if (option->type != opt_type || >+ strcmp(option->name, opt_name)) >+ continue; >+ opt_found = true; >+ opt_data_attr = mode_attrs[TEAM_ATTR_OPTION_DATA]; >+ switch (opt_type) { >+ case TEAM_OPTION_TYPE_U32: >+ arg = nla_get_u32(opt_data_attr); >+ break; >+ case TEAM_OPTION_TYPE_STRING: >+ arg = (long) nla_data(opt_data_attr); >+ break; >+ default: >+ BUG(); >+ } >+ err = team_option_set(team, option, &arg); >+ if (err) >+ goto team_put; >+ } >+ if (!opt_found) { >+ err = -ENOENT; >+ goto team_put; >+ } >+ } >+ >+team_put: >+ team_nl_team_put(team); >+ >+ return err; >+} >+ >+static int team_nl_fill_port_list_get_changed(struct sk_buff *skb, >+ u32 pid, u32 seq, int flags, >+ struct team *team, >+ struct team_port *changed_port) >+{ >+ struct nlattr *port_list; >+ void *hdr; >+ struct team_port *port; >+ >+ hdr = genlmsg_put(skb, pid, seq, &team_nl_family, flags, >+ TEAM_CMD_PORT_LIST_GET); >+ if (IS_ERR(hdr)) >+ return PTR_ERR(hdr); >+ >+ NLA_PUT_U32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex); >+ port_list = nla_nest_start(skb, TEAM_ATTR_LIST_PORT); >+ if (!port_list) >+ return -EMSGSIZE; >+ >+ list_for_each_entry_rcu(port, &team->port_list, list) { >+ struct nlattr *port_item; >+ >+ port_item = nla_nest_start(skb, TEAM_ATTR_ITEM_PORT); >+ if (!port_item) >+ goto nla_put_failure; >+ NLA_PUT_U32(skb, TEAM_ATTR_PORT_IFINDEX, port->dev->ifindex); >+ if (port == changed_port) >+ NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_CHANGED); >+ if (port->linkup) >+ NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_LINKUP); >+ NLA_PUT_U32(skb, TEAM_ATTR_PORT_SPEED, port->speed); >+ NLA_PUT_U8(skb, TEAM_ATTR_PORT_DUPLEX, port->duplex); >+ nla_nest_end(skb, port_item); >+ } >+ >+ nla_nest_end(skb, port_list); >+ return genlmsg_end(skb, hdr); >+ >+nla_put_failure: >+ genlmsg_cancel(skb, hdr); >+ return -EMSGSIZE; >+} >+ >+static int team_nl_fill_port_list_get(struct sk_buff *skb, >+ struct genl_info *info, int flags, >+ struct team *team) >+{ >+ return team_nl_fill_port_list_get_changed(skb, info->snd_pid, >+ info->snd_seq, NLM_F_ACK, >+ team, NULL); >+} >+ >+static int team_nl_cmd_port_list_get(struct sk_buff *skb, >+ struct genl_info *info) >+{ >+ struct team *team; >+ int err; >+ >+ team = team_nl_team_get(info); >+ if (!team) >+ return -EINVAL; >+ >+ err = team_nl_send_generic(info, team, team_nl_fill_port_list_get); >+ >+ team_nl_team_put(team); >+ >+ return err; >+} >+ >+static struct genl_ops team_nl_ops[] = { >+ { >+ .cmd = TEAM_CMD_NOOP, >+ .doit = team_nl_cmd_noop, >+ .policy = team_nl_policy, >+ }, >+ { >+ .cmd = TEAM_CMD_OPTIONS_SET, >+ .doit = team_nl_cmd_options_set, >+ .policy = team_nl_policy, >+ .flags = GENL_ADMIN_PERM, >+ }, >+ { >+ .cmd = TEAM_CMD_OPTIONS_GET, >+ .doit = team_nl_cmd_options_get, >+ .policy = team_nl_policy, >+ .flags = GENL_ADMIN_PERM, >+ }, >+ { >+ .cmd = TEAM_CMD_PORT_LIST_GET, >+ .doit = team_nl_cmd_port_list_get, >+ .policy = team_nl_policy, >+ .flags = GENL_ADMIN_PERM, >+ }, >+}; >+ >+static struct genl_multicast_group team_change_event_mcgrp = { >+ .name = TEAM_GENL_CHANGE_EVENT_MC_GRP_NAME, >+}; >+ >+static int team_nl_send_event_options_get(struct team *team, >+ struct team_option *changed_option) >+{ >+ struct sk_buff *skb; >+ int err; >+ struct net *net = dev_net(team->dev); >+ >+ skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); >+ if (!skb) >+ return -ENOMEM; >+ >+ err = team_nl_fill_options_get_changed(skb, 0, 0, 0, team, >+ changed_option); >+ if (err < 0) >+ goto err_fill; >+ >+ err = genlmsg_multicast_netns(net, skb, 0, team_change_event_mcgrp.id, >+ GFP_KERNEL); >+ return err; >+ >+err_fill: >+ nlmsg_free(skb); >+ return err; >+} >+ >+static int team_nl_send_event_port_list_get(struct team_port *port) >+{ >+ struct sk_buff *skb; >+ int err; >+ struct net *net = dev_net(port->team->dev); >+ >+ skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); >+ if (!skb) >+ return -ENOMEM; >+ >+ err = team_nl_fill_port_list_get_changed(skb, 0, 0, 0, >+ port->team, port); >+ if (err < 0) >+ goto err_fill; >+ >+ err = genlmsg_multicast_netns(net, skb, 0, team_change_event_mcgrp.id, >+ GFP_KERNEL); >+ return err; >+ >+err_fill: >+ nlmsg_free(skb); >+ return err; >+} >+ >+static int team_nl_init(void) >+{ >+ int err; >+ >+ err = genl_register_family_with_ops(&team_nl_family, team_nl_ops, >+ ARRAY_SIZE(team_nl_ops)); >+ if (err) >+ return err; >+ >+ err = genl_register_mc_group(&team_nl_family, &team_change_event_mcgrp); >+ if (err) >+ goto err_change_event_grp_reg; >+ >+ return 0; >+ >+err_change_event_grp_reg: >+ genl_unregister_family(&team_nl_family); >+ >+ return err; >+} >+ >+static void team_nl_fini(void) >+{ >+ genl_unregister_family(&team_nl_family); >+} >+ >+ >+/****************** >+ * Change checkers >+ ******************/ >+ >+static void __team_options_change_check(struct team *team, >+ struct team_option *changed_option) >+{ >+ int err; >+ >+ err = team_nl_send_event_options_get(team, changed_option); >+ if (err) >+ netdev_warn(team->dev, "Failed to send options change via netlink\n"); >+} >+ >+/* rtnl lock is held */ >+static void __team_port_change_check(struct team_port *port, bool linkup) >+{ >+ int err; >+ >+ if (port->linkup == linkup) >+ return; >+ >+ port->linkup = linkup; >+ if (linkup) { >+ struct ethtool_cmd ecmd; >+ >+ err = __ethtool_get_settings(port->dev, &ecmd); >+ if (!err) { >+ port->speed = ethtool_cmd_speed(&ecmd); >+ port->duplex = ecmd.duplex; >+ goto send_event; >+ } >+ } >+ port->speed = 0; >+ port->duplex = 0; >+ >+send_event: >+ err = team_nl_send_event_port_list_get(port); >+ if (err) >+ netdev_warn(port->team->dev, "Failed to send port change of device %s via netlink\n", >+ port->dev->name); >+ >+} >+ >+static void team_port_change_check(struct team_port *port, bool linkup) >+{ >+ struct team *team = port->team; >+ >+ spin_lock(&team->lock); >+ __team_port_change_check(port, linkup); >+ spin_unlock(&team->lock); >+} >+ >+/************************************ >+ * Net device notifier event handler >+ ************************************/ >+ >+static int team_device_event(struct notifier_block *unused, >+ unsigned long event, void *ptr) >+{ >+ struct net_device *dev = (struct net_device *) ptr; >+ struct team_port *port; >+ >+ port = team_port_get_rtnl(dev); >+ if (!port) >+ return NOTIFY_DONE; >+ >+ switch (event) { >+ case NETDEV_UP: >+ if (netif_carrier_ok(dev)) >+ team_port_change_check(port, true); >+ case NETDEV_DOWN: >+ team_port_change_check(port, false); >+ case NETDEV_CHANGE: >+ if (netif_running(port->dev)) >+ team_port_change_check(port, >+ !!netif_carrier_ok(port->dev)); >+ break; >+ case NETDEV_UNREGISTER: >+ team_del_slave(port->team->dev, dev); >+ break; >+ case NETDEV_FEAT_CHANGE: >+ team_compute_features(port->team); >+ break; >+ case NETDEV_CHANGEMTU: >+ /* Forbid to change mtu of underlaying device */ >+ return NOTIFY_BAD; >+ case NETDEV_CHANGEADDR: >+ /* Forbid to change addr of underlaying device */ >+ return NOTIFY_BAD; >+ case NETDEV_PRE_TYPE_CHANGE: >+ /* Forbid to change type of underlaying device */ >+ return NOTIFY_BAD; >+ } >+ return NOTIFY_DONE; >+} >+ >+static struct notifier_block team_notifier_block __read_mostly = { >+ .notifier_call = team_device_event, >+}; >+ >+ >+/*********************** >+ * Module init and exit >+ ***********************/ >+ >+static int __init team_module_init(void) >+{ >+ int err; >+ >+ register_netdevice_notifier(&team_notifier_block); >+ >+ err = rtnl_link_register(&team_link_ops); >+ if (err) >+ goto err_rtnl_reg; >+ >+ err = team_nl_init(); >+ if (err) >+ goto err_nl_init; >+ >+ return 0; >+ >+err_nl_init: >+ rtnl_link_unregister(&team_link_ops); >+ >+err_rtnl_reg: >+ unregister_netdevice_notifier(&team_notifier_block); >+ >+ return err; >+} >+ >+static void __exit team_module_exit(void) >+{ >+ team_nl_fini(); >+ rtnl_link_unregister(&team_link_ops); >+ unregister_netdevice_notifier(&team_notifier_block); >+} >+ >+module_init(team_module_init); >+module_exit(team_module_exit); >+ >+MODULE_LICENSE("GPL v2"); >+MODULE_AUTHOR("Jiri Pirko "); >+MODULE_DESCRIPTION("Ethernet team device driver"); >+MODULE_ALIAS_RTNL_LINK(DRV_NAME); >diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c >new file mode 100644 >index 0000000..1aa2bfb >--- /dev/null >+++ b/drivers/net/team/team_mode_activebackup.c >@@ -0,0 +1,152 @@ >+/* >+ * net/drivers/team/team_mode_activebackup.c - Active-backup mode for team >+ * Copyright (c) 2011 Jiri Pirko >+ * >+ * 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 >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+ >+struct ab_priv { >+ struct team_port __rcu *active_port; >+}; >+ >+static struct ab_priv *ab_priv(struct team *team) >+{ >+ return (struct ab_priv *) &team->mode_priv; >+} >+ >+static rx_handler_result_t ab_receive(struct team *team, struct team_port *port, >+ struct sk_buff *skb) { >+ struct team_port *active_port; >+ >+ active_port = rcu_dereference(ab_priv(team)->active_port); >+ if (active_port != port) >+ return RX_HANDLER_EXACT; >+ return RX_HANDLER_ANOTHER; >+} >+ >+static bool ab_transmit(struct team *team, struct sk_buff *skb) >+{ >+ struct team_port *active_port; >+ >+ active_port = rcu_dereference(ab_priv(team)->active_port); >+ if (unlikely(!active_port)) >+ goto drop; >+ skb->dev = active_port->dev; >+ if (dev_queue_xmit(skb)) >+ return false; >+ return true; >+ >+drop: >+ dev_kfree_skb(skb); >+ return false; >+} >+ >+static void ab_port_leave(struct team *team, struct team_port *port) >+{ >+ if (ab_priv(team)->active_port == port) >+ rcu_assign_pointer(ab_priv(team)->active_port, NULL); >+} >+ >+static void ab_port_change_mac(struct team *team, struct team_port *port) >+{ >+ if (ab_priv(team)->active_port == port) >+ team_port_set_team_mac(port); >+} >+ >+static int ab_active_port_get(struct team *team, void *arg) >+{ >+ u32 *ifindex = arg; >+ >+ *ifindex = 0; >+ if (ab_priv(team)->active_port) >+ *ifindex = ab_priv(team)->active_port->dev->ifindex; >+ return 0; >+} >+ >+static int ab_active_port_set(struct team *team, void *arg) >+{ >+ u32 *ifindex = arg; >+ struct team_port *port; >+ >+ list_for_each_entry_rcu(port, &team->port_list, list) { >+ if (port->dev->ifindex == *ifindex) { >+ struct team_port *ac_port = ab_priv(team)->active_port; >+ >+ /* rtnl_lock needs to be held when setting macs */ >+ rtnl_lock(); >+ if (ac_port) >+ team_port_set_orig_mac(ac_port); >+ rcu_assign_pointer(ab_priv(team)->active_port, port); >+ team_port_set_team_mac(port); >+ rtnl_unlock(); >+ return 0; >+ } >+ } >+ return -ENOENT; >+} >+ >+static struct team_option ab_options[] = { >+ { >+ .name = "activeport", >+ .type = TEAM_OPTION_TYPE_U32, >+ .getter = ab_active_port_get, >+ .setter = ab_active_port_set, >+ }, >+}; >+ >+int ab_init(struct team *team) >+{ >+ team_options_register(team, ab_options, ARRAY_SIZE(ab_options)); >+ return 0; >+} >+ >+void ab_exit(struct team *team) >+{ >+ team_options_unregister(team, ab_options, ARRAY_SIZE(ab_options)); >+} >+ >+static const struct team_mode_ops ab_mode_ops = { >+ .init = ab_init, >+ .exit = ab_exit, >+ .receive = ab_receive, >+ .transmit = ab_transmit, >+ .port_leave = ab_port_leave, >+ .port_change_mac = ab_port_change_mac, >+}; >+ >+static struct team_mode ab_mode = { >+ .kind = "activebackup", >+ .owner = THIS_MODULE, >+ .priv_size = sizeof(struct ab_priv), >+ .ops = &ab_mode_ops, >+}; >+ >+static int __init ab_init_module(void) >+{ >+ return team_mode_register(&ab_mode); >+} >+ >+static void __exit ab_cleanup_module(void) >+{ >+ team_mode_unregister(&ab_mode); >+} >+ >+module_init(ab_init_module); >+module_exit(ab_cleanup_module); >+ >+MODULE_LICENSE("GPL v2"); >+MODULE_AUTHOR("Jiri Pirko "); >+MODULE_DESCRIPTION("Active-backup mode for team"); >+MODULE_ALIAS("team-mode-activebackup"); >diff --git a/drivers/net/team/team_mode_roundrobin.c b/drivers/net/team/team_mode_roundrobin.c >new file mode 100644 >index 0000000..0374052 >--- /dev/null >+++ b/drivers/net/team/team_mode_roundrobin.c >@@ -0,0 +1,107 @@ >+/* >+ * net/drivers/team/team_mode_roundrobin.c - Round-robin mode for team >+ * Copyright (c) 2011 Jiri Pirko >+ * >+ * 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 >+#include >+#include >+#include >+#include >+#include >+#include >+ >+struct rr_priv { >+ unsigned int sent_packets; >+}; >+ >+static struct rr_priv *rr_priv(struct team *team) >+{ >+ return (struct rr_priv *) &team->mode_priv; >+} >+ >+static struct team_port *__get_first_port_up(struct team *team, >+ struct team_port *port) >+{ >+ struct team_port *cur; >+ >+ if (port->linkup) >+ return port; >+ cur = port; >+ list_for_each_entry_continue_rcu(cur, &team->port_list, list) >+ if (cur->linkup) >+ return cur; >+ list_for_each_entry_rcu(cur, &team->port_list, list) { >+ if (cur == port) >+ break; >+ if (cur->linkup) >+ return cur; >+ } >+ return NULL; >+} >+ >+static bool rr_transmit(struct team *team, struct sk_buff *skb) >+{ >+ struct team_port *port; >+ int port_index; >+ >+ port_index = rr_priv(team)->sent_packets++ % team->port_count; >+ port = team_get_port_by_index_rcu(team, port_index); >+ port = __get_first_port_up(team, port); >+ if (unlikely(!port)) >+ goto drop; >+ skb->dev = port->dev; >+ if (dev_queue_xmit(skb)) >+ return false; >+ return true; >+ >+drop: >+ dev_kfree_skb(skb); >+ return false; >+} >+ >+static int rr_port_enter(struct team *team, struct team_port *port) >+{ >+ return team_port_set_team_mac(port); >+} >+ >+static void rr_port_change_mac(struct team *team, struct team_port *port) >+{ >+ team_port_set_team_mac(port); >+} >+ >+static const struct team_mode_ops rr_mode_ops = { >+ .transmit = rr_transmit, >+ .port_enter = rr_port_enter, >+ .port_change_mac = rr_port_change_mac, >+}; >+ >+static struct team_mode rr_mode = { >+ .kind = "roundrobin", >+ .owner = THIS_MODULE, >+ .priv_size = sizeof(struct rr_priv), >+ .ops = &rr_mode_ops, >+}; >+ >+static int __init rr_init_module(void) >+{ >+ return team_mode_register(&rr_mode); >+} >+ >+static void __exit rr_cleanup_module(void) >+{ >+ team_mode_unregister(&rr_mode); >+} >+ >+module_init(rr_init_module); >+module_exit(rr_cleanup_module); >+ >+MODULE_LICENSE("GPL v2"); >+MODULE_AUTHOR("Jiri Pirko "); >+MODULE_DESCRIPTION("Round-robin mode for team"); >+MODULE_ALIAS("team-mode-roundrobin"); >diff --git a/include/linux/Kbuild b/include/linux/Kbuild >index 619b565..0b091b3 100644 >--- a/include/linux/Kbuild >+++ b/include/linux/Kbuild >@@ -185,6 +185,7 @@ header-y += if_pppol2tp.h > header-y += if_pppox.h > header-y += if_slip.h > header-y += if_strip.h >+header-y += if_team.h > header-y += if_tr.h > header-y += if_tun.h > header-y += if_tunnel.h >diff --git a/include/linux/if.h b/include/linux/if.h >index db20bd4..06b6ef6 100644 >--- a/include/linux/if.h >+++ b/include/linux/if.h >@@ -79,6 +79,7 @@ > #define IFF_TX_SKB_SHARING 0x10000 /* The interface supports sharing > * skbs on transmit */ > #define IFF_UNICAST_FLT 0x20000 /* Supports unicast filtering */ >+#define IFF_TEAM_PORT 0x40000 /* device used as team port */ > > #define IF_GET_IFACE 0x0001 /* for querying only */ > #define IF_GET_PROTO 0x0002 >diff --git a/include/linux/if_team.h b/include/linux/if_team.h >new file mode 100644 >index 0000000..de395fc >--- /dev/null >+++ b/include/linux/if_team.h >@@ -0,0 +1,254 @@ >+/* >+ * include/linux/if_team.h - Network team device driver header >+ * Copyright (c) 2011 Jiri Pirko >+ * >+ * 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 _LINUX_IF_TEAM_H_ >+#define _LINUX_IF_TEAM_H_ >+ >+#ifdef __KERNEL__ >+ >+struct team_pcpu_stats { >+ u64 rx_packets; >+ u64 rx_bytes; >+ u64 rx_multicast; >+ u64 tx_packets; >+ u64 tx_bytes; >+ struct u64_stats_sync syncp; >+ u32 rx_dropped; >+ u32 tx_dropped; >+}; >+ >+struct team; >+ >+struct team_port { >+ struct net_device *dev; >+ struct hlist_node hlist; /* node in hash list */ >+ struct list_head list; /* node in ordinary list */ >+ struct team *team; >+ int index; >+ >+ /* >+ * A place for storing original values of the device before it >+ * become a port. >+ */ >+ struct { >+ unsigned char dev_addr[MAX_ADDR_LEN]; >+ unsigned int mtu; >+ } orig; >+ >+ bool linkup; >+ u32 speed; >+ u8 duplex; >+ >+ struct rcu_head rcu; >+}; >+ >+struct team_mode_ops { >+ int (*init)(struct team *team); >+ void (*exit)(struct team *team); >+ rx_handler_result_t (*receive)(struct team *team, >+ struct team_port *port, >+ struct sk_buff *skb); >+ bool (*transmit)(struct team *team, struct sk_buff *skb); >+ int (*port_enter)(struct team *team, struct team_port *port); >+ void (*port_leave)(struct team *team, struct team_port *port); >+ void (*port_change_mac)(struct team *team, struct team_port *port); >+}; >+ >+static inline void team_mode_ops_copy(struct team_mode_ops *dst, >+ const struct team_mode_ops *src) >+{ >+ dst->init = src->init; >+ dst->exit = src->exit; >+ dst->receive = src->receive; >+ dst->transmit = src->transmit; >+ dst->port_enter = src->port_enter; >+ dst->port_leave = src->port_leave; >+ dst->port_change_mac = src->port_change_mac; >+} >+ >+static inline void team_mode_ops_clear(struct team_mode_ops *dst) >+{ >+ dst->init = NULL; >+ dst->exit = NULL; >+ dst->receive = NULL; >+ dst->transmit = NULL; >+ dst->port_enter = NULL; >+ dst->port_leave = NULL; >+ dst->port_change_mac = NULL; >+} >+ >+enum team_option_type { >+ TEAM_OPTION_TYPE_U32, >+ TEAM_OPTION_TYPE_STRING, >+}; >+ >+struct team_option { >+ struct list_head list; >+ const char *name; >+ enum team_option_type type; >+ int (*getter)(struct team *team, void *arg); >+ int (*setter)(struct team *team, void *arg); >+}; >+ >+struct team_mode { >+ struct list_head list; >+ const char *kind; >+ struct module *owner; >+ size_t priv_size; >+ const struct team_mode_ops *ops; >+}; >+ >+#define TEAM_PORT_HASHBITS 4 >+#define TEAM_PORT_HASHENTRIES (1 << TEAM_PORT_HASHBITS) >+ >+#define TEAM_MODE_PRIV_LONGS 4 >+#define TEAM_MODE_PRIV_SIZE (sizeof(long) * TEAM_MODE_PRIV_LONGS) >+ >+struct team { >+ struct net_device *dev; /* associated netdevice */ >+ struct team_pcpu_stats __percpu *pcpu_stats; >+ >+ spinlock_t lock; /* used for overall locking, e.g. port lists write */ >+ >+ /* >+ * port lists with port count >+ */ >+ int port_count; >+ struct hlist_head port_hlist[TEAM_PORT_HASHENTRIES]; >+ struct list_head port_list; >+ >+ struct list_head option_list; >+ >+ const char *mode_kind; >+ struct team_mode_ops mode_ops; >+ long mode_priv[TEAM_MODE_PRIV_LONGS]; >+}; >+ >+static inline struct hlist_head *team_port_index_hash(struct team *team, >+ int port_index) >+{ >+ return &team->port_hlist[port_index & (TEAM_PORT_HASHENTRIES - 1)]; >+} >+ >+static inline struct team_port *team_get_port_by_index_rcu(struct team *team, >+ int port_index) >+{ >+ struct hlist_node *p; >+ struct team_port *port; >+ struct hlist_head *head = team_port_index_hash(team, port_index); >+ >+ hlist_for_each_entry_rcu(port, p, head, hlist) >+ if (port->index == port_index) >+ return port; >+ return NULL; >+} >+ >+extern int team_port_set_orig_mac(struct team_port *port); >+extern int team_port_set_team_mac(struct team_port *port); >+extern void team_options_register(struct team *team, >+ struct team_option *option, >+ size_t option_count); >+extern void team_options_unregister(struct team *team, >+ struct team_option *option, >+ size_t option_count); >+extern int team_mode_register(struct team_mode *mode); >+extern int team_mode_unregister(struct team_mode *mode); >+ >+#endif /* __KERNEL__ */ >+ >+#define TEAM_STRING_MAX_LEN 32 >+ >+/********************************** >+ * NETLINK_GENERIC netlink family. >+ **********************************/ >+ >+enum { >+ TEAM_CMD_NOOP, >+ TEAM_CMD_OPTIONS_SET, >+ TEAM_CMD_OPTIONS_GET, >+ TEAM_CMD_PORT_LIST_GET, >+ >+ __TEAM_CMD_MAX, >+ TEAM_CMD_MAX = (__TEAM_CMD_MAX - 1), >+}; >+ >+enum { >+ TEAM_ATTR_UNSPEC, >+ TEAM_ATTR_TEAM_IFINDEX, /* u32 */ >+ TEAM_ATTR_LIST_OPTION, /* nest */ >+ TEAM_ATTR_LIST_PORT, /* nest */ >+ >+ __TEAM_ATTR_MAX, >+ TEAM_ATTR_MAX = __TEAM_ATTR_MAX - 1, >+}; >+ >+/* Nested layout of get/set msg: >+ * >+ * [TEAM_ATTR_LIST_OPTION] >+ * [TEAM_ATTR_ITEM_OPTION] >+ * [TEAM_ATTR_OPTION_*], ... >+ * [TEAM_ATTR_ITEM_OPTION] >+ * [TEAM_ATTR_OPTION_*], ... >+ * ... >+ * [TEAM_ATTR_LIST_PORT] >+ * [TEAM_ATTR_ITEM_PORT] >+ * [TEAM_ATTR_PORT_*], ... >+ * [TEAM_ATTR_ITEM_PORT] >+ * [TEAM_ATTR_PORT_*], ... >+ * ... >+ */ >+ >+enum { >+ TEAM_ATTR_ITEM_OPTION_UNSPEC, >+ TEAM_ATTR_ITEM_OPTION, /* nest */ >+ >+ __TEAM_ATTR_ITEM_OPTION_MAX, >+ TEAM_ATTR_ITEM_OPTION_MAX = __TEAM_ATTR_ITEM_OPTION_MAX - 1, >+}; >+ >+enum { >+ TEAM_ATTR_OPTION_UNSPEC, >+ TEAM_ATTR_OPTION_NAME, /* string */ >+ TEAM_ATTR_OPTION_CHANGED, /* flag */ >+ TEAM_ATTR_OPTION_TYPE, /* u8 */ >+ TEAM_ATTR_OPTION_DATA, /* dynamic */ >+ >+ __TEAM_ATTR_OPTION_MAX, >+ TEAM_ATTR_OPTION_MAX = __TEAM_ATTR_OPTION_MAX - 1, >+}; >+ >+enum { >+ TEAM_ATTR_ITEM_PORT_UNSPEC, >+ TEAM_ATTR_ITEM_PORT, /* nest */ >+ >+ __TEAM_ATTR_ITEM_PORT_MAX, >+ TEAM_ATTR_ITEM_PORT_MAX = __TEAM_ATTR_ITEM_PORT_MAX - 1, >+}; >+ >+enum { >+ TEAM_ATTR_PORT_UNSPEC, >+ TEAM_ATTR_PORT_IFINDEX, /* u32 */ >+ TEAM_ATTR_PORT_CHANGED, /* flag */ >+ TEAM_ATTR_PORT_LINKUP, /* flag */ >+ TEAM_ATTR_PORT_SPEED, /* u32 */ >+ TEAM_ATTR_PORT_DUPLEX, /* u8 */ >+ >+ __TEAM_ATTR_PORT_MAX, >+ TEAM_ATTR_PORT_MAX = __TEAM_ATTR_PORT_MAX - 1, >+}; >+ >+/* >+ * NETLINK_GENERIC related info >+ */ >+#define TEAM_GENL_NAME "team" >+#define TEAM_GENL_VERSION 0x1 >+#define TEAM_GENL_CHANGE_EVENT_MC_GRP_NAME "change_event" >+ >+#endif /* _LINUX_IF_TEAM_H_ */ >diff --git a/include/linux/rculist.h b/include/linux/rculist.h >index d079290..7586b2c 100644 >--- a/include/linux/rculist.h >+++ b/include/linux/rculist.h >@@ -288,6 +288,20 @@ static inline void list_splice_init_rcu(struct list_head *list, > pos = list_entry_rcu(pos->member.next, typeof(*pos), member)) > > /** >+ * list_for_each_entry_continue_reverse_rcu - iterate backwards from the given point >+ * @pos: the type * to use as a loop cursor. >+ * @head: the head for your list. >+ * @member: the name of the list_struct within the struct. >+ * >+ * Start to iterate over list of given type backwards, continuing after >+ * the current position. >+ */ >+#define list_for_each_entry_continue_reverse_rcu(pos, head, member) \ >+ for (pos = list_entry_rcu(pos->member.prev, typeof(*pos), member); \ >+ &pos->member != (head); \ >+ pos = list_entry_rcu(pos->member.prev, typeof(*pos), member)) >+ >+/** > * hlist_del_rcu - deletes entry from hash list without re-initialization > * @n: the element to delete from the hash list. > * >-- >1.7.6 >