From dbf716c7988828eae7e5a9ea9ccba63f3e6a6d92 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Thu, 7 Jun 2012 06:09:25 +0000 Subject: [PATCH] net: Backport team driver from Linux 3.5-rc1 svn path=/dists/sid/linux/; revision=19094 --- debian/changelog | 2 + debian/config/config | 8 + .../all/define-netdev_features_t.patch | 18 + ...llow-to-create-sk-unattached-filters.patch | 120 + ...et-introduce-ethernet-teaming-device.patch | 2322 +++++++++++++++++ ...-rcu_read_lock-when-running-netlink-.patch | 49 + ...am-convert-overall-spinlock-to-mutex.patch | 149 ++ ...4-team-replicate-options-on-register.patch | 189 ++ .../all/team/0005-team-add-fix_features.patch | 54 + ...am-avoid-using-variable-length-array.patch | 50 + ...am-replace-kmalloc-memcpy-by-kmemdup.patch | 38 + ...net-treewide-use-of-RCU_INIT_POINTER.patch | 28 + ...an_vid_-add-del-and-use-them-instead.patch | 132 + ...unctions-to-do-mass-addition-deletio.patch | 81 + ...team-use-vlan_vids_-addr-del-_by_dev.patch | 53 + ...ly-changed-options-ports-via-netlink.patch | 371 +++ .../team/0013-team-Stop-using-NLA_PUT.patch | 108 + .../0014-team-add-binary-option-type.patch | 124 + .../team/0015-team-add-loadbalance-mode.patch | 241 ++ ...eam-add-support-for-per-port-options.patch | 687 +++++ .../team/0017-team-add-bool-option-type.patch | 137 + ...nkup-and-user_linkup_enabled-per-por.patch | 184 ++ ...am-ab-walk-through-port-list-non-rcu.patch | 27 + .../team/0020-team-add-missed-statics.patch | 66 + ...b-let-userspace-care-about-port-macs.patch | 41 + ...2-team-allow-to-enable-disable-ports.patch | 212 ++ ...t-option-for-enabling-disabling-port.patch | 54 + debian/patches/series-all | 28 + 28 files changed, 5573 insertions(+) create mode 100644 debian/patches/features/all/define-netdev_features_t.patch create mode 100644 debian/patches/features/all/filter-Allow-to-create-sk-unattached-filters.patch create mode 100644 debian/patches/features/all/team/0001-net-introduce-ethernet-teaming-device.patch create mode 100644 debian/patches/features/all/team/0002-team-Do-not-hold-rcu_read_lock-when-running-netlink-.patch create mode 100644 debian/patches/features/all/team/0003-team-convert-overall-spinlock-to-mutex.patch create mode 100644 debian/patches/features/all/team/0004-team-replicate-options-on-register.patch create mode 100644 debian/patches/features/all/team/0005-team-add-fix_features.patch create mode 100644 debian/patches/features/all/team/0006-team-avoid-using-variable-length-array.patch create mode 100644 debian/patches/features/all/team/0007-team-replace-kmalloc-memcpy-by-kmemdup.patch create mode 100644 debian/patches/features/all/team/0008-net-treewide-use-of-RCU_INIT_POINTER.patch create mode 100644 debian/patches/features/all/team/0009-net-introduce-vlan_vid_-add-del-and-use-them-instead.patch create mode 100644 debian/patches/features/all/team/0010-vlan-introduce-functions-to-do-mass-addition-deletio.patch create mode 100644 debian/patches/features/all/team/0011-team-use-vlan_vids_-addr-del-_by_dev.patch create mode 100644 debian/patches/features/all/team/0012-team-send-only-changed-options-ports-via-netlink.patch create mode 100644 debian/patches/features/all/team/0013-team-Stop-using-NLA_PUT.patch create mode 100644 debian/patches/features/all/team/0014-team-add-binary-option-type.patch create mode 100644 debian/patches/features/all/team/0015-team-add-loadbalance-mode.patch create mode 100644 debian/patches/features/all/team/0016-team-add-support-for-per-port-options.patch create mode 100644 debian/patches/features/all/team/0017-team-add-bool-option-type.patch create mode 100644 debian/patches/features/all/team/0018-team-add-user_linkup-and-user_linkup_enabled-per-por.patch create mode 100644 debian/patches/features/all/team/0019-team-ab-walk-through-port-list-non-rcu.patch create mode 100644 debian/patches/features/all/team/0020-team-add-missed-statics.patch create mode 100644 debian/patches/features/all/team/0021-team-lb-let-userspace-care-about-port-macs.patch create mode 100644 debian/patches/features/all/team/0022-team-allow-to-enable-disable-ports.patch create mode 100644 debian/patches/features/all/team/0023-team-add-per-port-option-for-enabling-disabling-port.patch diff --git a/debian/changelog b/debian/changelog index 72131cceb..003832033 100644 --- a/debian/changelog +++ b/debian/changelog @@ -19,6 +19,8 @@ linux (3.2.19-2) UNRELEASED; urgency=low * Convert patch system to quilt, except for the 'orig' patch series * udeb: Build-Depend on kernel-wedge >= 2.84; this allows us to list modules as required even if they are built-in in some configurations + * filter: Allow to create sk-unattached filters + * net: Backport team driver from Linux 3.5-rc1 -- Ben Hutchings Sat, 02 Jun 2012 20:31:53 +0100 diff --git a/debian/config/config b/debian/config/config index 12c838caf..001ffd8d0 100644 --- a/debian/config/config +++ b/debian/config/config @@ -2075,6 +2075,14 @@ CONFIG_SLIP_COMPRESSED=y CONFIG_SLIP_SMART=y CONFIG_SLIP_MODE_SLIP6=y +## +## file: drivers/net/team/Kconfig +## +CONFIG_NET_TEAM=m +CONFIG_NET_TEAM_MODE_ROUNDROBIN=m +CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m +CONFIG_NET_TEAM_MODE_LOADBALANCE=m + ## ## file: drivers/net/tokenring/Kconfig ## diff --git a/debian/patches/features/all/define-netdev_features_t.patch b/debian/patches/features/all/define-netdev_features_t.patch new file mode 100644 index 000000000..b11cc2ee6 --- /dev/null +++ b/debian/patches/features/all/define-netdev_features_t.patch @@ -0,0 +1,18 @@ +From: Ben Hutchings +Subject: Define netdev_features_t + +This was done in upstream commit +c8f44affb7244f2ac3e703cab13d55ede27621bb, but we just want the type +definition for use by backported drivers rather than changing +everything to use it. +--- a/include/linux/netdevice.h ++++ b/include/linux/netdevice.h +@@ -950,6 +950,8 @@ + u32 features); + }; + ++typedef u32 netdev_features_t; ++ + /* + * The DEVICE structure. + * Actually, this whole structure is a big mistake. It mixes I/O diff --git a/debian/patches/features/all/filter-Allow-to-create-sk-unattached-filters.patch b/debian/patches/features/all/filter-Allow-to-create-sk-unattached-filters.patch new file mode 100644 index 000000000..f3fb9779b --- /dev/null +++ b/debian/patches/features/all/filter-Allow-to-create-sk-unattached-filters.patch @@ -0,0 +1,120 @@ +From: Jiri Pirko +Date: Sat, 31 Mar 2012 11:01:19 +0000 +Subject: filter: Allow to create sk-unattached filters + +commit 302d663740cfaf2c364df6bb61cd339014ed714c upstream. + +Today, BPF filters are bind to sockets. Since BPF machine becomes handy +for other purposes, this patch allows to create unattached filter. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + include/linux/filter.h | 3 +++ + net/core/filter.c | 66 +++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 65 insertions(+), 4 deletions(-) + +diff --git a/include/linux/filter.h b/include/linux/filter.h +index 8eeb205..92dd993 100644 +--- a/include/linux/filter.h ++++ b/include/linux/filter.h +@@ -153,6 +153,9 @@ static inline unsigned int sk_filter_len(const struct sk_filter *fp) + extern int sk_filter(struct sock *sk, struct sk_buff *skb); + extern unsigned int sk_run_filter(const struct sk_buff *skb, + const struct sock_filter *filter); ++extern int sk_unattached_filter_create(struct sk_filter **pfp, ++ struct sock_fprog *fprog); ++extern void sk_unattached_filter_destroy(struct sk_filter *fp); + extern int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk); + extern int sk_detach_filter(struct sock *sk); + extern int sk_chk_filter(struct sock_filter *filter, unsigned int flen); +diff --git a/net/core/filter.c b/net/core/filter.c +index 5dea452..cfbea88 100644 +--- a/net/core/filter.c ++++ b/net/core/filter.c +@@ -587,6 +587,67 @@ void sk_filter_release_rcu(struct rcu_head *rcu) + } + EXPORT_SYMBOL(sk_filter_release_rcu); + ++static int __sk_prepare_filter(struct sk_filter *fp) ++{ ++ int err; ++ ++ fp->bpf_func = sk_run_filter; ++ ++ err = sk_chk_filter(fp->insns, fp->len); ++ if (err) ++ return err; ++ ++ bpf_jit_compile(fp); ++ return 0; ++} ++ ++/** ++ * sk_unattached_filter_create - create an unattached filter ++ * @fprog: the filter program ++ * @sk: the socket to use ++ * ++ * Create a filter independent ofr any socket. We first run some ++ * sanity checks on it to make sure it does not explode on us later. ++ * If an error occurs or there is insufficient memory for the filter ++ * a negative errno code is returned. On success the return is zero. ++ */ ++int sk_unattached_filter_create(struct sk_filter **pfp, ++ struct sock_fprog *fprog) ++{ ++ struct sk_filter *fp; ++ unsigned int fsize = sizeof(struct sock_filter) * fprog->len; ++ int err; ++ ++ /* Make sure new filter is there and in the right amounts. */ ++ if (fprog->filter == NULL) ++ return -EINVAL; ++ ++ fp = kmalloc(fsize + sizeof(*fp), GFP_KERNEL); ++ if (!fp) ++ return -ENOMEM; ++ memcpy(fp->insns, fprog->filter, fsize); ++ ++ atomic_set(&fp->refcnt, 1); ++ fp->len = fprog->len; ++ ++ err = __sk_prepare_filter(fp); ++ if (err) ++ goto free_mem; ++ ++ *pfp = fp; ++ return 0; ++free_mem: ++ kfree(fp); ++ return err; ++} ++EXPORT_SYMBOL_GPL(sk_unattached_filter_create); ++ ++void sk_unattached_filter_destroy(struct sk_filter *fp) ++{ ++ sk_filter_release(fp); ++} ++EXPORT_SYMBOL_GPL(sk_unattached_filter_destroy); ++ + /** + * sk_attach_filter - attach a socket filter + * @fprog: the filter program +@@ -617,16 +678,13 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) + + atomic_set(&fp->refcnt, 1); + fp->len = fprog->len; +- fp->bpf_func = sk_run_filter; + +- err = sk_chk_filter(fp->insns, fp->len); ++ err = __sk_prepare_filter(fp); + if (err) { + sk_filter_uncharge(sk, fp); + return err; + } + +- bpf_jit_compile(fp); +- + old_fp = rcu_dereference_protected(sk->sk_filter, + sock_owned_by_user(sk)); + rcu_assign_pointer(sk->sk_filter, fp); diff --git a/debian/patches/features/all/team/0001-net-introduce-ethernet-teaming-device.patch b/debian/patches/features/all/team/0001-net-introduce-ethernet-teaming-device.patch new file mode 100644 index 000000000..bfc901069 --- /dev/null +++ b/debian/patches/features/all/team/0001-net-introduce-ethernet-teaming-device.patch @@ -0,0 +1,2322 @@ +From: Jiri Pirko +Date: Fri, 11 Nov 2011 22:16:48 +0000 +Subject: [01/23] net: introduce ethernet teaming device + +commit 3d249d4ca7d0ed6629a135ea1ea21c72286c0d80 upstream. + +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 of libteam was recently introduced. +Daemon providing arpmon/miimon active-backup functionality will be +introduced shortly. All what's necessary is already implemented in +kernel team driver. + +v7->v8: + - check ndo_ndo_vlan_rx_[add/kill]_vid functions before calling + them. + - use dev_kfree_skb_any() instead of dev_kfree_skb() + +v6->v7: + - transmit and receive functions are not checked in hot paths. + That also resolves memory leak on transmit when no port is + present + +v5->v6: + - changed couple of _rcu calls to non _rcu ones in non-readers + +v4->v5: + - team_change_mtu() uses team->lock while travesing though port + list + - mac address changes are moved completely to jurisdiction of + userspace daemon. This way the daemon can do FOM1, FOM2 and + possibly other weird things with mac addresses. + Only round-robin mode sets up all ports to bond's address then + enslaved. + - Extended Kconfig text + +v3->v4: + - remove redundant synchronize_rcu from __team_change_mode() + - revert "set and clear of mode_ops happens per pointer, not per + byte" + - extend comment of function __team_change_mode() + +v2->v3: + - team_change_mtu() uses 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. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + Documentation/networking/team.txt | 2 + + MAINTAINERS | 7 + + drivers/net/Kconfig | 2 + + drivers/net/Makefile | 1 + + drivers/net/team/Kconfig | 43 + + drivers/net/team/Makefile | 7 + + drivers/net/team/team.c | 1583 +++++++++++++++++++++++++++++ + drivers/net/team/team_mode_activebackup.c | 137 +++ + drivers/net/team/team_mode_roundrobin.c | 107 ++ + include/linux/Kbuild | 1 + + include/linux/if.h | 1 + + include/linux/if_team.h | 242 +++++ + 12 files changed, 2133 insertions(+) + 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 4808256..8d94169 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -6484,6 +6484,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: Olof Johansson +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..248a144 +--- /dev/null ++++ b/drivers/net/team/Kconfig +@@ -0,0 +1,43 @@ ++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. ++ ++ All added ports are setup to have bond's mac address. ++ ++ 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. ++ ++ Mac addresses of ports are not modified. Userspace is responsible ++ to do so. ++ ++ 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..60672bb +--- /dev/null ++++ b/drivers/net/team/team.c +@@ -0,0 +1,1583 @@ ++/* ++ * 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); ++} ++ ++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 struct team_mode *mode) ++{ ++ module_put(mode->owner); ++} ++ ++static bool team_dummy_transmit(struct team *team, struct sk_buff *skb) ++{ ++ dev_kfree_skb_any(skb); ++ return false; ++} ++ ++rx_handler_result_t team_dummy_receive(struct team *team, ++ struct team_port *port, ++ struct sk_buff *skb) ++{ ++ return RX_HANDLER_ANOTHER; ++} ++ ++static void team_adjust_ops(struct team *team) ++{ ++ /* ++ * To avoid checks in rx/tx skb paths, ensure here that non-null and ++ * correct ops are always set. ++ */ ++ ++ if (list_empty(&team->port_list) || ++ !team->mode || !team->mode->ops->transmit) ++ team->ops.transmit = team_dummy_transmit; ++ else ++ team->ops.transmit = team->mode->ops->transmit; ++ ++ if (list_empty(&team->port_list) || ++ !team->mode || !team->mode->ops->receive) ++ team->ops.receive = team_dummy_receive; ++ else ++ team->ops.receive = team->mode->ops->receive; ++} ++ ++/* ++ * We can benefit from the fact that it's ensured no port is present ++ * at the time of mode change. Therefore no packets are in fly so there's no ++ * need to set mode operations in any special way. ++ */ ++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) { ++ void (*exit_op)(struct team *team) = team->ops.exit; ++ ++ /* Clear ops area so no callback is called any longer */ ++ memset(&team->ops, 0, sizeof(struct team_mode_ops)); ++ team_adjust_ops(team); ++ ++ if (exit_op) ++ exit_op(team); ++ team_mode_put(team->mode); ++ team->mode = 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 = new_mode; ++ memcpy(&team->ops, new_mode->ops, sizeof(struct team_mode_ops)); ++ team_adjust_ops(team); ++ ++ 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 && 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(new_mode); ++ 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; ++ ++ 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; ++ ++ res = team->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(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->ops.port_enter) { ++ err = team->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->ops.port_leave) ++ team->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; ++ 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); ++ ++ 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 = dev_open(port_dev); ++ if (err) { ++ netdev_dbg(dev, "Device %s opening failed\n", ++ portname); ++ goto err_dev_open; ++ } ++ ++ 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_adjust_ops(team); ++ __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: ++ dev_close(port_dev); ++ ++err_dev_open: ++ team_port_leave(team, port); ++ team_port_set_orig_mac(port); ++ ++err_port_enter: ++ 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); ++ team_adjust_ops(team); ++ netdev_rx_handler_unregister(port_dev); ++ netdev_set_master(port_dev, NULL); ++ dev_close(port_dev); ++ team_port_leave(team, port); ++ 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 ? 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); ++ ++ team_adjust_ops(team); ++ ++ 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; ++ ++ tx_success = team->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->ops.port_change_mac) ++ team->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; ++ ++ /* ++ * Alhough this is reader, it's guarded by team lock. It's not possible ++ * to traverse list in reverse under rcu_read_lock ++ */ ++ spin_lock(&team->lock); ++ list_for_each_entry(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; ++ } ++ } ++ spin_unlock(&team->lock); ++ ++ dev->mtu = new_mtu; ++ ++ return 0; ++ ++unwind: ++ list_for_each_entry_continue_reverse(port, &team->port_list, list) ++ dev_set_mtu(port->dev, dev->mtu); ++ spin_unlock(&team->lock); ++ ++ 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; ++ ++ if (ops->ndo_vlan_rx_add_vid) ++ 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; ++ ++ if (ops->ndo_vlan_rx_kill_vid) ++ 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(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_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..6fe920c +--- /dev/null ++++ b/drivers/net/team/team_mode_activebackup.c +@@ -0,0 +1,137 @@ ++/* ++ * 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_any(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 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) { ++ rcu_assign_pointer(ab_priv(team)->active_port, port); ++ 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, ++}; ++ ++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..a0e8f80 +--- /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_any(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..14f6388 +--- /dev/null ++++ b/include/linux/if_team.h +@@ -0,0 +1,242 @@ ++/* ++ * 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); ++}; ++ ++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 struct team_mode *mode; ++ struct team_mode_ops 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(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(port, p, head, hlist) ++ if (port->index == port_index) ++ return port; ++ return NULL; ++} ++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_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/debian/patches/features/all/team/0002-team-Do-not-hold-rcu_read_lock-when-running-netlink-.patch b/debian/patches/features/all/team/0002-team-Do-not-hold-rcu_read_lock-when-running-netlink-.patch new file mode 100644 index 000000000..3aea3caeb --- /dev/null +++ b/debian/patches/features/all/team/0002-team-Do-not-hold-rcu_read_lock-when-running-netlink-.patch @@ -0,0 +1,49 @@ +From: Jiri Pirko +Date: Wed, 16 Nov 2011 11:55:54 +0000 +Subject: [02/23] team: Do not hold rcu_read_lock when running netlink cmds + +commit 8c0713a57482ebfadef197c856a38af3a55444c6 upstream. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 11 +++++------ + 1 file changed, 5 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 60672bb..e5390c7 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -1043,8 +1043,7 @@ err_msg_put: + + /* + * 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. ++ * Since dev gets held here, that ensures dev won't disappear in between. + */ + static struct team *team_nl_team_get(struct genl_info *info) + { +@@ -1057,10 +1056,10 @@ static struct team *team_nl_team_get(struct genl_info *info) + return NULL; + + ifindex = nla_get_u32(info->attrs[TEAM_ATTR_TEAM_IFINDEX]); +- rcu_read_lock(); +- dev = dev_get_by_index_rcu(net, ifindex); ++ dev = dev_get_by_index(net, ifindex); + if (!dev || dev->netdev_ops != &team_netdev_ops) { +- rcu_read_unlock(); ++ if (dev) ++ dev_put(dev); + return NULL; + } + +@@ -1072,7 +1071,7 @@ static struct team *team_nl_team_get(struct genl_info *info) + static void team_nl_team_put(struct team *team) + { + spin_unlock(&team->lock); +- rcu_read_unlock(); ++ dev_put(team->dev); + } + + static int team_nl_send_generic(struct genl_info *info, struct team *team, diff --git a/debian/patches/features/all/team/0003-team-convert-overall-spinlock-to-mutex.patch b/debian/patches/features/all/team/0003-team-convert-overall-spinlock-to-mutex.patch new file mode 100644 index 000000000..67f740f4f --- /dev/null +++ b/debian/patches/features/all/team/0003-team-convert-overall-spinlock-to-mutex.patch @@ -0,0 +1,149 @@ +From: Jiri Pirko +Date: Wed, 16 Nov 2011 11:09:08 +0000 +Subject: [03/23] team: convert overall spinlock to mutex + +commit 61dc3461b9549bc10a2f16d254250680cadafcce upstream. + +No need to have spinlock for this purpose. So convert this to mutex and +avoid current schedule while atomic problems in netlink code. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 32 ++++++++++++++++---------------- + include/linux/if_team.h | 2 +- + 2 files changed, 17 insertions(+), 17 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index e5390c7..7db219c 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -443,9 +443,9 @@ static void __team_compute_features(struct team *team) + + static void team_compute_features(struct team *team) + { +- spin_lock(&team->lock); ++ mutex_lock(&team->lock); + __team_compute_features(team); +- spin_unlock(&team->lock); ++ mutex_unlock(&team->lock); + } + + static int team_port_enter(struct team *team, struct team_port *port) +@@ -647,7 +647,7 @@ static int team_init(struct net_device *dev) + int i; + + team->dev = dev; +- spin_lock_init(&team->lock); ++ mutex_init(&team->lock); + + team->pcpu_stats = alloc_percpu(struct team_pcpu_stats); + if (!team->pcpu_stats) +@@ -672,13 +672,13 @@ static void team_uninit(struct net_device *dev) + struct team_port *port; + struct team_port *tmp; + +- spin_lock(&team->lock); ++ mutex_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); ++ mutex_unlock(&team->lock); + } + + static void team_destructor(struct net_device *dev) +@@ -784,7 +784,7 @@ static int team_change_mtu(struct net_device *dev, int new_mtu) + * Alhough this is reader, it's guarded by team lock. It's not possible + * to traverse list in reverse under rcu_read_lock + */ +- spin_lock(&team->lock); ++ mutex_lock(&team->lock); + list_for_each_entry(port, &team->port_list, list) { + err = dev_set_mtu(port->dev, new_mtu); + if (err) { +@@ -793,7 +793,7 @@ static int team_change_mtu(struct net_device *dev, int new_mtu) + goto unwind; + } + } +- spin_unlock(&team->lock); ++ mutex_unlock(&team->lock); + + dev->mtu = new_mtu; + +@@ -802,7 +802,7 @@ static int team_change_mtu(struct net_device *dev, int new_mtu) + unwind: + list_for_each_entry_continue_reverse(port, &team->port_list, list) + dev_set_mtu(port->dev, dev->mtu); +- spin_unlock(&team->lock); ++ mutex_unlock(&team->lock); + + return err; + } +@@ -880,9 +880,9 @@ 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); ++ mutex_lock(&team->lock); + err = team_port_add(team, port_dev); +- spin_unlock(&team->lock); ++ mutex_unlock(&team->lock); + return err; + } + +@@ -891,9 +891,9 @@ 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); ++ mutex_lock(&team->lock); + err = team_port_del(team, port_dev); +- spin_unlock(&team->lock); ++ mutex_unlock(&team->lock); + return err; + } + +@@ -1064,13 +1064,13 @@ static struct team *team_nl_team_get(struct genl_info *info) + } + + team = netdev_priv(dev); +- spin_lock(&team->lock); ++ mutex_lock(&team->lock); + return team; + } + + static void team_nl_team_put(struct team *team) + { +- spin_unlock(&team->lock); ++ mutex_unlock(&team->lock); + dev_put(team->dev); + } + +@@ -1486,9 +1486,9 @@ static void team_port_change_check(struct team_port *port, bool linkup) + { + struct team *team = port->team; + +- spin_lock(&team->lock); ++ mutex_lock(&team->lock); + __team_port_change_check(port, linkup); +- spin_unlock(&team->lock); ++ mutex_unlock(&team->lock); + } + + /************************************ +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index 14f6388..a6eac12 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -92,7 +92,7 @@ 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 */ ++ struct mutex lock; /* used for overall locking, e.g. port lists write */ + + /* + * port lists with port count diff --git a/debian/patches/features/all/team/0004-team-replicate-options-on-register.patch b/debian/patches/features/all/team/0004-team-replicate-options-on-register.patch new file mode 100644 index 000000000..24b65c675 --- /dev/null +++ b/debian/patches/features/all/team/0004-team-replicate-options-on-register.patch @@ -0,0 +1,189 @@ +From: Jiri Pirko +Date: Wed, 16 Nov 2011 11:09:09 +0000 +Subject: [04/23] team: replicate options on register + +commit 358b838291f618278080bbed435b755f9b46748e upstream. + +Since multiple team instances are putting defined options into their +option list, during register each option must be cloned before added +into list. This resolves uncool memory corruptions when using multiple +teams. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 76 +++++++++++++++++++++++++---- + drivers/net/team/team_mode_activebackup.c | 5 +- + include/linux/if_team.h | 8 +-- + 3 files changed, 72 insertions(+), 17 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 7db219c..f309274 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -80,30 +80,78 @@ EXPORT_SYMBOL(team_port_set_team_mac); + * Options handling + *******************/ + +-void team_options_register(struct team *team, struct team_option *option, +- size_t option_count) ++struct team_option *__team_find_option(struct team *team, const char *opt_name) ++{ ++ struct team_option *option; ++ ++ list_for_each_entry(option, &team->option_list, list) { ++ if (strcmp(option->name, opt_name) == 0) ++ return option; ++ } ++ return NULL; ++} ++ ++int team_options_register(struct team *team, ++ const struct team_option *option, ++ size_t option_count) + { + int i; ++ struct team_option *dst_opts[option_count]; ++ int err; ++ ++ memset(dst_opts, 0, sizeof(dst_opts)); ++ for (i = 0; i < option_count; i++, option++) { ++ struct team_option *dst_opt; ++ ++ if (__team_find_option(team, option->name)) { ++ err = -EEXIST; ++ goto rollback; ++ } ++ dst_opt = kmalloc(sizeof(*option), GFP_KERNEL); ++ if (!dst_opt) { ++ err = -ENOMEM; ++ goto rollback; ++ } ++ memcpy(dst_opt, option, sizeof(*option)); ++ dst_opts[i] = dst_opt; ++ } ++ ++ for (i = 0; i < option_count; i++) ++ list_add_tail(&dst_opts[i]->list, &team->option_list); + +- for (i = 0; i < option_count; i++, option++) +- list_add_tail(&option->list, &team->option_list); ++ return 0; ++ ++rollback: ++ for (i = 0; i < option_count; i++) ++ kfree(dst_opts[i]); ++ ++ return err; + } ++ + 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, ++ const struct team_option *option, + size_t option_count) + { + int i; + +- for (i = 0; i < option_count; i++, option++) +- list_del(&option->list); ++ for (i = 0; i < option_count; i++, option++) { ++ struct team_option *del_opt; ++ ++ del_opt = __team_find_option(team, option->name); ++ if (del_opt) { ++ list_del(&del_opt->list); ++ kfree(del_opt); ++ } ++ } + } + +-void team_options_unregister(struct team *team, struct team_option *option, ++void team_options_unregister(struct team *team, ++ const struct team_option *option, + size_t option_count) + { + __team_options_unregister(team, option, option_count); +@@ -632,7 +680,7 @@ static int team_mode_option_set(struct team *team, void *arg) + return team_change_mode(team, *str); + } + +-static struct team_option team_options[] = { ++static const struct team_option team_options[] = { + { + .name = "mode", + .type = TEAM_OPTION_TYPE_STRING, +@@ -645,6 +693,7 @@ static int team_init(struct net_device *dev) + { + struct team *team = netdev_priv(dev); + int i; ++ int err; + + team->dev = dev; + mutex_init(&team->lock); +@@ -660,10 +709,17 @@ static int team_init(struct net_device *dev) + team_adjust_ops(team); + + INIT_LIST_HEAD(&team->option_list); +- team_options_register(team, team_options, ARRAY_SIZE(team_options)); ++ err = team_options_register(team, team_options, ARRAY_SIZE(team_options)); ++ if (err) ++ goto err_options_register; + netif_carrier_off(dev); + + return 0; ++ ++err_options_register: ++ free_percpu(team->pcpu_stats); ++ ++ return err; + } + + static void team_uninit(struct net_device *dev) +diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c +index 6fe920c..b344275 100644 +--- a/drivers/net/team/team_mode_activebackup.c ++++ b/drivers/net/team/team_mode_activebackup.c +@@ -83,7 +83,7 @@ static int ab_active_port_set(struct team *team, void *arg) + return -ENOENT; + } + +-static struct team_option ab_options[] = { ++static const struct team_option ab_options[] = { + { + .name = "activeport", + .type = TEAM_OPTION_TYPE_U32, +@@ -94,8 +94,7 @@ static struct team_option ab_options[] = { + + int ab_init(struct team *team) + { +- team_options_register(team, ab_options, ARRAY_SIZE(ab_options)); +- return 0; ++ return team_options_register(team, ab_options, ARRAY_SIZE(ab_options)); + } + + void ab_exit(struct team *team) +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index a6eac12..828181f 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -140,11 +140,11 @@ static inline struct team_port *team_get_port_by_index_rcu(struct team *team, + } + + 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 int team_options_register(struct team *team, ++ const struct team_option *option, ++ size_t option_count); + extern void team_options_unregister(struct team *team, +- struct team_option *option, ++ const 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); diff --git a/debian/patches/features/all/team/0005-team-add-fix_features.patch b/debian/patches/features/all/team/0005-team-add-fix_features.patch new file mode 100644 index 000000000..85a504ce5 --- /dev/null +++ b/debian/patches/features/all/team/0005-team-add-fix_features.patch @@ -0,0 +1,54 @@ +From: Jiri Pirko +Date: Thu, 17 Nov 2011 04:16:04 +0000 +Subject: [05/23] team: add fix_features + +commit 234a8fd49d086f5a3debb379bfdf4e6b51f0c0e2 upstream. + +do fix features in similar way as bonding code does + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index f309274..5b169c1 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -953,6 +953,27 @@ static int team_del_slave(struct net_device *dev, struct net_device *port_dev) + return err; + } + ++static netdev_features_t team_fix_features(struct net_device *dev, ++ netdev_features_t features) ++{ ++ struct team_port *port; ++ struct team *team = netdev_priv(dev); ++ netdev_features_t mask; ++ ++ mask = features; ++ features &= ~NETIF_F_ONE_FOR_ALL; ++ features |= NETIF_F_ALL_FOR_ALL; ++ ++ rcu_read_lock(); ++ list_for_each_entry_rcu(port, &team->port_list, list) { ++ features = netdev_increment_features(features, ++ port->dev->features, ++ mask); ++ } ++ rcu_read_unlock(); ++ return features; ++} ++ + static const struct net_device_ops team_netdev_ops = { + .ndo_init = team_init, + .ndo_uninit = team_uninit, +@@ -968,6 +989,7 @@ static const struct net_device_ops team_netdev_ops = { + .ndo_vlan_rx_kill_vid = team_vlan_rx_kill_vid, + .ndo_add_slave = team_add_slave, + .ndo_del_slave = team_del_slave, ++ .ndo_fix_features = team_fix_features, + }; + + diff --git a/debian/patches/features/all/team/0006-team-avoid-using-variable-length-array.patch b/debian/patches/features/all/team/0006-team-avoid-using-variable-length-array.patch new file mode 100644 index 000000000..2abd0fe1c --- /dev/null +++ b/debian/patches/features/all/team/0006-team-avoid-using-variable-length-array.patch @@ -0,0 +1,50 @@ +From: Jiri Pirko +Date: Thu, 17 Nov 2011 04:16:05 +0000 +Subject: [06/23] team: avoid using variable-length array + +commit 2bba19fff8d09bf19df5d5e2de7188d65de67c3e upstream. + +Apparently using variable-length array is not correct +(https://lkml.org/lkml/2011/10/23/25). So remove it. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 5b169c1..c48ef19 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -96,10 +96,13 @@ int team_options_register(struct team *team, + size_t option_count) + { + int i; +- struct team_option *dst_opts[option_count]; ++ struct team_option **dst_opts; + int err; + +- memset(dst_opts, 0, sizeof(dst_opts)); ++ dst_opts = kzalloc(sizeof(struct team_option *) * option_count, ++ GFP_KERNEL); ++ if (!dst_opts) ++ return -ENOMEM; + for (i = 0; i < option_count; i++, option++) { + struct team_option *dst_opt; + +@@ -119,12 +122,14 @@ int team_options_register(struct team *team, + for (i = 0; i < option_count; i++) + list_add_tail(&dst_opts[i]->list, &team->option_list); + ++ kfree(dst_opts); + return 0; + + rollback: + for (i = 0; i < option_count; i++) + kfree(dst_opts[i]); + ++ kfree(dst_opts); + return err; + } + diff --git a/debian/patches/features/all/team/0007-team-replace-kmalloc-memcpy-by-kmemdup.patch b/debian/patches/features/all/team/0007-team-replace-kmalloc-memcpy-by-kmemdup.patch new file mode 100644 index 000000000..7680fbdfd --- /dev/null +++ b/debian/patches/features/all/team/0007-team-replace-kmalloc-memcpy-by-kmemdup.patch @@ -0,0 +1,38 @@ +From: Jiri Pirko +Date: Thu, 17 Nov 2011 06:32:37 +0000 +Subject: [07/23] team: replace kmalloc+memcpy by kmemdup + +commit f8a15af093b19b86d56933c8757cee298d0f32a8 upstream. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 8 ++------ + 1 file changed, 2 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index c48ef19..064155d 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -104,19 +104,15 @@ int team_options_register(struct team *team, + if (!dst_opts) + return -ENOMEM; + for (i = 0; i < option_count; i++, option++) { +- struct team_option *dst_opt; +- + if (__team_find_option(team, option->name)) { + err = -EEXIST; + goto rollback; + } +- dst_opt = kmalloc(sizeof(*option), GFP_KERNEL); +- if (!dst_opt) { ++ dst_opts[i] = kmemdup(option, sizeof(*option), GFP_KERNEL); ++ if (!dst_opts[i]) { + err = -ENOMEM; + goto rollback; + } +- memcpy(dst_opt, option, sizeof(*option)); +- dst_opts[i] = dst_opt; + } + + for (i = 0; i < option_count; i++) diff --git a/debian/patches/features/all/team/0008-net-treewide-use-of-RCU_INIT_POINTER.patch b/debian/patches/features/all/team/0008-net-treewide-use-of-RCU_INIT_POINTER.patch new file mode 100644 index 000000000..9bd5f39de --- /dev/null +++ b/debian/patches/features/all/team/0008-net-treewide-use-of-RCU_INIT_POINTER.patch @@ -0,0 +1,28 @@ +From: Eric Dumazet +Date: Wed, 23 Nov 2011 07:09:32 +0000 +Subject: [08/23] net: treewide use of RCU_INIT_POINTER + +commit 2cfa5a0471fef43fda0b7bd87e3a5e4dbadb7809 upstream. + +rcu_assign_pointer(ptr, NULL) can be safely replaced by +RCU_INIT_POINTER(ptr, NULL) + +(old rcu_assign_pointer() macro was testing the NULL value and could +omit the smp_wmb(), but this had to be removed because of compiler +warnings) + +Signed-off-by: Eric Dumazet +Signed-off-by: David S. Miller +[bwh: Restrict to drivers/net/team/] +--- +--- a/drivers/net/team/team_mode_activebackup.c ++++ b/drivers/net/team/team_mode_activebackup.c +@@ -56,7 +56,7 @@ drop: + 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); ++ RCU_INIT_POINTER(ab_priv(team)->active_port, NULL); + } + + static int ab_active_port_get(struct team *team, void *arg) diff --git a/debian/patches/features/all/team/0009-net-introduce-vlan_vid_-add-del-and-use-them-instead.patch b/debian/patches/features/all/team/0009-net-introduce-vlan_vid_-add-del-and-use-them-instead.patch new file mode 100644 index 000000000..f30b01ec2 --- /dev/null +++ b/debian/patches/features/all/team/0009-net-introduce-vlan_vid_-add-del-and-use-them-instead.patch @@ -0,0 +1,132 @@ +From: Jiri Pirko +Date: Thu, 8 Dec 2011 04:11:17 +0000 +Subject: [09/23] net: introduce vlan_vid_[add/del] and use them instead of + direct [add/kill]_vid ndo calls + +commit 87002b03baabd2b8f6281ab6411ed88d24958de1 upstream. + +This patch adds wrapper for ndo_vlan_rx_add_vid/ndo_vlan_rx_kill_vid +functions. Check for NETIF_F_HW_VLAN_FILTER feature is done in this +wrapper. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +[bwh: Drop changes to bonding, macvlan, vlan; assume + ndo_vlan_rx_{add,kill}_vid still return void] +--- +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -906,15 +907,26 @@ + { + 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) { +- const struct net_device_ops *ops = port->dev->netdev_ops; +- +- if (ops->ndo_vlan_rx_add_vid) +- ops->ndo_vlan_rx_add_vid(port->dev, vid); ++ /* ++ * Alhough this is reader, it's guarded by team lock. It's not possible ++ * to traverse list in reverse under rcu_read_lock ++ */ ++ mutex_lock(&team->lock); ++ list_for_each_entry(port, &team->port_list, list) { ++ err = vlan_vid_add(port->dev, vid); ++ if (err) ++ goto unwind; + } +- rcu_read_unlock(); ++ mutex_unlock(&team->lock); ++ ++ return; ++ ++unwind: ++ list_for_each_entry_continue_reverse(port, &team->port_list, list) ++ vlan_vid_del(port->dev, vid); ++ mutex_unlock(&team->lock); + } + + static void team_vlan_rx_kill_vid(struct net_device *dev, uint16_t vid) +@@ -923,12 +935,8 @@ + 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; +- +- if (ops->ndo_vlan_rx_kill_vid) +- ops->ndo_vlan_rx_kill_vid(port->dev, vid); +- } ++ list_for_each_entry_rcu(port, &team->port_list, list) ++ vlan_vid_del(port->dev, vid); + rcu_read_unlock(); + } + +--- a/include/linux/if_vlan.h ++++ b/include/linux/if_vlan.h +@@ -109,6 +109,9 @@ + extern bool vlan_do_receive(struct sk_buff **skb, bool last_handler); + extern struct sk_buff *vlan_untag(struct sk_buff *skb); + ++extern int vlan_vid_add(struct net_device *dev, unsigned short vid); ++extern void vlan_vid_del(struct net_device *dev, unsigned short vid); ++ + #else + static inline struct net_device * + __vlan_find_dev_deep(struct net_device *real_dev, u16 vlan_id) +@@ -139,6 +142,15 @@ + { + return skb; + } ++ ++static inline int vlan_vid_add(struct net_device *dev, unsigned short vid) ++{ ++ return 0; ++} ++ ++static inline void vlan_vid_del(struct net_device *dev, unsigned short vid) ++{ ++} + #endif + + /** +--- a/net/8021q/vlan_core.c ++++ b/net/8021q/vlan_core.c +@@ -179,3 +179,26 @@ + kfree_skb(skb); + return NULL; + } ++ ++int vlan_vid_add(struct net_device *dev, unsigned short vid) ++{ ++ const struct net_device_ops *ops = dev->netdev_ops; ++ ++ if ((dev->features & NETIF_F_HW_VLAN_FILTER) && ++ ops->ndo_vlan_rx_add_vid) { ++ ops->ndo_vlan_rx_add_vid(dev, vid); ++ } ++ return 0; ++} ++EXPORT_SYMBOL(vlan_vid_add); ++ ++void vlan_vid_del(struct net_device *dev, unsigned short vid) ++{ ++ const struct net_device_ops *ops = dev->netdev_ops; ++ ++ if ((dev->features & NETIF_F_HW_VLAN_FILTER) && ++ ops->ndo_vlan_rx_kill_vid) { ++ ops->ndo_vlan_rx_kill_vid(dev, vid); ++ } ++} ++EXPORT_SYMBOL(vlan_vid_del); diff --git a/debian/patches/features/all/team/0010-vlan-introduce-functions-to-do-mass-addition-deletio.patch b/debian/patches/features/all/team/0010-vlan-introduce-functions-to-do-mass-addition-deletio.patch new file mode 100644 index 000000000..f6ebb7e8c --- /dev/null +++ b/debian/patches/features/all/team/0010-vlan-introduce-functions-to-do-mass-addition-deletio.patch @@ -0,0 +1,81 @@ +From: Jiri Pirko +Date: Thu, 8 Dec 2011 04:11:19 +0000 +Subject: [10/23] vlan: introduce functions to do mass addition/deletion of vids by + another device + +commit 348a1443cc4303c72cf1ee3b26e476fec8e7b5fa upstream. + +Introduce functions handy to copy vlan ids from one driver's list to +another. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +[bwh: Assume ndo_vlan_rx_add_id still returns void] +--- +--- a/include/linux/if_vlan.h ++++ b/include/linux/if_vlan.h +@@ -112,6 +112,10 @@ + extern int vlan_vid_add(struct net_device *dev, unsigned short vid); + extern void vlan_vid_del(struct net_device *dev, unsigned short vid); + ++extern int vlan_vids_add_by_dev(struct net_device *dev, ++ const struct net_device *by_dev); ++extern void vlan_vids_del_by_dev(struct net_device *dev, ++ const struct net_device *by_dev); + #else + static inline struct net_device * + __vlan_find_dev_deep(struct net_device *real_dev, u16 vlan_id) +@@ -151,6 +155,16 @@ + static inline void vlan_vid_del(struct net_device *dev, unsigned short vid) + { + } ++ ++static inline int vlan_vids_add_by_dev(struct net_device *dev, ++ const struct net_device *by_dev) ++{ ++} ++ ++static inline void vlan_vids_del_by_dev(struct net_device *dev, ++ const struct net_device *by_dev) ++{ ++} + #endif + + /** +--- a/net/8021q/vlan_core.c ++++ b/net/8021q/vlan_core.c +@@ -202,3 +202,34 @@ + } + } + EXPORT_SYMBOL(vlan_vid_del); ++ ++int vlan_vids_add_by_dev(struct net_device *dev, ++ const struct net_device *by_dev) ++{ ++ struct vlan_vid_info *vid_info; ++ ++ ASSERT_RTNL(); ++ ++ if (!by_dev->vlan_info) ++ return; ++ ++ list_for_each_entry(vid_info, &by_dev->vlan_info->vid_list, list) { ++ vlan_vid_add(dev, vid_info->vid); ++ } ++ return 0; ++} ++EXPORT_SYMBOL(vlan_vids_add_by_dev); ++ ++void vlan_vids_del_by_dev(struct net_device *dev, ++ const struct net_device *by_dev) ++{ ++ struct vlan_vid_info *vid_info; ++ ++ ASSERT_RTNL(); ++ ++ if (!by_dev->vlan_info) ++ return; ++ ++ list_for_each_entry(vid_info, &by_dev->vlan_info->vid_list, list) ++ vlan_vid_del(dev, vid_info->vid); ++} diff --git a/debian/patches/features/all/team/0011-team-use-vlan_vids_-addr-del-_by_dev.patch b/debian/patches/features/all/team/0011-team-use-vlan_vids_-addr-del-_by_dev.patch new file mode 100644 index 000000000..9cbbde450 --- /dev/null +++ b/debian/patches/features/all/team/0011-team-use-vlan_vids_-addr-del-_by_dev.patch @@ -0,0 +1,53 @@ +From: Jiri Pirko +Date: Thu, 8 Dec 2011 04:11:20 +0000 +Subject: [11/23] team: use vlan_vids_[addr/del]_by_dev + +commit 57459185a19b0246866479522b77cbb9732201d1 upstream. + +So far when vlan id was added to team device befor port was added, this +vid was not added to port's vlan filter. Also after removal, vid stayed +in port device's vlan filter. Benefit of new vlan functions to handle +this work. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 79c2d1b..ed2a862 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -588,6 +588,13 @@ static int team_port_add(struct team *team, struct net_device *port_dev) + goto err_dev_open; + } + ++ err = vlan_vids_add_by_dev(port_dev, dev); ++ if (err) { ++ netdev_err(dev, "Failed to add vlan ids to device %s\n", ++ portname); ++ goto err_vids_add; ++ } ++ + err = netdev_set_master(port_dev, dev); + if (err) { + netdev_err(dev, "Device %s failed to set master\n", portname); +@@ -615,6 +622,9 @@ err_handler_register: + netdev_set_master(port_dev, NULL); + + err_set_master: ++ vlan_vids_del_by_dev(port_dev, dev); ++ ++err_vids_add: + dev_close(port_dev); + + err_dev_open: +@@ -648,6 +658,7 @@ static int team_port_del(struct team *team, struct net_device *port_dev) + team_adjust_ops(team); + netdev_rx_handler_unregister(port_dev); + netdev_set_master(port_dev, NULL); ++ vlan_vids_del_by_dev(port_dev, dev); + dev_close(port_dev); + team_port_leave(team, port); + team_port_set_orig_mac(port); diff --git a/debian/patches/features/all/team/0012-team-send-only-changed-options-ports-via-netlink.patch b/debian/patches/features/all/team/0012-team-send-only-changed-options-ports-via-netlink.patch new file mode 100644 index 000000000..1e8d7bb6b --- /dev/null +++ b/debian/patches/features/all/team/0012-team-send-only-changed-options-ports-via-netlink.patch @@ -0,0 +1,371 @@ +From: Jiri Pirko +Date: Tue, 24 Jan 2012 05:16:00 +0000 +Subject: [12/23] team: send only changed options/ports via netlink + +commit b82b9183d4f18f9b8c4bb31f223eb6c79b734eb0 upstream. + +This patch changes event message behaviour to send only updated records +instead of whole list. This fixes bug on which userspace receives non-actual +data in case multiple events occur in row. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 136 +++++++++++++++++++++++++++++++---------------- + include/linux/if_team.h | 10 ++++ + 2 files changed, 100 insertions(+), 46 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index ed2a862..6b678f3 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -92,9 +92,9 @@ struct team_option *__team_find_option(struct team *team, const char *opt_name) + return NULL; + } + +-int team_options_register(struct team *team, +- const struct team_option *option, +- size_t option_count) ++int __team_options_register(struct team *team, ++ const struct team_option *option, ++ size_t option_count) + { + int i; + struct team_option **dst_opts; +@@ -116,8 +116,11 @@ int team_options_register(struct team *team, + } + } + +- for (i = 0; i < option_count; i++) ++ for (i = 0; i < option_count; i++) { ++ dst_opts[i]->changed = true; ++ dst_opts[i]->removed = false; + list_add_tail(&dst_opts[i]->list, &team->option_list); ++ } + + kfree(dst_opts); + return 0; +@@ -130,10 +133,22 @@ rollback: + return err; + } + +-EXPORT_SYMBOL(team_options_register); ++static void __team_options_mark_removed(struct team *team, ++ const struct team_option *option, ++ size_t option_count) ++{ ++ int i; ++ ++ for (i = 0; i < option_count; i++, option++) { ++ struct team_option *del_opt; + +-static void __team_options_change_check(struct team *team, +- struct team_option *changed_option); ++ del_opt = __team_find_option(team, option->name); ++ if (del_opt) { ++ del_opt->changed = true; ++ del_opt->removed = true; ++ } ++ } ++} + + static void __team_options_unregister(struct team *team, + const struct team_option *option, +@@ -152,12 +167,29 @@ static void __team_options_unregister(struct team *team, + } + } + ++static void __team_options_change_check(struct team *team); ++ ++int team_options_register(struct team *team, ++ const struct team_option *option, ++ size_t option_count) ++{ ++ int err; ++ ++ err = __team_options_register(team, option, option_count); ++ if (err) ++ return err; ++ __team_options_change_check(team); ++ return 0; ++} ++EXPORT_SYMBOL(team_options_register); ++ + void team_options_unregister(struct team *team, + const struct team_option *option, + size_t option_count) + { ++ __team_options_mark_removed(team, option, option_count); ++ __team_options_change_check(team); + __team_options_unregister(team, option, option_count); +- __team_options_change_check(team, NULL); + } + EXPORT_SYMBOL(team_options_unregister); + +@@ -176,7 +208,8 @@ static int team_option_set(struct team *team, struct team_option *option, + if (err) + return err; + +- __team_options_change_check(team, option); ++ option->changed = true; ++ __team_options_change_check(team); + return err; + } + +@@ -653,6 +686,7 @@ static int team_port_del(struct team *team, struct net_device *port_dev) + return -ENOENT; + } + ++ port->removed = true; + __team_port_change_check(port, false); + team_port_list_del_port(team, port); + team_adjust_ops(team); +@@ -1200,10 +1234,9 @@ err_fill: + 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) ++static int team_nl_fill_options_get(struct sk_buff *skb, ++ u32 pid, u32 seq, int flags, ++ struct team *team, bool fillall) + { + struct nlattr *option_list; + void *hdr; +@@ -1223,12 +1256,19 @@ static int team_nl_fill_options_get_changed(struct sk_buff *skb, + struct nlattr *option_item; + long arg; + ++ /* Include only changed options if fill all mode is not on */ ++ if (!fillall && !option->changed) ++ continue; + 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) ++ if (option->changed) { + NLA_PUT_FLAG(skb, TEAM_ATTR_OPTION_CHANGED); ++ option->changed = false; ++ } ++ if (option->removed) ++ NLA_PUT_FLAG(skb, TEAM_ATTR_OPTION_REMOVED); + switch (option->type) { + case TEAM_OPTION_TYPE_U32: + NLA_PUT_U8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32); +@@ -1255,13 +1295,13 @@ nla_put_failure: + return -EMSGSIZE; + } + +-static int team_nl_fill_options_get(struct sk_buff *skb, +- struct genl_info *info, int flags, +- struct team *team) ++static int team_nl_fill_options_get_all(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); ++ return team_nl_fill_options_get(skb, info->snd_pid, ++ info->snd_seq, NLM_F_ACK, ++ team, true); + } + + static int team_nl_cmd_options_get(struct sk_buff *skb, struct genl_info *info) +@@ -1273,7 +1313,7 @@ static int team_nl_cmd_options_get(struct sk_buff *skb, struct genl_info *info) + if (!team) + return -EINVAL; + +- err = team_nl_send_generic(info, team, team_nl_fill_options_get); ++ err = team_nl_send_generic(info, team, team_nl_fill_options_get_all); + + team_nl_team_put(team); + +@@ -1365,10 +1405,10 @@ team_put: + 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) ++static int team_nl_fill_port_list_get(struct sk_buff *skb, ++ u32 pid, u32 seq, int flags, ++ struct team *team, ++ bool fillall) + { + struct nlattr *port_list; + void *hdr; +@@ -1387,12 +1427,19 @@ static int team_nl_fill_port_list_get_changed(struct sk_buff *skb, + list_for_each_entry(port, &team->port_list, list) { + struct nlattr *port_item; + ++ /* Include only changed ports if fill all mode is not on */ ++ if (!fillall && !port->changed) ++ continue; + 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) ++ if (port->changed) { + NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_CHANGED); ++ port->changed = false; ++ } ++ if (port->removed) ++ NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_REMOVED); + if (port->linkup) + NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_LINKUP); + NLA_PUT_U32(skb, TEAM_ATTR_PORT_SPEED, port->speed); +@@ -1408,13 +1455,13 @@ nla_put_failure: + return -EMSGSIZE; + } + +-static int team_nl_fill_port_list_get(struct sk_buff *skb, +- struct genl_info *info, int flags, +- struct team *team) ++static int team_nl_fill_port_list_get_all(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); ++ return team_nl_fill_port_list_get(skb, info->snd_pid, ++ info->snd_seq, NLM_F_ACK, ++ team, true); + } + + static int team_nl_cmd_port_list_get(struct sk_buff *skb, +@@ -1427,7 +1474,7 @@ static int team_nl_cmd_port_list_get(struct sk_buff *skb, + if (!team) + return -EINVAL; + +- err = team_nl_send_generic(info, team, team_nl_fill_port_list_get); ++ err = team_nl_send_generic(info, team, team_nl_fill_port_list_get_all); + + team_nl_team_put(team); + +@@ -1464,8 +1511,7 @@ 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) ++static int team_nl_send_event_options_get(struct team *team) + { + struct sk_buff *skb; + int err; +@@ -1475,8 +1521,7 @@ static int team_nl_send_event_options_get(struct team *team, + if (!skb) + return -ENOMEM; + +- err = team_nl_fill_options_get_changed(skb, 0, 0, 0, team, +- changed_option); ++ err = team_nl_fill_options_get(skb, 0, 0, 0, team, false); + if (err < 0) + goto err_fill; + +@@ -1489,18 +1534,17 @@ err_fill: + return err; + } + +-static int team_nl_send_event_port_list_get(struct team_port *port) ++static int team_nl_send_event_port_list_get(struct team *team) + { + struct sk_buff *skb; + int err; +- struct net *net = dev_net(port->team->dev); ++ struct net *net = dev_net(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); ++ err = team_nl_fill_port_list_get(skb, 0, 0, 0, team, false); + if (err < 0) + goto err_fill; + +@@ -1544,12 +1588,11 @@ static void team_nl_fini(void) + * Change checkers + ******************/ + +-static void __team_options_change_check(struct team *team, +- struct team_option *changed_option) ++static void __team_options_change_check(struct team *team) + { + int err; + +- err = team_nl_send_event_options_get(team, changed_option); ++ err = team_nl_send_event_options_get(team); + if (err) + netdev_warn(team->dev, "Failed to send options change via netlink\n"); + } +@@ -1559,9 +1602,10 @@ static void __team_port_change_check(struct team_port *port, bool linkup) + { + int err; + +- if (port->linkup == linkup) ++ if (!port->removed && port->linkup == linkup) + return; + ++ port->changed = true; + port->linkup = linkup; + if (linkup) { + struct ethtool_cmd ecmd; +@@ -1577,7 +1621,7 @@ static void __team_port_change_check(struct team_port *port, bool linkup) + port->duplex = 0; + + send_event: +- err = team_nl_send_event_port_list_get(port); ++ err = team_nl_send_event_port_list_get(port->team); + if (err) + netdev_warn(port->team->dev, "Failed to send port change of device %s via netlink\n", + port->dev->name); +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index 828181f..58404b0 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -46,6 +46,10 @@ struct team_port { + u32 speed; + u8 duplex; + ++ /* Custom gennetlink interface related flags */ ++ bool changed; ++ bool removed; ++ + struct rcu_head rcu; + }; + +@@ -72,6 +76,10 @@ struct team_option { + enum team_option_type type; + int (*getter)(struct team *team, void *arg); + int (*setter)(struct team *team, void *arg); ++ ++ /* Custom gennetlink interface related flags */ ++ bool changed; ++ bool removed; + }; + + struct team_mode { +@@ -207,6 +215,7 @@ enum { + TEAM_ATTR_OPTION_CHANGED, /* flag */ + TEAM_ATTR_OPTION_TYPE, /* u8 */ + TEAM_ATTR_OPTION_DATA, /* dynamic */ ++ TEAM_ATTR_OPTION_REMOVED, /* flag */ + + __TEAM_ATTR_OPTION_MAX, + TEAM_ATTR_OPTION_MAX = __TEAM_ATTR_OPTION_MAX - 1, +@@ -227,6 +236,7 @@ enum { + TEAM_ATTR_PORT_LINKUP, /* flag */ + TEAM_ATTR_PORT_SPEED, /* u32 */ + TEAM_ATTR_PORT_DUPLEX, /* u8 */ ++ TEAM_ATTR_PORT_REMOVED, /* flag */ + + __TEAM_ATTR_PORT_MAX, + TEAM_ATTR_PORT_MAX = __TEAM_ATTR_PORT_MAX - 1, diff --git a/debian/patches/features/all/team/0013-team-Stop-using-NLA_PUT.patch b/debian/patches/features/all/team/0013-team-Stop-using-NLA_PUT.patch new file mode 100644 index 000000000..5f8f3a28c --- /dev/null +++ b/debian/patches/features/all/team/0013-team-Stop-using-NLA_PUT.patch @@ -0,0 +1,108 @@ +From: "David S. Miller" +Date: Sun, 1 Apr 2012 20:25:18 -0400 +Subject: [13/23] team: Stop using NLA_PUT*(). + +commit 86ebb02dc793058ea17ad647c802b507dafff7cb upstream. + +These macros contain a hidden goto, and are thus extremely error +prone and make code hard to audit. + +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 50 +++++++++++++++++++++++++++++------------------ + 1 file changed, 31 insertions(+), 19 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 8f81805..0db6e66 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -1248,7 +1248,8 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + if (IS_ERR(hdr)) + return PTR_ERR(hdr); + +- NLA_PUT_U32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex); ++ if (nla_put_u32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex)) ++ goto nla_put_failure; + option_list = nla_nest_start(skb, TEAM_ATTR_LIST_OPTION); + if (!option_list) + return -EMSGSIZE; +@@ -1263,24 +1264,31 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + 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 (nla_put_string(skb, TEAM_ATTR_OPTION_NAME, option->name)) ++ goto nla_put_failure; + if (option->changed) { +- NLA_PUT_FLAG(skb, TEAM_ATTR_OPTION_CHANGED); ++ if (nla_put_flag(skb, TEAM_ATTR_OPTION_CHANGED)) ++ goto nla_put_failure; + option->changed = false; + } +- if (option->removed) +- NLA_PUT_FLAG(skb, TEAM_ATTR_OPTION_REMOVED); ++ if (option->removed && ++ nla_put_flag(skb, TEAM_ATTR_OPTION_REMOVED)) ++ goto nla_put_failure; + switch (option->type) { + case TEAM_OPTION_TYPE_U32: +- NLA_PUT_U8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32); ++ if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32)) ++ goto nla_put_failure; + team_option_get(team, option, &arg); +- NLA_PUT_U32(skb, TEAM_ATTR_OPTION_DATA, arg); ++ if (nla_put_u32(skb, TEAM_ATTR_OPTION_DATA, arg)) ++ goto nla_put_failure; + break; + case TEAM_OPTION_TYPE_STRING: +- NLA_PUT_U8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING); ++ if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING)) ++ goto nla_put_failure; + team_option_get(team, option, &arg); +- NLA_PUT_STRING(skb, TEAM_ATTR_OPTION_DATA, +- (char *) arg); ++ if (nla_put_string(skb, TEAM_ATTR_OPTION_DATA, ++ (char *) arg)) ++ goto nla_put_failure; + break; + default: + BUG(); +@@ -1420,7 +1428,8 @@ static int team_nl_fill_port_list_get(struct sk_buff *skb, + if (IS_ERR(hdr)) + return PTR_ERR(hdr); + +- NLA_PUT_U32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex); ++ if (nla_put_u32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex)) ++ goto nla_put_failure; + port_list = nla_nest_start(skb, TEAM_ATTR_LIST_PORT); + if (!port_list) + return -EMSGSIZE; +@@ -1434,17 +1443,20 @@ static int team_nl_fill_port_list_get(struct sk_buff *skb, + 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 (nla_put_u32(skb, TEAM_ATTR_PORT_IFINDEX, port->dev->ifindex)) ++ goto nla_put_failure; + if (port->changed) { +- NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_CHANGED); ++ if (nla_put_flag(skb, TEAM_ATTR_PORT_CHANGED)) ++ goto nla_put_failure; + port->changed = false; + } +- if (port->removed) +- NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_REMOVED); +- 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); ++ if ((port->removed && ++ nla_put_flag(skb, TEAM_ATTR_PORT_REMOVED)) || ++ (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)) ++ goto nla_put_failure; + nla_nest_end(skb, port_item); + } + diff --git a/debian/patches/features/all/team/0014-team-add-binary-option-type.patch b/debian/patches/features/all/team/0014-team-add-binary-option-type.patch new file mode 100644 index 000000000..b1fadec16 --- /dev/null +++ b/debian/patches/features/all/team/0014-team-add-binary-option-type.patch @@ -0,0 +1,124 @@ +From: Jiri Pirko +Date: Wed, 4 Apr 2012 12:16:26 +0000 +Subject: [14/23] team: add binary option type + +commit 2615598fc100451c71b83d06bdf5faead619a40e upstream. + +For transfering generic binary data (e.g. BPF code), introduce new +binary option type. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 30 ++++++++++++++++++++++++++---- + include/linux/if_team.h | 8 ++++++++ + 2 files changed, 34 insertions(+), 4 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 0db6e66..ea96f82 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -1145,10 +1145,7 @@ team_nl_option_policy[TEAM_ATTR_OPTION_MAX + 1] = { + }, + [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, +- }, ++ [TEAM_ATTR_OPTION_DATA] = { .type = NLA_BINARY }, + }; + + static int team_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info) +@@ -1257,6 +1254,7 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + list_for_each_entry(option, &team->option_list, list) { + struct nlattr *option_item; + long arg; ++ struct team_option_binary tbinary; + + /* Include only changed options if fill all mode is not on */ + if (!fillall && !option->changed) +@@ -1290,6 +1288,15 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + (char *) arg)) + goto nla_put_failure; + break; ++ case TEAM_OPTION_TYPE_BINARY: ++ if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_BINARY)) ++ goto nla_put_failure; ++ arg = (long) &tbinary; ++ team_option_get(team, option, &arg); ++ if (nla_put(skb, TEAM_ATTR_OPTION_DATA, ++ tbinary.data_len, tbinary.data)) ++ goto nla_put_failure; ++ break; + default: + BUG(); + } +@@ -1374,6 +1381,9 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + case NLA_STRING: + opt_type = TEAM_OPTION_TYPE_STRING; + break; ++ case NLA_BINARY: ++ opt_type = TEAM_OPTION_TYPE_BINARY; ++ break; + default: + goto team_put; + } +@@ -1382,19 +1392,31 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + list_for_each_entry(option, &team->option_list, list) { + long arg; + struct nlattr *opt_data_attr; ++ struct team_option_binary tbinary; ++ int data_len; + + if (option->type != opt_type || + strcmp(option->name, opt_name)) + continue; + opt_found = true; + opt_data_attr = mode_attrs[TEAM_ATTR_OPTION_DATA]; ++ data_len = nla_len(opt_data_attr); + switch (opt_type) { + case TEAM_OPTION_TYPE_U32: + arg = nla_get_u32(opt_data_attr); + break; + case TEAM_OPTION_TYPE_STRING: ++ if (data_len > TEAM_STRING_MAX_LEN) { ++ err = -EINVAL; ++ goto team_put; ++ } + arg = (long) nla_data(opt_data_attr); + break; ++ case TEAM_OPTION_TYPE_BINARY: ++ tbinary.data_len = data_len; ++ tbinary.data = nla_data(opt_data_attr); ++ arg = (long) &tbinary; ++ break; + default: + BUG(); + } +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index 58404b0..41163ac 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -68,6 +68,7 @@ struct team_mode_ops { + enum team_option_type { + TEAM_OPTION_TYPE_U32, + TEAM_OPTION_TYPE_STRING, ++ TEAM_OPTION_TYPE_BINARY, + }; + + struct team_option { +@@ -82,6 +83,13 @@ struct team_option { + bool removed; + }; + ++struct team_option_binary { ++ u32 data_len; ++ void *data; ++}; ++ ++#define team_optarg_tbinary(arg) (*((struct team_option_binary **) arg)) ++ + struct team_mode { + struct list_head list; + const char *kind; diff --git a/debian/patches/features/all/team/0015-team-add-loadbalance-mode.patch b/debian/patches/features/all/team/0015-team-add-loadbalance-mode.patch new file mode 100644 index 000000000..3158c3010 --- /dev/null +++ b/debian/patches/features/all/team/0015-team-add-loadbalance-mode.patch @@ -0,0 +1,241 @@ +From: Jiri Pirko +Date: Wed, 4 Apr 2012 12:16:27 +0000 +Subject: [15/23] team: add loadbalance mode + +commit 01d7f30a9f962573b6c91ed520c73fb30658d826 upstream. + +This patch introduces new team mode. It's TX port is selected by +user-set BPF hash function. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/Kconfig | 11 ++ + drivers/net/team/Makefile | 1 + + drivers/net/team/team_mode_loadbalance.c | 188 ++++++++++++++++++++++++++++++ + 3 files changed, 200 insertions(+) + create mode 100644 drivers/net/team/team_mode_loadbalance.c + +diff --git a/drivers/net/team/Kconfig b/drivers/net/team/Kconfig +index 248a144..89024d5 100644 +--- a/drivers/net/team/Kconfig ++++ b/drivers/net/team/Kconfig +@@ -40,4 +40,15 @@ config NET_TEAM_MODE_ACTIVEBACKUP + To compile this team mode as a module, choose M here: the module + will be called team_mode_activebackup. + ++config NET_TEAM_MODE_LOADBALANCE ++ tristate "Load-balance mode support" ++ depends on NET_TEAM ++ ---help--- ++ This mode provides load balancing functionality. Tx port selection ++ is done using BPF function set up from userspace (bpf_hash_func ++ option) ++ ++ To compile this team mode as a module, choose M here: the module ++ will be called team_mode_loadbalance. ++ + endif # NET_TEAM +diff --git a/drivers/net/team/Makefile b/drivers/net/team/Makefile +index 85f2028..fb9f4c1 100644 +--- a/drivers/net/team/Makefile ++++ b/drivers/net/team/Makefile +@@ -5,3 +5,4 @@ + 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 ++obj-$(CONFIG_NET_TEAM_MODE_LOADBALANCE) += team_mode_loadbalance.o +diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c +new file mode 100644 +index 0000000..ed20f39 +--- /dev/null ++++ b/drivers/net/team/team_mode_loadbalance.c +@@ -0,0 +1,188 @@ ++/* ++ * drivers/net/team/team_mode_loadbalance.c - Load-balancing mode for team ++ * Copyright (c) 2012 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 lb_priv { ++ struct sk_filter __rcu *fp; ++ struct sock_fprog *orig_fprog; ++}; ++ ++static struct lb_priv *lb_priv(struct team *team) ++{ ++ return (struct lb_priv *) &team->mode_priv; ++} ++ ++static bool lb_transmit(struct team *team, struct sk_buff *skb) ++{ ++ struct sk_filter *fp; ++ struct team_port *port; ++ unsigned int hash; ++ int port_index; ++ ++ fp = rcu_dereference(lb_priv(team)->fp); ++ if (unlikely(!fp)) ++ goto drop; ++ hash = SK_RUN_FILTER(fp, skb); ++ port_index = hash % team->port_count; ++ port = team_get_port_by_index_rcu(team, port_index); ++ if (unlikely(!port)) ++ goto drop; ++ skb->dev = port->dev; ++ if (dev_queue_xmit(skb)) ++ return false; ++ return true; ++ ++drop: ++ dev_kfree_skb_any(skb); ++ return false; ++} ++ ++static int lb_bpf_func_get(struct team *team, void *arg) ++{ ++ struct team_option_binary *tbinary = team_optarg_tbinary(arg); ++ ++ memset(tbinary, 0, sizeof(*tbinary)); ++ if (!lb_priv(team)->orig_fprog) ++ return 0; ++ ++ tbinary->data_len = lb_priv(team)->orig_fprog->len * ++ sizeof(struct sock_filter); ++ tbinary->data = lb_priv(team)->orig_fprog->filter; ++ return 0; ++} ++ ++static int __fprog_create(struct sock_fprog **pfprog, u32 data_len, ++ void *data) ++{ ++ struct sock_fprog *fprog; ++ struct sock_filter *filter = (struct sock_filter *) data; ++ ++ if (data_len % sizeof(struct sock_filter)) ++ return -EINVAL; ++ fprog = kmalloc(sizeof(struct sock_fprog), GFP_KERNEL); ++ if (!fprog) ++ return -ENOMEM; ++ fprog->filter = kmemdup(filter, data_len, GFP_KERNEL); ++ if (!fprog->filter) { ++ kfree(fprog); ++ return -ENOMEM; ++ } ++ fprog->len = data_len / sizeof(struct sock_filter); ++ *pfprog = fprog; ++ return 0; ++} ++ ++static void __fprog_destroy(struct sock_fprog *fprog) ++{ ++ kfree(fprog->filter); ++ kfree(fprog); ++} ++ ++static int lb_bpf_func_set(struct team *team, void *arg) ++{ ++ struct team_option_binary *tbinary = team_optarg_tbinary(arg); ++ struct sk_filter *fp = NULL; ++ struct sock_fprog *fprog = NULL; ++ int err; ++ ++ if (tbinary->data_len) { ++ err = __fprog_create(&fprog, tbinary->data_len, ++ tbinary->data); ++ if (err) ++ return err; ++ err = sk_unattached_filter_create(&fp, fprog); ++ if (err) { ++ __fprog_destroy(fprog); ++ return err; ++ } ++ } ++ ++ if (lb_priv(team)->orig_fprog) { ++ /* Clear old filter data */ ++ __fprog_destroy(lb_priv(team)->orig_fprog); ++ sk_unattached_filter_destroy(lb_priv(team)->fp); ++ } ++ ++ rcu_assign_pointer(lb_priv(team)->fp, fp); ++ lb_priv(team)->orig_fprog = fprog; ++ return 0; ++} ++ ++static const struct team_option lb_options[] = { ++ { ++ .name = "bpf_hash_func", ++ .type = TEAM_OPTION_TYPE_BINARY, ++ .getter = lb_bpf_func_get, ++ .setter = lb_bpf_func_set, ++ }, ++}; ++ ++int lb_init(struct team *team) ++{ ++ return team_options_register(team, lb_options, ++ ARRAY_SIZE(lb_options)); ++} ++ ++void lb_exit(struct team *team) ++{ ++ team_options_unregister(team, lb_options, ++ ARRAY_SIZE(lb_options)); ++} ++ ++static int lb_port_enter(struct team *team, struct team_port *port) ++{ ++ return team_port_set_team_mac(port); ++} ++ ++static void lb_port_change_mac(struct team *team, struct team_port *port) ++{ ++ team_port_set_team_mac(port); ++} ++ ++static const struct team_mode_ops lb_mode_ops = { ++ .init = lb_init, ++ .exit = lb_exit, ++ .transmit = lb_transmit, ++ .port_enter = lb_port_enter, ++ .port_change_mac = lb_port_change_mac, ++}; ++ ++static struct team_mode lb_mode = { ++ .kind = "loadbalance", ++ .owner = THIS_MODULE, ++ .priv_size = sizeof(struct lb_priv), ++ .ops = &lb_mode_ops, ++}; ++ ++static int __init lb_init_module(void) ++{ ++ return team_mode_register(&lb_mode); ++} ++ ++static void __exit lb_cleanup_module(void) ++{ ++ team_mode_unregister(&lb_mode); ++} ++ ++module_init(lb_init_module); ++module_exit(lb_cleanup_module); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Jiri Pirko "); ++MODULE_DESCRIPTION("Load-balancing mode for team"); ++MODULE_ALIAS("team-mode-loadbalance"); diff --git a/debian/patches/features/all/team/0016-team-add-support-for-per-port-options.patch b/debian/patches/features/all/team/0016-team-add-support-for-per-port-options.patch new file mode 100644 index 000000000..204877e6a --- /dev/null +++ b/debian/patches/features/all/team/0016-team-add-support-for-per-port-options.patch @@ -0,0 +1,687 @@ +From: Jiri Pirko +Date: Tue, 10 Apr 2012 05:15:42 +0000 +Subject: [16/23] team: add support for per-port options + +commit 80f7c6683fe0e891ef1db7c967d538b5fdddd22c upstream. + +This patch allows to create per-port options. That becomes handy for all +sorts of stuff, for example for userspace driven link-state, 802.3ad +implementation and so on. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 306 +++++++++++++++++++++++------ + drivers/net/team/team_mode_activebackup.c | 14 +- + drivers/net/team/team_mode_loadbalance.c | 28 ++- + include/linux/if_team.h | 30 +-- + 4 files changed, 278 insertions(+), 100 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index ea96f82..eaf8441 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -81,7 +81,16 @@ EXPORT_SYMBOL(team_port_set_team_mac); + * Options handling + *******************/ + +-struct team_option *__team_find_option(struct team *team, const char *opt_name) ++struct team_option_inst { /* One for each option instance */ ++ struct list_head list; ++ struct team_option *option; ++ struct team_port *port; /* != NULL if per-port */ ++ bool changed; ++ bool removed; ++}; ++ ++static struct team_option *__team_find_option(struct team *team, ++ const char *opt_name) + { + struct team_option *option; + +@@ -92,9 +101,121 @@ struct team_option *__team_find_option(struct team *team, const char *opt_name) + return NULL; + } + +-int __team_options_register(struct team *team, +- const struct team_option *option, +- size_t option_count) ++static int __team_option_inst_add(struct team *team, struct team_option *option, ++ struct team_port *port) ++{ ++ struct team_option_inst *opt_inst; ++ ++ opt_inst = kmalloc(sizeof(*opt_inst), GFP_KERNEL); ++ if (!opt_inst) ++ return -ENOMEM; ++ opt_inst->option = option; ++ opt_inst->port = port; ++ opt_inst->changed = true; ++ opt_inst->removed = false; ++ list_add_tail(&opt_inst->list, &team->option_inst_list); ++ return 0; ++} ++ ++static void __team_option_inst_del(struct team_option_inst *opt_inst) ++{ ++ list_del(&opt_inst->list); ++ kfree(opt_inst); ++} ++ ++static void __team_option_inst_del_option(struct team *team, ++ struct team_option *option) ++{ ++ struct team_option_inst *opt_inst, *tmp; ++ ++ list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) { ++ if (opt_inst->option == option) ++ __team_option_inst_del(opt_inst); ++ } ++} ++ ++static int __team_option_inst_add_option(struct team *team, ++ struct team_option *option) ++{ ++ struct team_port *port; ++ int err; ++ ++ if (!option->per_port) ++ return __team_option_inst_add(team, option, 0); ++ ++ list_for_each_entry(port, &team->port_list, list) { ++ err = __team_option_inst_add(team, option, port); ++ if (err) ++ goto inst_del_option; ++ } ++ return 0; ++ ++inst_del_option: ++ __team_option_inst_del_option(team, option); ++ return err; ++} ++ ++static void __team_option_inst_mark_removed_option(struct team *team, ++ struct team_option *option) ++{ ++ struct team_option_inst *opt_inst; ++ ++ list_for_each_entry(opt_inst, &team->option_inst_list, list) { ++ if (opt_inst->option == option) { ++ opt_inst->changed = true; ++ opt_inst->removed = true; ++ } ++ } ++} ++ ++static void __team_option_inst_del_port(struct team *team, ++ struct team_port *port) ++{ ++ struct team_option_inst *opt_inst, *tmp; ++ ++ list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) { ++ if (opt_inst->option->per_port && ++ opt_inst->port == port) ++ __team_option_inst_del(opt_inst); ++ } ++} ++ ++static int __team_option_inst_add_port(struct team *team, ++ struct team_port *port) ++{ ++ struct team_option *option; ++ int err; ++ ++ list_for_each_entry(option, &team->option_list, list) { ++ if (!option->per_port) ++ continue; ++ err = __team_option_inst_add(team, option, port); ++ if (err) ++ goto inst_del_port; ++ } ++ return 0; ++ ++inst_del_port: ++ __team_option_inst_del_port(team, port); ++ return err; ++} ++ ++static void __team_option_inst_mark_removed_port(struct team *team, ++ struct team_port *port) ++{ ++ struct team_option_inst *opt_inst; ++ ++ list_for_each_entry(opt_inst, &team->option_inst_list, list) { ++ if (opt_inst->port == port) { ++ opt_inst->changed = true; ++ opt_inst->removed = true; ++ } ++ } ++} ++ ++static int __team_options_register(struct team *team, ++ const struct team_option *option, ++ size_t option_count) + { + int i; + struct team_option **dst_opts; +@@ -107,26 +228,32 @@ int __team_options_register(struct team *team, + for (i = 0; i < option_count; i++, option++) { + if (__team_find_option(team, option->name)) { + err = -EEXIST; +- goto rollback; ++ goto alloc_rollback; + } + dst_opts[i] = kmemdup(option, sizeof(*option), GFP_KERNEL); + if (!dst_opts[i]) { + err = -ENOMEM; +- goto rollback; ++ goto alloc_rollback; + } + } + + for (i = 0; i < option_count; i++) { +- dst_opts[i]->changed = true; +- dst_opts[i]->removed = false; ++ err = __team_option_inst_add_option(team, dst_opts[i]); ++ if (err) ++ goto inst_rollback; + list_add_tail(&dst_opts[i]->list, &team->option_list); + } + + kfree(dst_opts); + return 0; + +-rollback: +- for (i = 0; i < option_count; i++) ++inst_rollback: ++ for (i--; i >= 0; i--) ++ __team_option_inst_del_option(team, dst_opts[i]); ++ ++ i = option_count - 1; ++alloc_rollback: ++ for (i--; i >= 0; i--) + kfree(dst_opts[i]); + + kfree(dst_opts); +@@ -143,10 +270,8 @@ static void __team_options_mark_removed(struct team *team, + struct team_option *del_opt; + + del_opt = __team_find_option(team, option->name); +- if (del_opt) { +- del_opt->changed = true; +- del_opt->removed = true; +- } ++ if (del_opt) ++ __team_option_inst_mark_removed_option(team, del_opt); + } + } + +@@ -161,6 +286,7 @@ static void __team_options_unregister(struct team *team, + + del_opt = __team_find_option(team, option->name); + if (del_opt) { ++ __team_option_inst_del_option(team, del_opt); + list_del(&del_opt->list); + kfree(del_opt); + } +@@ -193,22 +319,42 @@ void team_options_unregister(struct team *team, + } + EXPORT_SYMBOL(team_options_unregister); + +-static int team_option_get(struct team *team, struct team_option *option, +- void *arg) ++static int team_option_port_add(struct team *team, struct team_port *port) + { +- return option->getter(team, arg); ++ int err; ++ ++ err = __team_option_inst_add_port(team, port); ++ if (err) ++ return err; ++ __team_options_change_check(team); ++ return 0; + } + +-static int team_option_set(struct team *team, struct team_option *option, +- void *arg) ++static void team_option_port_del(struct team *team, struct team_port *port) ++{ ++ __team_option_inst_mark_removed_port(team, port); ++ __team_options_change_check(team); ++ __team_option_inst_del_port(team, port); ++} ++ ++static int team_option_get(struct team *team, ++ struct team_option_inst *opt_inst, ++ struct team_gsetter_ctx *ctx) ++{ ++ return opt_inst->option->getter(team, ctx); ++} ++ ++static int team_option_set(struct team *team, ++ struct team_option_inst *opt_inst, ++ struct team_gsetter_ctx *ctx) + { + int err; + +- err = option->setter(team, arg); ++ err = opt_inst->option->setter(team, ctx); + if (err) + return err; + +- option->changed = true; ++ opt_inst->changed = true; + __team_options_change_check(team); + return err; + } +@@ -642,6 +788,13 @@ static int team_port_add(struct team *team, struct net_device *port_dev) + goto err_handler_register; + } + ++ err = team_option_port_add(team, port); ++ if (err) { ++ netdev_err(dev, "Device %s failed to add per-port options\n", ++ portname); ++ goto err_option_port_add; ++ } ++ + team_port_list_add_port(team, port); + team_adjust_ops(team); + __team_compute_features(team); +@@ -651,6 +804,9 @@ static int team_port_add(struct team *team, struct net_device *port_dev) + + return 0; + ++err_option_port_add: ++ netdev_rx_handler_unregister(port_dev); ++ + err_handler_register: + netdev_set_master(port_dev, NULL); + +@@ -690,6 +846,7 @@ static int team_port_del(struct team *team, struct net_device *port_dev) + __team_port_change_check(port, false); + team_port_list_del_port(team, port); + team_adjust_ops(team); ++ team_option_port_del(team, port); + netdev_rx_handler_unregister(port_dev); + netdev_set_master(port_dev, NULL); + vlan_vids_del_by_dev(port_dev, dev); +@@ -712,19 +869,15 @@ static int team_port_del(struct team *team, struct net_device *port_dev) + + static const char team_no_mode_kind[] = "*NOMODE*"; + +-static int team_mode_option_get(struct team *team, void *arg) ++static int team_mode_option_get(struct team *team, struct team_gsetter_ctx *ctx) + { +- const char **str = arg; +- +- *str = team->mode ? team->mode->kind : team_no_mode_kind; ++ ctx->data.str_val = team->mode ? team->mode->kind : team_no_mode_kind; + return 0; + } + +-static int team_mode_option_set(struct team *team, void *arg) ++static int team_mode_option_set(struct team *team, struct team_gsetter_ctx *ctx) + { +- const char **str = arg; +- +- return team_change_mode(team, *str); ++ return team_change_mode(team, ctx->data.str_val); + } + + static const struct team_option team_options[] = { +@@ -756,6 +909,7 @@ static int team_init(struct net_device *dev) + team_adjust_ops(team); + + INIT_LIST_HEAD(&team->option_list); ++ INIT_LIST_HEAD(&team->option_inst_list); + err = team_options_register(team, team_options, ARRAY_SIZE(team_options)); + if (err) + goto err_options_register; +@@ -1238,7 +1392,8 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + { + struct nlattr *option_list; + void *hdr; +- struct team_option *option; ++ struct team_option_inst *opt_inst; ++ int err; + + hdr = genlmsg_put(skb, pid, seq, &team_nl_family, flags, + TEAM_CMD_OPTIONS_GET); +@@ -1251,50 +1406,61 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + if (!option_list) + return -EMSGSIZE; + +- list_for_each_entry(option, &team->option_list, list) { ++ list_for_each_entry(opt_inst, &team->option_inst_list, list) { + struct nlattr *option_item; +- long arg; +- struct team_option_binary tbinary; ++ struct team_option *option = opt_inst->option; ++ struct team_gsetter_ctx ctx; + + /* Include only changed options if fill all mode is not on */ +- if (!fillall && !option->changed) ++ if (!fillall && !opt_inst->changed) + continue; + option_item = nla_nest_start(skb, TEAM_ATTR_ITEM_OPTION); + if (!option_item) + goto nla_put_failure; + if (nla_put_string(skb, TEAM_ATTR_OPTION_NAME, option->name)) + goto nla_put_failure; +- if (option->changed) { ++ if (opt_inst->changed) { + if (nla_put_flag(skb, TEAM_ATTR_OPTION_CHANGED)) + goto nla_put_failure; +- option->changed = false; ++ opt_inst->changed = false; + } +- if (option->removed && ++ if (opt_inst->removed && + nla_put_flag(skb, TEAM_ATTR_OPTION_REMOVED)) + goto nla_put_failure; ++ if (opt_inst->port && ++ nla_put_u32(skb, TEAM_ATTR_OPTION_PORT_IFINDEX, ++ opt_inst->port->dev->ifindex)) ++ goto nla_put_failure; ++ ctx.port = opt_inst->port; + switch (option->type) { + case TEAM_OPTION_TYPE_U32: + if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32)) + goto nla_put_failure; +- team_option_get(team, option, &arg); +- if (nla_put_u32(skb, TEAM_ATTR_OPTION_DATA, arg)) ++ err = team_option_get(team, opt_inst, &ctx); ++ if (err) ++ goto errout; ++ if (nla_put_u32(skb, TEAM_ATTR_OPTION_DATA, ++ ctx.data.u32_val)) + goto nla_put_failure; + break; + case TEAM_OPTION_TYPE_STRING: + if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING)) + goto nla_put_failure; +- team_option_get(team, option, &arg); ++ err = team_option_get(team, opt_inst, &ctx); ++ if (err) ++ goto errout; + if (nla_put_string(skb, TEAM_ATTR_OPTION_DATA, +- (char *) arg)) ++ ctx.data.str_val)) + goto nla_put_failure; + break; + case TEAM_OPTION_TYPE_BINARY: + if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_BINARY)) + goto nla_put_failure; +- arg = (long) &tbinary; +- team_option_get(team, option, &arg); ++ err = team_option_get(team, opt_inst, &ctx); ++ if (err) ++ goto errout; + if (nla_put(skb, TEAM_ATTR_OPTION_DATA, +- tbinary.data_len, tbinary.data)) ++ ctx.data.bin_val.len, ctx.data.bin_val.ptr)) + goto nla_put_failure; + break; + default: +@@ -1307,8 +1473,10 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + return genlmsg_end(skb, hdr); + + nla_put_failure: ++ err = -EMSGSIZE; ++errout: + genlmsg_cancel(skb, hdr); +- return -EMSGSIZE; ++ return err; + } + + static int team_nl_fill_options_get_all(struct sk_buff *skb, +@@ -1354,9 +1522,11 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + } + + nla_for_each_nested(nl_option, info->attrs[TEAM_ATTR_LIST_OPTION], i) { +- struct nlattr *mode_attrs[TEAM_ATTR_OPTION_MAX + 1]; ++ struct nlattr *opt_attrs[TEAM_ATTR_OPTION_MAX + 1]; ++ struct nlattr *attr_port_ifindex; + enum team_option_type opt_type; +- struct team_option *option; ++ int opt_port_ifindex = 0; /* != 0 for per-port options */ ++ struct team_option_inst *opt_inst; + char *opt_name; + bool opt_found = false; + +@@ -1364,17 +1534,17 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + err = -EINVAL; + goto team_put; + } +- err = nla_parse_nested(mode_attrs, TEAM_ATTR_OPTION_MAX, ++ err = nla_parse_nested(opt_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]) { ++ if (!opt_attrs[TEAM_ATTR_OPTION_NAME] || ++ !opt_attrs[TEAM_ATTR_OPTION_TYPE] || ++ !opt_attrs[TEAM_ATTR_OPTION_DATA]) { + err = -EINVAL; + goto team_put; + } +- switch (nla_get_u8(mode_attrs[TEAM_ATTR_OPTION_TYPE])) { ++ switch (nla_get_u8(opt_attrs[TEAM_ATTR_OPTION_TYPE])) { + case NLA_U32: + opt_type = TEAM_OPTION_TYPE_U32; + break; +@@ -1388,39 +1558,47 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + goto team_put; + } + +- opt_name = nla_data(mode_attrs[TEAM_ATTR_OPTION_NAME]); +- list_for_each_entry(option, &team->option_list, list) { +- long arg; ++ opt_name = nla_data(opt_attrs[TEAM_ATTR_OPTION_NAME]); ++ attr_port_ifindex = opt_attrs[TEAM_ATTR_OPTION_PORT_IFINDEX]; ++ if (attr_port_ifindex) ++ opt_port_ifindex = nla_get_u32(attr_port_ifindex); ++ ++ list_for_each_entry(opt_inst, &team->option_inst_list, list) { ++ struct team_option *option = opt_inst->option; + struct nlattr *opt_data_attr; +- struct team_option_binary tbinary; ++ struct team_gsetter_ctx ctx; + int data_len; ++ int tmp_ifindex; + ++ tmp_ifindex = opt_inst->port ? ++ opt_inst->port->dev->ifindex : 0; + if (option->type != opt_type || +- strcmp(option->name, opt_name)) ++ strcmp(option->name, opt_name) || ++ tmp_ifindex != opt_port_ifindex) + continue; + opt_found = true; +- opt_data_attr = mode_attrs[TEAM_ATTR_OPTION_DATA]; ++ opt_data_attr = opt_attrs[TEAM_ATTR_OPTION_DATA]; + data_len = nla_len(opt_data_attr); ++ ctx.port = opt_inst->port; + switch (opt_type) { + case TEAM_OPTION_TYPE_U32: +- arg = nla_get_u32(opt_data_attr); ++ ctx.data.u32_val = nla_get_u32(opt_data_attr); + break; + case TEAM_OPTION_TYPE_STRING: + if (data_len > TEAM_STRING_MAX_LEN) { + err = -EINVAL; + goto team_put; + } +- arg = (long) nla_data(opt_data_attr); ++ ctx.data.str_val = nla_data(opt_data_attr); + break; + case TEAM_OPTION_TYPE_BINARY: +- tbinary.data_len = data_len; +- tbinary.data = nla_data(opt_data_attr); +- arg = (long) &tbinary; ++ ctx.data.bin_val.len = data_len; ++ ctx.data.bin_val.ptr = nla_data(opt_data_attr); + break; + default: + BUG(); + } +- err = team_option_set(team, option, &arg); ++ err = team_option_set(team, opt_inst, &ctx); + if (err) + goto team_put; + } +diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c +index f4d960e..6cde1ab 100644 +--- a/drivers/net/team/team_mode_activebackup.c ++++ b/drivers/net/team/team_mode_activebackup.c +@@ -59,23 +59,21 @@ static void ab_port_leave(struct team *team, struct team_port *port) + RCU_INIT_POINTER(ab_priv(team)->active_port, NULL); + } + +-static int ab_active_port_get(struct team *team, void *arg) ++static int ab_active_port_get(struct team *team, struct team_gsetter_ctx *ctx) + { +- u32 *ifindex = arg; +- +- *ifindex = 0; + if (ab_priv(team)->active_port) +- *ifindex = ab_priv(team)->active_port->dev->ifindex; ++ ctx->data.u32_val = ab_priv(team)->active_port->dev->ifindex; ++ else ++ ctx->data.u32_val = 0; + return 0; + } + +-static int ab_active_port_set(struct team *team, void *arg) ++static int ab_active_port_set(struct team *team, struct team_gsetter_ctx *ctx) + { +- u32 *ifindex = arg; + struct team_port *port; + + list_for_each_entry_rcu(port, &team->port_list, list) { +- if (port->dev->ifindex == *ifindex) { ++ if (port->dev->ifindex == ctx->data.u32_val) { + rcu_assign_pointer(ab_priv(team)->active_port, port); + return 0; + } +diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c +index ed20f39..167cdb4 100644 +--- a/drivers/net/team/team_mode_loadbalance.c ++++ b/drivers/net/team/team_mode_loadbalance.c +@@ -52,22 +52,21 @@ drop: + return false; + } + +-static int lb_bpf_func_get(struct team *team, void *arg) ++static int lb_bpf_func_get(struct team *team, struct team_gsetter_ctx *ctx) + { +- struct team_option_binary *tbinary = team_optarg_tbinary(arg); +- +- memset(tbinary, 0, sizeof(*tbinary)); +- if (!lb_priv(team)->orig_fprog) ++ if (!lb_priv(team)->orig_fprog) { ++ ctx->data.bin_val.len = 0; ++ ctx->data.bin_val.ptr = NULL; + return 0; +- +- tbinary->data_len = lb_priv(team)->orig_fprog->len * +- sizeof(struct sock_filter); +- tbinary->data = lb_priv(team)->orig_fprog->filter; ++ } ++ ctx->data.bin_val.len = lb_priv(team)->orig_fprog->len * ++ sizeof(struct sock_filter); ++ ctx->data.bin_val.ptr = lb_priv(team)->orig_fprog->filter; + return 0; + } + + static int __fprog_create(struct sock_fprog **pfprog, u32 data_len, +- void *data) ++ const void *data) + { + struct sock_fprog *fprog; + struct sock_filter *filter = (struct sock_filter *) data; +@@ -93,16 +92,15 @@ static void __fprog_destroy(struct sock_fprog *fprog) + kfree(fprog); + } + +-static int lb_bpf_func_set(struct team *team, void *arg) ++static int lb_bpf_func_set(struct team *team, struct team_gsetter_ctx *ctx) + { +- struct team_option_binary *tbinary = team_optarg_tbinary(arg); + struct sk_filter *fp = NULL; + struct sock_fprog *fprog = NULL; + int err; + +- if (tbinary->data_len) { +- err = __fprog_create(&fprog, tbinary->data_len, +- tbinary->data); ++ if (ctx->data.bin_val.len) { ++ err = __fprog_create(&fprog, ctx->data.bin_val.len, ++ ctx->data.bin_val.ptr); + if (err) + return err; + err = sk_unattached_filter_create(&fp, fprog); +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index 41163ac..6f27c84 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -71,25 +71,27 @@ enum team_option_type { + TEAM_OPTION_TYPE_BINARY, + }; + ++struct team_gsetter_ctx { ++ union { ++ u32 u32_val; ++ const char *str_val; ++ struct { ++ const void *ptr; ++ u32 len; ++ } bin_val; ++ } data; ++ struct team_port *port; ++}; ++ + struct team_option { + struct list_head list; + const char *name; ++ bool per_port; + enum team_option_type type; +- int (*getter)(struct team *team, void *arg); +- int (*setter)(struct team *team, void *arg); +- +- /* Custom gennetlink interface related flags */ +- bool changed; +- bool removed; ++ int (*getter)(struct team *team, struct team_gsetter_ctx *ctx); ++ int (*setter)(struct team *team, struct team_gsetter_ctx *ctx); + }; + +-struct team_option_binary { +- u32 data_len; +- void *data; +-}; +- +-#define team_optarg_tbinary(arg) (*((struct team_option_binary **) arg)) +- + struct team_mode { + struct list_head list; + const char *kind; +@@ -118,6 +120,7 @@ struct team { + struct list_head port_list; + + struct list_head option_list; ++ struct list_head option_inst_list; /* list of option instances */ + + const struct team_mode *mode; + struct team_mode_ops ops; +@@ -224,6 +227,7 @@ enum { + TEAM_ATTR_OPTION_TYPE, /* u8 */ + TEAM_ATTR_OPTION_DATA, /* dynamic */ + TEAM_ATTR_OPTION_REMOVED, /* flag */ ++ TEAM_ATTR_OPTION_PORT_IFINDEX, /* u32 */ /* for per-port options */ + + __TEAM_ATTR_OPTION_MAX, + TEAM_ATTR_OPTION_MAX = __TEAM_ATTR_OPTION_MAX - 1, diff --git a/debian/patches/features/all/team/0017-team-add-bool-option-type.patch b/debian/patches/features/all/team/0017-team-add-bool-option-type.patch new file mode 100644 index 000000000..077e46621 --- /dev/null +++ b/debian/patches/features/all/team/0017-team-add-bool-option-type.patch @@ -0,0 +1,137 @@ +From: Jiri Pirko +Date: Tue, 10 Apr 2012 05:15:43 +0000 +Subject: [17/23] team: add bool option type + +commit 14f066bab19946545130a7379f420af860a02ae8 upstream. + +Add another (hopefully last) option type. Use NLA_FLAG to implement +that. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 40 +++++++++++++++++++++++++++++----------- + include/linux/if_team.h | 2 ++ + 2 files changed, 31 insertions(+), 11 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index eaf8441..2645fae 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -1463,6 +1463,16 @@ static int team_nl_fill_options_get(struct sk_buff *skb, + ctx.data.bin_val.len, ctx.data.bin_val.ptr)) + goto nla_put_failure; + break; ++ case TEAM_OPTION_TYPE_BOOL: ++ if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_FLAG)) ++ goto nla_put_failure; ++ err = team_option_get(team, opt_inst, &ctx); ++ if (err) ++ goto errout; ++ if (ctx.data.bool_val && ++ nla_put_flag(skb, TEAM_ATTR_OPTION_DATA)) ++ goto nla_put_failure; ++ break; + default: + BUG(); + } +@@ -1524,6 +1534,7 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + nla_for_each_nested(nl_option, info->attrs[TEAM_ATTR_LIST_OPTION], i) { + struct nlattr *opt_attrs[TEAM_ATTR_OPTION_MAX + 1]; + struct nlattr *attr_port_ifindex; ++ struct nlattr *attr_data; + enum team_option_type opt_type; + int opt_port_ifindex = 0; /* != 0 for per-port options */ + struct team_option_inst *opt_inst; +@@ -1539,8 +1550,7 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + if (err) + goto team_put; + if (!opt_attrs[TEAM_ATTR_OPTION_NAME] || +- !opt_attrs[TEAM_ATTR_OPTION_TYPE] || +- !opt_attrs[TEAM_ATTR_OPTION_DATA]) { ++ !opt_attrs[TEAM_ATTR_OPTION_TYPE]) { + err = -EINVAL; + goto team_put; + } +@@ -1554,10 +1564,19 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + case NLA_BINARY: + opt_type = TEAM_OPTION_TYPE_BINARY; + break; ++ case NLA_FLAG: ++ opt_type = TEAM_OPTION_TYPE_BOOL; ++ break; + default: + goto team_put; + } + ++ attr_data = opt_attrs[TEAM_ATTR_OPTION_DATA]; ++ if (opt_type != TEAM_OPTION_TYPE_BOOL && !attr_data) { ++ err = -EINVAL; ++ goto team_put; ++ } ++ + opt_name = nla_data(opt_attrs[TEAM_ATTR_OPTION_NAME]); + attr_port_ifindex = opt_attrs[TEAM_ATTR_OPTION_PORT_IFINDEX]; + if (attr_port_ifindex) +@@ -1565,9 +1584,7 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + + list_for_each_entry(opt_inst, &team->option_inst_list, list) { + struct team_option *option = opt_inst->option; +- struct nlattr *opt_data_attr; + struct team_gsetter_ctx ctx; +- int data_len; + int tmp_ifindex; + + tmp_ifindex = opt_inst->port ? +@@ -1577,23 +1594,24 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) + tmp_ifindex != opt_port_ifindex) + continue; + opt_found = true; +- opt_data_attr = opt_attrs[TEAM_ATTR_OPTION_DATA]; +- data_len = nla_len(opt_data_attr); + ctx.port = opt_inst->port; + switch (opt_type) { + case TEAM_OPTION_TYPE_U32: +- ctx.data.u32_val = nla_get_u32(opt_data_attr); ++ ctx.data.u32_val = nla_get_u32(attr_data); + break; + case TEAM_OPTION_TYPE_STRING: +- if (data_len > TEAM_STRING_MAX_LEN) { ++ if (nla_len(attr_data) > TEAM_STRING_MAX_LEN) { + err = -EINVAL; + goto team_put; + } +- ctx.data.str_val = nla_data(opt_data_attr); ++ ctx.data.str_val = nla_data(attr_data); + break; + case TEAM_OPTION_TYPE_BINARY: +- ctx.data.bin_val.len = data_len; +- ctx.data.bin_val.ptr = nla_data(opt_data_attr); ++ ctx.data.bin_val.len = nla_len(attr_data); ++ ctx.data.bin_val.ptr = nla_data(attr_data); ++ break; ++ case TEAM_OPTION_TYPE_BOOL: ++ ctx.data.bool_val = attr_data ? true : false; + break; + default: + BUG(); +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index 6f27c84..78c84fd 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -69,6 +69,7 @@ enum team_option_type { + TEAM_OPTION_TYPE_U32, + TEAM_OPTION_TYPE_STRING, + TEAM_OPTION_TYPE_BINARY, ++ TEAM_OPTION_TYPE_BOOL, + }; + + struct team_gsetter_ctx { +@@ -79,6 +80,7 @@ struct team_gsetter_ctx { + const void *ptr; + u32 len; + } bin_val; ++ bool bool_val; + } data; + struct team_port *port; + }; diff --git a/debian/patches/features/all/team/0018-team-add-user_linkup-and-user_linkup_enabled-per-por.patch b/debian/patches/features/all/team/0018-team-add-user_linkup-and-user_linkup_enabled-per-por.patch new file mode 100644 index 000000000..1dadd07c6 --- /dev/null +++ b/debian/patches/features/all/team/0018-team-add-user_linkup-and-user_linkup_enabled-per-por.patch @@ -0,0 +1,184 @@ +From: Jiri Pirko +Date: Tue, 10 Apr 2012 05:15:44 +0000 +Subject: [18/23] team: add user_linkup and user_linkup_enabled per-port + option + +commit 71472ec12c61dd305ab4d11822af7ecc4f9717f9 upstream. + +Allows userspace to setup linkup for ports. Default is to take linkup +directly from ethtool state. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 72 +++++++++++++++++++++++++++++++++++++++++------ + include/linux/if_team.h | 26 +++++++++++------ + 2 files changed, 81 insertions(+), 17 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 2645fae..e639abe 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -76,6 +76,11 @@ int team_port_set_team_mac(struct team_port *port) + } + EXPORT_SYMBOL(team_port_set_team_mac); + ++static void team_refresh_port_linkup(struct team_port *port) ++{ ++ port->linkup = port->user.linkup_enabled ? port->user.linkup : ++ port->state.linkup; ++} + + /******************* + * Options handling +@@ -880,6 +885,40 @@ static int team_mode_option_set(struct team *team, struct team_gsetter_ctx *ctx) + return team_change_mode(team, ctx->data.str_val); + } + ++static int team_user_linkup_option_get(struct team *team, ++ struct team_gsetter_ctx *ctx) ++{ ++ ctx->data.bool_val = ctx->port->user.linkup; ++ return 0; ++} ++ ++static int team_user_linkup_option_set(struct team *team, ++ struct team_gsetter_ctx *ctx) ++{ ++ ctx->port->user.linkup = ctx->data.bool_val; ++ team_refresh_port_linkup(ctx->port); ++ return 0; ++} ++ ++static int team_user_linkup_en_option_get(struct team *team, ++ struct team_gsetter_ctx *ctx) ++{ ++ struct team_port *port = ctx->port; ++ ++ ctx->data.bool_val = port->user.linkup_enabled; ++ return 0; ++} ++ ++static int team_user_linkup_en_option_set(struct team *team, ++ struct team_gsetter_ctx *ctx) ++{ ++ struct team_port *port = ctx->port; ++ ++ port->user.linkup_enabled = ctx->data.bool_val; ++ team_refresh_port_linkup(ctx->port); ++ return 0; ++} ++ + static const struct team_option team_options[] = { + { + .name = "mode", +@@ -887,6 +926,20 @@ static const struct team_option team_options[] = { + .getter = team_mode_option_get, + .setter = team_mode_option_set, + }, ++ { ++ .name = "user_linkup", ++ .type = TEAM_OPTION_TYPE_BOOL, ++ .per_port = true, ++ .getter = team_user_linkup_option_get, ++ .setter = team_user_linkup_option_set, ++ }, ++ { ++ .name = "user_linkup_enabled", ++ .type = TEAM_OPTION_TYPE_BOOL, ++ .per_port = true, ++ .getter = team_user_linkup_en_option_get, ++ .setter = team_user_linkup_en_option_set, ++ }, + }; + + static int team_init(struct net_device *dev) +@@ -1670,10 +1723,10 @@ static int team_nl_fill_port_list_get(struct sk_buff *skb, + } + if ((port->removed && + nla_put_flag(skb, TEAM_ATTR_PORT_REMOVED)) || +- (port->linkup && ++ (port->state.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_put_u32(skb, TEAM_ATTR_PORT_SPEED, port->state.speed) || ++ nla_put_u8(skb, TEAM_ATTR_PORT_DUPLEX, port->state.duplex)) + goto nla_put_failure; + nla_nest_end(skb, port_item); + } +@@ -1833,23 +1886,24 @@ static void __team_port_change_check(struct team_port *port, bool linkup) + { + int err; + +- if (!port->removed && port->linkup == linkup) ++ if (!port->removed && port->state.linkup == linkup) + return; + + port->changed = true; +- port->linkup = linkup; ++ port->state.linkup = linkup; ++ team_refresh_port_linkup(port); + 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; ++ port->state.speed = ethtool_cmd_speed(&ecmd); ++ port->state.duplex = ecmd.duplex; + goto send_event; + } + } +- port->speed = 0; +- port->duplex = 0; ++ port->state.speed = 0; ++ port->state.duplex = 0; + + send_event: + err = team_nl_send_event_port_list_get(port->team); +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index 78c84fd..5fd5ab1 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -33,6 +33,24 @@ struct team_port { + struct team *team; + int index; + ++ bool linkup; /* either state.linkup or user.linkup */ ++ ++ struct { ++ bool linkup; ++ u32 speed; ++ u8 duplex; ++ } state; ++ ++ /* Values set by userspace */ ++ struct { ++ bool linkup; ++ bool linkup_enabled; ++ } user; ++ ++ /* Custom gennetlink interface related flags */ ++ bool changed; ++ bool removed; ++ + /* + * A place for storing original values of the device before it + * become a port. +@@ -42,14 +60,6 @@ struct team_port { + unsigned int mtu; + } orig; + +- bool linkup; +- u32 speed; +- u8 duplex; +- +- /* Custom gennetlink interface related flags */ +- bool changed; +- bool removed; +- + struct rcu_head rcu; + }; + diff --git a/debian/patches/features/all/team/0019-team-ab-walk-through-port-list-non-rcu.patch b/debian/patches/features/all/team/0019-team-ab-walk-through-port-list-non-rcu.patch new file mode 100644 index 000000000..13fde09c1 --- /dev/null +++ b/debian/patches/features/all/team/0019-team-ab-walk-through-port-list-non-rcu.patch @@ -0,0 +1,27 @@ +From: Jiri Pirko +Date: Tue, 10 Apr 2012 05:15:45 +0000 +Subject: [19/23] team: ab: walk through port list non-rcu + +commit 679b16073008cc536e85e2773e67234b596fb62e upstream. + +Since team->lock is being held, _rcu variant make no sense. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team_mode_activebackup.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c +index 6cde1ab..a715c40 100644 +--- a/drivers/net/team/team_mode_activebackup.c ++++ b/drivers/net/team/team_mode_activebackup.c +@@ -72,7 +72,7 @@ static int ab_active_port_set(struct team *team, struct team_gsetter_ctx *ctx) + { + struct team_port *port; + +- list_for_each_entry_rcu(port, &team->port_list, list) { ++ list_for_each_entry(port, &team->port_list, list) { + if (port->dev->ifindex == ctx->data.u32_val) { + rcu_assign_pointer(ab_priv(team)->active_port, port); + return 0; diff --git a/debian/patches/features/all/team/0020-team-add-missed-statics.patch b/debian/patches/features/all/team/0020-team-add-missed-statics.patch new file mode 100644 index 000000000..da5f23a29 --- /dev/null +++ b/debian/patches/features/all/team/0020-team-add-missed-statics.patch @@ -0,0 +1,66 @@ +From: Jiri Pirko +Date: Tue, 10 Apr 2012 05:15:46 +0000 +Subject: [20/23] team: add missed "statics" + +commit cade455596504fae8e134a27189713ddf7c6d04d upstream. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 2 +- + drivers/net/team/team_mode_activebackup.c | 4 ++-- + drivers/net/team/team_mode_loadbalance.c | 4 ++-- + 3 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index e639abe..153a62d 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -65,7 +65,7 @@ static int __set_port_mac(struct net_device *port_dev, + return dev_set_mac_address(port_dev, &addr); + } + +-int team_port_set_orig_mac(struct team_port *port) ++static int team_port_set_orig_mac(struct team_port *port) + { + return __set_port_mac(port->dev, port->orig.dev_addr); + } +diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c +index a715c40..fd6bd03 100644 +--- a/drivers/net/team/team_mode_activebackup.c ++++ b/drivers/net/team/team_mode_activebackup.c +@@ -90,12 +90,12 @@ static const struct team_option ab_options[] = { + }, + }; + +-int ab_init(struct team *team) ++static int ab_init(struct team *team) + { + return team_options_register(team, ab_options, ARRAY_SIZE(ab_options)); + } + +-void ab_exit(struct team *team) ++static void ab_exit(struct team *team) + { + team_options_unregister(team, ab_options, ARRAY_SIZE(ab_options)); + } +diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c +index 167cdb4..2b506b2 100644 +--- a/drivers/net/team/team_mode_loadbalance.c ++++ b/drivers/net/team/team_mode_loadbalance.c +@@ -130,13 +130,13 @@ static const struct team_option lb_options[] = { + }, + }; + +-int lb_init(struct team *team) ++static int lb_init(struct team *team) + { + return team_options_register(team, lb_options, + ARRAY_SIZE(lb_options)); + } + +-void lb_exit(struct team *team) ++static void lb_exit(struct team *team) + { + team_options_unregister(team, lb_options, + ARRAY_SIZE(lb_options)); diff --git a/debian/patches/features/all/team/0021-team-lb-let-userspace-care-about-port-macs.patch b/debian/patches/features/all/team/0021-team-lb-let-userspace-care-about-port-macs.patch new file mode 100644 index 000000000..e90a68c34 --- /dev/null +++ b/debian/patches/features/all/team/0021-team-lb-let-userspace-care-about-port-macs.patch @@ -0,0 +1,41 @@ +From: Jiri Pirko +Date: Fri, 20 Apr 2012 04:42:04 +0000 +Subject: [21/23] team: lb: let userspace care about port macs + +commit 4c78bb845bd2aaf1f7136e75314c7d034cfd120f upstream. + +Better to leave this for userspace + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team_mode_loadbalance.c | 12 ------------ + 1 file changed, 12 deletions(-) + +diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c +index 2b506b2..438d5b8 100644 +--- a/drivers/net/team/team_mode_loadbalance.c ++++ b/drivers/net/team/team_mode_loadbalance.c +@@ -142,22 +142,10 @@ static void lb_exit(struct team *team) + ARRAY_SIZE(lb_options)); + } + +-static int lb_port_enter(struct team *team, struct team_port *port) +-{ +- return team_port_set_team_mac(port); +-} +- +-static void lb_port_change_mac(struct team *team, struct team_port *port) +-{ +- team_port_set_team_mac(port); +-} +- + static const struct team_mode_ops lb_mode_ops = { + .init = lb_init, + .exit = lb_exit, + .transmit = lb_transmit, +- .port_enter = lb_port_enter, +- .port_change_mac = lb_port_change_mac, + }; + + static struct team_mode lb_mode = { diff --git a/debian/patches/features/all/team/0022-team-allow-to-enable-disable-ports.patch b/debian/patches/features/all/team/0022-team-allow-to-enable-disable-ports.patch new file mode 100644 index 000000000..8df50f7d6 --- /dev/null +++ b/debian/patches/features/all/team/0022-team-allow-to-enable-disable-ports.patch @@ -0,0 +1,212 @@ +From: Jiri Pirko +Date: Fri, 20 Apr 2012 04:42:05 +0000 +Subject: [22/23] team: allow to enable/disable ports + +commit 19a0b58e506b06fd41659d8734bba6a3e87980f4 upstream. + +This patch changes content of hashlist (used to get port struct by +computed index (0...en_port_count-1)). Now the hash list contains only +enabled ports so userspace will be able to say what ports can be used +for tx/rx. This becomes handy when userspace will need to disable ports +which does not belong to active aggregator. By default, newly added port +is enabled. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 51 ++++++++++++++++++++---------- + drivers/net/team/team_mode_loadbalance.c | 2 +- + drivers/net/team/team_mode_roundrobin.c | 2 +- + include/linux/if_team.h | 15 +++++---- + 4 files changed, 45 insertions(+), 25 deletions(-) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 153a62d..fe7ca40 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -559,6 +559,8 @@ static int team_change_mode(struct team *team, const char *kind) + * Rx path frame handler + ************************/ + ++static bool team_port_enabled(struct team_port *port); ++ + /* note: already called with rcu_read_lock */ + static rx_handler_result_t team_handle_frame(struct sk_buff **pskb) + { +@@ -575,8 +577,12 @@ static rx_handler_result_t team_handle_frame(struct sk_buff **pskb) + + port = team_port_get_rcu(skb->dev); + team = port->team; +- +- res = team->ops.receive(team, port, skb); ++ if (!team_port_enabled(port)) { ++ /* allow exact match delivery for disabled ports */ ++ res = RX_HANDLER_EXACT; ++ } else { ++ res = team->ops.receive(team, port, skb); ++ } + if (res == RX_HANDLER_ANOTHER) { + struct team_pcpu_stats *pcpu_stats; + +@@ -612,17 +618,25 @@ static bool team_port_find(const struct team *team, + return false; + } + ++static bool team_port_enabled(struct team_port *port) ++{ ++ return port->index != -1; ++} ++ + /* +- * Add/delete port to the team port list. Write guarded by rtnl_lock. +- * Takes care of correct port->index setup (might be racy). ++ * Enable/disable port by adding to enabled port hashlist and setting ++ * port->index (Might be racy so reader could see incorrect ifindex when ++ * processing a flying packet, but that is not a problem). Write guarded ++ * by team->lock. + */ +-static void team_port_list_add_port(struct team *team, +- struct team_port *port) ++static void team_port_enable(struct team *team, ++ struct team_port *port) + { +- port->index = team->port_count++; ++ if (team_port_enabled(port)) ++ return; ++ port->index = team->en_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) +@@ -630,7 +644,7 @@ 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++) { ++ for (i = rm_index + 1; i < team->en_port_count; i++) { + port = team_get_port_by_index(team, i); + hlist_del_rcu(&port->hlist); + port->index--; +@@ -639,15 +653,17 @@ static void __reconstruct_port_hlist(struct team *team, int rm_index) + } + } + +-static void team_port_list_del_port(struct team *team, +- struct team_port *port) ++static void team_port_disable(struct team *team, ++ struct team_port *port) + { + int rm_index = port->index; + ++ if (!team_port_enabled(port)) ++ return; + hlist_del_rcu(&port->hlist); +- list_del_rcu(&port->list); + __reconstruct_port_hlist(team, rm_index); +- team->port_count--; ++ team->en_port_count--; ++ port->index = -1; + } + + #define TEAM_VLAN_FEATURES (NETIF_F_ALL_CSUM | NETIF_F_SG | \ +@@ -800,7 +816,9 @@ static int team_port_add(struct team *team, struct net_device *port_dev) + goto err_option_port_add; + } + +- team_port_list_add_port(team, port); ++ port->index = -1; ++ team_port_enable(team, port); ++ list_add_tail_rcu(&port->list, &team->port_list); + team_adjust_ops(team); + __team_compute_features(team); + __team_port_change_check(port, !!netif_carrier_ok(port_dev)); +@@ -849,7 +867,8 @@ static int team_port_del(struct team *team, struct net_device *port_dev) + + port->removed = true; + __team_port_change_check(port, false); +- team_port_list_del_port(team, port); ++ team_port_disable(team, port); ++ list_del_rcu(&port->list); + team_adjust_ops(team); + team_option_port_del(team, port); + netdev_rx_handler_unregister(port_dev); +@@ -956,7 +975,7 @@ static int team_init(struct net_device *dev) + return -ENOMEM; + + for (i = 0; i < TEAM_PORT_HASHENTRIES; i++) +- INIT_HLIST_HEAD(&team->port_hlist[i]); ++ INIT_HLIST_HEAD(&team->en_port_hlist[i]); + INIT_LIST_HEAD(&team->port_list); + + team_adjust_ops(team); +diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c +index 438d5b8..86e8183 100644 +--- a/drivers/net/team/team_mode_loadbalance.c ++++ b/drivers/net/team/team_mode_loadbalance.c +@@ -38,7 +38,7 @@ static bool lb_transmit(struct team *team, struct sk_buff *skb) + if (unlikely(!fp)) + goto drop; + hash = SK_RUN_FILTER(fp, skb); +- port_index = hash % team->port_count; ++ port_index = hash % team->en_port_count; + port = team_get_port_by_index_rcu(team, port_index); + if (unlikely(!port)) + goto drop; +diff --git a/drivers/net/team/team_mode_roundrobin.c b/drivers/net/team/team_mode_roundrobin.c +index a0e8f80..6abfbdc 100644 +--- a/drivers/net/team/team_mode_roundrobin.c ++++ b/drivers/net/team/team_mode_roundrobin.c +@@ -50,7 +50,7 @@ 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_index = rr_priv(team)->sent_packets++ % team->en_port_count; + port = team_get_port_by_index_rcu(team, port_index); + port = __get_first_port_up(team, port); + if (unlikely(!port)) +diff --git a/include/linux/if_team.h b/include/linux/if_team.h +index 5fd5ab1..8185f57 100644 +--- a/include/linux/if_team.h ++++ b/include/linux/if_team.h +@@ -28,10 +28,10 @@ struct team; + + struct team_port { + struct net_device *dev; +- struct hlist_node hlist; /* node in hash list */ ++ struct hlist_node hlist; /* node in enabled ports hash list */ + struct list_head list; /* node in ordinary list */ + struct team *team; +- int index; ++ int index; /* index of enabled port. If disabled, it's set to -1 */ + + bool linkup; /* either state.linkup or user.linkup */ + +@@ -125,11 +125,12 @@ struct team { + struct mutex lock; /* used for overall locking, e.g. port lists write */ + + /* +- * port lists with port count ++ * List of enabled ports and their count + */ +- int port_count; +- struct hlist_head port_hlist[TEAM_PORT_HASHENTRIES]; +- struct list_head port_list; ++ int en_port_count; ++ struct hlist_head en_port_hlist[TEAM_PORT_HASHENTRIES]; ++ ++ struct list_head port_list; /* list of all ports */ + + struct list_head option_list; + struct list_head option_inst_list; /* list of option instances */ +@@ -142,7 +143,7 @@ struct team { + 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)]; ++ return &team->en_port_hlist[port_index & (TEAM_PORT_HASHENTRIES - 1)]; + } + + static inline struct team_port *team_get_port_by_index(struct team *team, diff --git a/debian/patches/features/all/team/0023-team-add-per-port-option-for-enabling-disabling-port.patch b/debian/patches/features/all/team/0023-team-add-per-port-option-for-enabling-disabling-port.patch new file mode 100644 index 000000000..09ef3e098 --- /dev/null +++ b/debian/patches/features/all/team/0023-team-add-per-port-option-for-enabling-disabling-port.patch @@ -0,0 +1,54 @@ +From: Jiri Pirko +Date: Fri, 20 Apr 2012 04:42:06 +0000 +Subject: [23/23] team: add per-port option for enabling/disabling ports + +commit acd69962341a956b5bcc5b4178b70fa527d7ce11 upstream. + +Signed-off-by: Jiri Pirko +Signed-off-by: David S. Miller +--- + drivers/net/team/team.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index fe7ca40..c61ae35 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -904,6 +904,23 @@ static int team_mode_option_set(struct team *team, struct team_gsetter_ctx *ctx) + return team_change_mode(team, ctx->data.str_val); + } + ++static int team_port_en_option_get(struct team *team, ++ struct team_gsetter_ctx *ctx) ++{ ++ ctx->data.bool_val = team_port_enabled(ctx->port); ++ return 0; ++} ++ ++static int team_port_en_option_set(struct team *team, ++ struct team_gsetter_ctx *ctx) ++{ ++ if (ctx->data.bool_val) ++ team_port_enable(team, ctx->port); ++ else ++ team_port_disable(team, ctx->port); ++ return 0; ++} ++ + static int team_user_linkup_option_get(struct team *team, + struct team_gsetter_ctx *ctx) + { +@@ -946,6 +963,13 @@ static const struct team_option team_options[] = { + .setter = team_mode_option_set, + }, + { ++ .name = "enabled", ++ .type = TEAM_OPTION_TYPE_BOOL, ++ .per_port = true, ++ .getter = team_port_en_option_get, ++ .setter = team_port_en_option_set, ++ }, ++ { + .name = "user_linkup", + .type = TEAM_OPTION_TYPE_BOOL, + .per_port = true, diff --git a/debian/patches/series-all b/debian/patches/series-all index a57a4d24c..658496c17 100644 --- a/debian/patches/series-all +++ b/debian/patches/series-all @@ -309,3 +309,31 @@ bugfix/all/mm-fix-vma_resv_map-null-pointer.patch bugfix/all/hugepages-fix-use-after-free-bug-in-quota-handling.patch bugfix/all/fix-scsi_wait_scan.patch + +features/all/define-netdev_features_t.patch +features/all/filter-Allow-to-create-sk-unattached-filters.patch + +# team driver from 3.5ish +features/all/team/0001-net-introduce-ethernet-teaming-device.patch +features/all/team/0002-team-Do-not-hold-rcu_read_lock-when-running-netlink-.patch +features/all/team/0003-team-convert-overall-spinlock-to-mutex.patch +features/all/team/0004-team-replicate-options-on-register.patch +features/all/team/0005-team-add-fix_features.patch +features/all/team/0006-team-avoid-using-variable-length-array.patch +features/all/team/0007-team-replace-kmalloc-memcpy-by-kmemdup.patch +features/all/team/0008-net-treewide-use-of-RCU_INIT_POINTER.patch +features/all/team/0009-net-introduce-vlan_vid_-add-del-and-use-them-instead.patch +features/all/team/0010-vlan-introduce-functions-to-do-mass-addition-deletio.patch +features/all/team/0011-team-use-vlan_vids_-addr-del-_by_dev.patch +features/all/team/0012-team-send-only-changed-options-ports-via-netlink.patch +features/all/team/0013-team-Stop-using-NLA_PUT.patch +features/all/team/0014-team-add-binary-option-type.patch +features/all/team/0015-team-add-loadbalance-mode.patch +features/all/team/0016-team-add-support-for-per-port-options.patch +features/all/team/0017-team-add-bool-option-type.patch +features/all/team/0018-team-add-user_linkup-and-user_linkup_enabled-per-por.patch +features/all/team/0019-team-ab-walk-through-port-list-non-rcu.patch +features/all/team/0020-team-add-missed-statics.patch +features/all/team/0021-team-lb-let-userspace-care-about-port-macs.patch +features/all/team/0022-team-allow-to-enable-disable-ports.patch +features/all/team/0023-team-add-per-port-option-for-enabling-disabling-port.patch