diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch')
-rw-r--r-- | meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch | 1425 |
1 files changed, 1425 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch new file mode 100644 index 000000000..83717369c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch @@ -0,0 +1,1425 @@ +From 58c3299017c5e6022fb2a2a74b662b2a4c0306f5 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Tue, 20 Nov 2018 10:14:47 -0800 +Subject: [PATCH] net/ncsi: backport ncsi patches + +net/ncsi: Allow enabling multiple packages & channels + +This series extends the NCSI driver to configure multiple packages +and/or channels simultaneously. Since the RFC series this includes a few +extra changes to fix areas in the driver that either made this harder or +were roadblocks due to deviations from the NCSI specification. + +Patches 1 & 2 fix two issues where the driver made assumptions about the +capabilities of the NCSI topology. +Patches 3 & 4 change some internal semantics slightly to make multi-mode +easier. +Patch 5 introduces a cleaner way of reconfiguring the NCSI configuration +and keeping track of channel states. +Patch 6 implements the main multi-package/multi-channel configuration, +configured via the Netlink interface. + +Readers who have an interesting NCSI setup - especially multi-package +with HWA - please test! I think I've covered all permutations but I +don't have infinite hardware to test on. + +net/ncsi: Don't enable all channels when HWA available + +NCSI hardware arbitration allows multiple packages to be enabled at once +and share the same wiring. If the NCSI driver recognises that HWA is +available it unconditionally enables all packages and channels; but that +is a configuration decision rather than something required by HWA. +Additionally the current implementation will not failover on link events +which can cause connectivity to be lost unless the interface is manually +bounced. + +Retain basic HWA support but remove the separate configuration path to +enable all channels, leaving this to be handled by a later +implementation. + +net/ncsi: Probe single packages to avoid conflict + +Currently the NCSI driver sends a select-package command to all possible +packages simultaneously to discover what packages are available. However +at this stage in the probe process the driver does not know if +hardware arbitration is available: if it isn't then this process could +cause collisions on the RMII bus when packages try to respond. + +Update the probe loop to probe each package one by one, and once +complete check if HWA is universally supported. + +net/ncsi: Don't deselect package in suspend if active + +When a package is deselected all channels of that package cease +communication. If there are other channels active on the package of the +suspended channel this will disable them as well, so only send a +deselect-package command if no other channels are active. + +net/ncsi: Don't mark configured channels inactive + +The concepts of a channel being 'active' and it having link are slightly +muddled in the NCSI driver. Tweak this slightly so that +NCSI_CHANNEL_ACTIVE represents a channel that has been configured and +enabled, and NCSI_CHANNEL_INACTIVE represents a de-configured channel. +This distinction is important because a channel can be 'active' but have +its link down; in this case the channel may still need to be configured +so that it may receive AEN link-state-change packets. + +net/ncsi: Reset channel state in ncsi_start_dev() + +When the NCSI driver is stopped with ncsi_stop_dev() the channel +monitors are stopped and the state set to "inactive". However the +channels are still configured and active from the perspective of the +network controller. We should suspend each active channel but in the +context of ncsi_stop_dev() the transmit queue has been or is about to be +stopped so we won't have time to do so. + +Instead when ncsi_start_dev() is called if the NCSI topology has already +been probed then call ncsi_reset_dev() to suspend any channels that were +previously active. This resets the network controller to a known state, +provides an up to date view of channel link state, and makes sure that +mode flags such as NCSI_MODE_TX_ENABLE are properly reset. + +In addition to ncsi_start_dev() use ncsi_reset_dev() in ncsi-netlink.c +to update the channel configuration more cleanly. + +net/ncsi: Configure multi-package, multi-channel modes with failover + +This patch extends the ncsi-netlink interface with two new commands and +three new attributes to configure multiple packages and/or channels at +once, and configure specific failover modes. + +NCSI_CMD_SET_PACKAGE mask and NCSI_CMD_SET_CHANNEL_MASK set a whitelist +of packages or channels allowed to be configured with the +NCSI_ATTR_PACKAGE_MASK and NCSI_ATTR_CHANNEL_MASK attributes +respectively. If one of these whitelists is set only packages or +channels matching the whitelist are considered for the channel queue in +ncsi_choose_active_channel(). + +These commands may also use the NCSI_ATTR_MULTI_FLAG to signal that +multiple packages or channels may be configured simultaneously. NCSI +hardware arbitration (HWA) must be available in order to enable +multi-package mode. Multi-channel mode is always available. + +If the NCSI_ATTR_CHANNEL_ID attribute is present in the +NCSI_CMD_SET_CHANNEL_MASK command the it sets the preferred channel as +with the NCSI_CMD_SET_INTERFACE command. The combination of preferred +channel and channel whitelist defines a primary channel and the allowed +failover channels. +If the NCSI_ATTR_MULTI_FLAG attribute is also present then the preferred +channel is configured for Tx/Rx and the other channels are enabled only +for Rx. + +Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com> +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + include/uapi/linux/ncsi.h | 15 ++ + net/ncsi/internal.h | 19 +- + net/ncsi/ncsi-aen.c | 75 +++++-- + net/ncsi/ncsi-manage.c | 522 ++++++++++++++++++++++++++++++++-------------- + net/ncsi/ncsi-netlink.c | 233 ++++++++++++++++++--- + net/ncsi/ncsi-rsp.c | 2 +- + 6 files changed, 660 insertions(+), 206 deletions(-) + +diff --git a/include/uapi/linux/ncsi.h b/include/uapi/linux/ncsi.h +index 0a26a5576645..a3f87c54fdb3 100644 +--- a/include/uapi/linux/ncsi.h ++++ b/include/uapi/linux/ncsi.h +@@ -26,6 +26,12 @@ + * @NCSI_CMD_SEND_CMD: send NC-SI command to network card. + * Requires NCSI_ATTR_IFINDEX, NCSI_ATTR_PACKAGE_ID + * and NCSI_ATTR_CHANNEL_ID. ++ * @NCSI_CMD_SET_PACKAGE_MASK: set a whitelist of allowed packages. ++ * Requires NCSI_ATTR_IFINDEX and NCSI_ATTR_PACKAGE_MASK. ++ * @NCSI_CMD_SET_CHANNEL_MASK: set a whitelist of allowed channels. ++ * Requires NCSI_ATTR_IFINDEX, NCSI_ATTR_PACKAGE_ID, and ++ * NCSI_ATTR_CHANNEL_MASK. If NCSI_ATTR_CHANNEL_ID is present it sets ++ * the primary channel. + * @NCSI_CMD_MAX: highest command number + */ + enum ncsi_nl_commands { +@@ -34,6 +40,8 @@ enum ncsi_nl_commands { + NCSI_CMD_SET_INTERFACE, + NCSI_CMD_CLEAR_INTERFACE, + NCSI_CMD_SEND_CMD, ++ NCSI_CMD_SET_PACKAGE_MASK, ++ NCSI_CMD_SET_CHANNEL_MASK, + + __NCSI_CMD_AFTER_LAST, + NCSI_CMD_MAX = __NCSI_CMD_AFTER_LAST - 1 +@@ -48,6 +56,10 @@ enum ncsi_nl_commands { + * @NCSI_ATTR_PACKAGE_ID: package ID + * @NCSI_ATTR_CHANNEL_ID: channel ID + * @NCSI_ATTR_DATA: command payload ++ * @NCSI_ATTR_MULTI_FLAG: flag to signal that multi-mode should be enabled with ++ * NCSI_CMD_SET_PACKAGE_MASK or NCSI_CMD_SET_CHANNEL_MASK. ++ * @NCSI_ATTR_PACKAGE_MASK: 32-bit mask of allowed packages. ++ * @NCSI_ATTR_CHANNEL_MASK: 32-bit mask of allowed channels. + * @NCSI_ATTR_MAX: highest attribute number + */ + enum ncsi_nl_attrs { +@@ -57,6 +69,9 @@ enum ncsi_nl_attrs { + NCSI_ATTR_PACKAGE_ID, + NCSI_ATTR_CHANNEL_ID, + NCSI_ATTR_DATA, ++ NCSI_ATTR_MULTI_FLAG, ++ NCSI_ATTR_PACKAGE_MASK, ++ NCSI_ATTR_CHANNEL_MASK, + + __NCSI_ATTR_AFTER_LAST, + NCSI_ATTR_MAX = __NCSI_ATTR_AFTER_LAST - 1 +diff --git a/net/ncsi/internal.h b/net/ncsi/internal.h +index 1dae77c54009..9e3642b802c4 100644 +--- a/net/ncsi/internal.h ++++ b/net/ncsi/internal.h +@@ -222,6 +222,10 @@ struct ncsi_package { + unsigned int channel_num; /* Number of channels */ + struct list_head channels; /* List of chanels */ + struct list_head node; /* Form list of packages */ ++ ++ bool multi_channel; /* Enable multiple channels */ ++ u32 channel_whitelist; /* Channels to configure */ ++ struct ncsi_channel *preferred_channel; /* Primary channel */ + }; + + struct ncsi_request { +@@ -287,16 +291,16 @@ struct ncsi_dev_priv { + #define NCSI_DEV_PROBED 1 /* Finalized NCSI topology */ + #define NCSI_DEV_HWA 2 /* Enabled HW arbitration */ + #define NCSI_DEV_RESHUFFLE 4 ++#define NCSI_DEV_RESET 8 /* Reset state of NC */ + unsigned int gma_flag; /* OEM GMA flag */ + spinlock_t lock; /* Protect the NCSI device */ + #if IS_ENABLED(CONFIG_IPV6) + unsigned int inet6_addr_num; /* Number of IPv6 addresses */ + #endif ++ unsigned int package_probe_id;/* Current ID during probe */ + unsigned int package_num; /* Number of packages */ + struct list_head packages; /* List of packages */ + struct ncsi_channel *hot_channel; /* Channel was ever active */ +- struct ncsi_package *force_package; /* Force a specific package */ +- struct ncsi_channel *force_channel; /* Force a specific channel */ + struct ncsi_request requests[256]; /* Request table */ + unsigned int request_id; /* Last used request ID */ + #define NCSI_REQ_START_IDX 1 +@@ -309,6 +313,9 @@ struct ncsi_dev_priv { + struct list_head node; /* Form NCSI device list */ + #define NCSI_MAX_VLAN_VIDS 15 + struct list_head vlan_vids; /* List of active VLAN IDs */ ++ ++ bool multi_package; /* Enable multiple packages */ ++ u32 package_whitelist; /* Packages to configure */ + }; + + struct ncsi_cmd_arg { +@@ -341,6 +348,7 @@ extern spinlock_t ncsi_dev_lock; + list_for_each_entry_rcu(nc, &np->channels, node) + + /* Resources */ ++int ncsi_reset_dev(struct ncsi_dev *nd); + void ncsi_start_channel_monitor(struct ncsi_channel *nc); + void ncsi_stop_channel_monitor(struct ncsi_channel *nc); + struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np, +@@ -361,6 +369,13 @@ struct ncsi_request *ncsi_alloc_request(struct ncsi_dev_priv *ndp, + void ncsi_free_request(struct ncsi_request *nr); + struct ncsi_dev *ncsi_find_dev(struct net_device *dev); + int ncsi_process_next_channel(struct ncsi_dev_priv *ndp); ++bool ncsi_channel_has_link(struct ncsi_channel *channel); ++bool ncsi_channel_is_last(struct ncsi_dev_priv *ndp, ++ struct ncsi_channel *channel); ++int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, ++ struct ncsi_package *np, ++ struct ncsi_channel *disable, ++ struct ncsi_channel *enable); + + /* Packet handlers */ + u32 ncsi_calculate_checksum(unsigned char *data, int len); +diff --git a/net/ncsi/ncsi-aen.c b/net/ncsi/ncsi-aen.c +index 25e483e8278b..26d67e27551f 100644 +--- a/net/ncsi/ncsi-aen.c ++++ b/net/ncsi/ncsi-aen.c +@@ -50,13 +50,15 @@ static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h, + static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, + struct ncsi_aen_pkt_hdr *h) + { +- struct ncsi_aen_lsc_pkt *lsc; +- struct ncsi_channel *nc; ++ struct ncsi_channel *nc, *tmp; + struct ncsi_channel_mode *ncm; +- bool chained; +- int state; + unsigned long old_data, data; ++ struct ncsi_aen_lsc_pkt *lsc; ++ struct ncsi_package *np; ++ bool had_link, has_link; + unsigned long flags; ++ bool chained; ++ int state; + + /* Find the NCSI channel */ + ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc); +@@ -73,6 +75,9 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, + ncm->data[2] = data; + ncm->data[4] = ntohl(lsc->oem_status); + ++ had_link = !!(old_data & 0x1); ++ has_link = !!(data & 0x1); ++ + netdev_dbg(ndp->ndev.dev, "NCSI: LSC AEN - channel %u state %s\n", + nc->id, data & 0x1 ? "up" : "down"); + +@@ -80,22 +85,60 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, + state = nc->state; + spin_unlock_irqrestore(&nc->lock, flags); + +- if (!((old_data ^ data) & 0x1) || chained) +- return 0; +- if (!(state == NCSI_CHANNEL_INACTIVE && (data & 0x1)) && +- !(state == NCSI_CHANNEL_ACTIVE && !(data & 0x1))) ++ if (state == NCSI_CHANNEL_INACTIVE) ++ netdev_warn(ndp->ndev.dev, ++ "NCSI: Inactive channel %u received AEN!\n", ++ nc->id); ++ ++ if ((had_link == has_link) || chained) + return 0; + +- if (!(ndp->flags & NCSI_DEV_HWA) && +- state == NCSI_CHANNEL_ACTIVE) +- ndp->flags |= NCSI_DEV_RESHUFFLE; ++ if (!ndp->multi_package && !nc->package->multi_channel) { ++ if (had_link) { ++ ndp->flags |= NCSI_DEV_RESHUFFLE; ++ ncsi_stop_channel_monitor(nc); ++ spin_lock_irqsave(&ndp->lock, flags); ++ list_add_tail_rcu(&nc->link, &ndp->channel_queue); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return ncsi_process_next_channel(ndp); ++ } ++ /* Configured channel came up */ ++ return 0; ++ } + +- ncsi_stop_channel_monitor(nc); +- spin_lock_irqsave(&ndp->lock, flags); +- list_add_tail_rcu(&nc->link, &ndp->channel_queue); +- spin_unlock_irqrestore(&ndp->lock, flags); ++ if (had_link) { ++ ncm = &nc->modes[NCSI_MODE_TX_ENABLE]; ++ if (ncsi_channel_is_last(ndp, nc)) { ++ /* No channels left, reconfigure */ ++ return ncsi_reset_dev(&ndp->ndev); ++ } else if (ncm->enable) { ++ /* Need to failover Tx channel */ ++ ncsi_update_tx_channel(ndp, nc->package, nc, NULL); ++ } ++ } else if (has_link && nc->package->preferred_channel == nc) { ++ /* Return Tx to preferred channel */ ++ ncsi_update_tx_channel(ndp, nc->package, NULL, nc); ++ } else if (has_link) { ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ NCSI_FOR_EACH_CHANNEL(np, tmp) { ++ /* Enable Tx on this channel if the current Tx ++ * channel is down. ++ */ ++ ncm = &tmp->modes[NCSI_MODE_TX_ENABLE]; ++ if (ncm->enable && ++ !ncsi_channel_has_link(tmp)) { ++ ncsi_update_tx_channel(ndp, nc->package, ++ tmp, nc); ++ break; ++ } ++ } ++ } ++ } + +- return ncsi_process_next_channel(ndp); ++ /* Leave configured channels active in a multi-channel scenario so ++ * AEN events are still received. ++ */ ++ return 0; + } + + static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp, +diff --git a/net/ncsi/ncsi-manage.c b/net/ncsi/ncsi-manage.c +index bfc43b28c7a6..92e59f07f9a7 100644 +--- a/net/ncsi/ncsi-manage.c ++++ b/net/ncsi/ncsi-manage.c +@@ -28,6 +28,29 @@ + LIST_HEAD(ncsi_dev_list); + DEFINE_SPINLOCK(ncsi_dev_lock); + ++bool ncsi_channel_has_link(struct ncsi_channel *channel) ++{ ++ return !!(channel->modes[NCSI_MODE_LINK].data[2] & 0x1); ++} ++ ++bool ncsi_channel_is_last(struct ncsi_dev_priv *ndp, ++ struct ncsi_channel *channel) ++{ ++ struct ncsi_package *np; ++ struct ncsi_channel *nc; ++ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) ++ NCSI_FOR_EACH_CHANNEL(np, nc) { ++ if (nc == channel) ++ continue; ++ if (nc->state == NCSI_CHANNEL_ACTIVE && ++ ncsi_channel_has_link(nc)) ++ return false; ++ } ++ ++ return true; ++} ++ + static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down) + { + struct ncsi_dev *nd = &ndp->ndev; +@@ -52,7 +75,7 @@ static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down) + continue; + } + +- if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) { ++ if (ncsi_channel_has_link(nc)) { + spin_unlock_irqrestore(&nc->lock, flags); + nd->link_up = 1; + goto report; +@@ -113,10 +136,8 @@ static void ncsi_channel_monitor(struct timer_list *t) + default: + netdev_err(ndp->ndev.dev, "NCSI Channel %d timed out!\n", + nc->id); +- if (!(ndp->flags & NCSI_DEV_HWA)) { +- ncsi_report_link(ndp, true); +- ndp->flags |= NCSI_DEV_RESHUFFLE; +- } ++ ncsi_report_link(ndp, true); ++ ndp->flags |= NCSI_DEV_RESHUFFLE; + + ncsi_stop_channel_monitor(nc); + +@@ -269,6 +290,7 @@ struct ncsi_package *ncsi_add_package(struct ncsi_dev_priv *ndp, + np->ndp = ndp; + spin_lock_init(&np->lock); + INIT_LIST_HEAD(&np->channels); ++ np->channel_whitelist = UINT_MAX; + + spin_lock_irqsave(&ndp->lock, flags); + tmp = ncsi_find_package(ndp, id); +@@ -442,12 +464,14 @@ static void ncsi_request_timeout(struct timer_list *t) + static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) + { + struct ncsi_dev *nd = &ndp->ndev; +- struct ncsi_package *np = ndp->active_package; +- struct ncsi_channel *nc = ndp->active_channel; ++ struct ncsi_package *np; ++ struct ncsi_channel *nc, *tmp; + struct ncsi_cmd_arg nca; + unsigned long flags; + int ret; + ++ np = ndp->active_package; ++ nc = ndp->active_channel; + nca.ndp = ndp; + nca.req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN; + switch (nd->state) { +@@ -523,6 +547,15 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) + if (ret) + goto error; + ++ NCSI_FOR_EACH_CHANNEL(np, tmp) { ++ /* If there is another channel active on this package ++ * do not deselect the package. ++ */ ++ if (tmp != nc && tmp->state == NCSI_CHANNEL_ACTIVE) { ++ nd->state = ncsi_dev_state_suspend_done; ++ break; ++ } ++ } + break; + case ncsi_dev_state_suspend_deselect: + ndp->pending_req_num = 1; +@@ -541,8 +574,10 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) + spin_lock_irqsave(&nc->lock, flags); + nc->state = NCSI_CHANNEL_INACTIVE; + spin_unlock_irqrestore(&nc->lock, flags); +- ncsi_process_next_channel(ndp); +- ++ if (ndp->flags & NCSI_DEV_RESET) ++ ncsi_reset_dev(nd); ++ else ++ ncsi_process_next_channel(ndp); + break; + default: + netdev_warn(nd->dev, "Wrong NCSI state 0x%x in suspend\n", +@@ -717,13 +752,144 @@ static int ncsi_gma_handler(struct ncsi_cmd_arg *nca, unsigned int mf_id) + + #endif /* CONFIG_NCSI_OEM_CMD_GET_MAC */ + ++/* Determine if a given channel from the channel_queue should be used for Tx */ ++static bool ncsi_channel_is_tx(struct ncsi_dev_priv *ndp, ++ struct ncsi_channel *nc) ++{ ++ struct ncsi_channel_mode *ncm; ++ struct ncsi_channel *channel; ++ struct ncsi_package *np; ++ ++ /* Check if any other channel has Tx enabled; a channel may have already ++ * been configured and removed from the channel queue. ++ */ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ if (!ndp->multi_package && np != nc->package) ++ continue; ++ NCSI_FOR_EACH_CHANNEL(np, channel) { ++ ncm = &channel->modes[NCSI_MODE_TX_ENABLE]; ++ if (ncm->enable) ++ return false; ++ } ++ } ++ ++ /* This channel is the preferred channel and has link */ ++ list_for_each_entry_rcu(channel, &ndp->channel_queue, link) { ++ np = channel->package; ++ if (np->preferred_channel && ++ ncsi_channel_has_link(np->preferred_channel)) { ++ return np->preferred_channel == nc; ++ } ++ } ++ ++ /* This channel has link */ ++ if (ncsi_channel_has_link(nc)) ++ return true; ++ ++ list_for_each_entry_rcu(channel, &ndp->channel_queue, link) ++ if (ncsi_channel_has_link(channel)) ++ return false; ++ ++ /* No other channel has link; default to this one */ ++ return true; ++} ++ ++/* Change the active Tx channel in a multi-channel setup */ ++int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, ++ struct ncsi_package *package, ++ struct ncsi_channel *disable, ++ struct ncsi_channel *enable) ++{ ++ struct ncsi_cmd_arg nca; ++ struct ncsi_channel *nc; ++ struct ncsi_package *np; ++ int ret = 0; ++ ++ if (!package->multi_channel && !ndp->multi_package) ++ netdev_warn(ndp->ndev.dev, ++ "NCSI: Trying to update Tx channel in single-channel mode\n"); ++ nca.ndp = ndp; ++ nca.req_flags = 0; ++ ++ /* Find current channel with Tx enabled */ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ if (disable) ++ break; ++ if (!ndp->multi_package && np != package) ++ continue; ++ ++ NCSI_FOR_EACH_CHANNEL(np, nc) ++ if (nc->modes[NCSI_MODE_TX_ENABLE].enable) { ++ disable = nc; ++ break; ++ } ++ } ++ ++ /* Find a suitable channel for Tx */ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ if (enable) ++ break; ++ if (!ndp->multi_package && np != package) ++ continue; ++ if (!(ndp->package_whitelist & (0x1 << np->id))) ++ continue; ++ ++ if (np->preferred_channel && ++ ncsi_channel_has_link(np->preferred_channel)) { ++ enable = np->preferred_channel; ++ break; ++ } ++ ++ NCSI_FOR_EACH_CHANNEL(np, nc) { ++ if (!(np->channel_whitelist & 0x1 << nc->id)) ++ continue; ++ if (nc->state != NCSI_CHANNEL_ACTIVE) ++ continue; ++ if (ncsi_channel_has_link(nc)) { ++ enable = nc; ++ break; ++ } ++ } ++ } ++ ++ if (disable == enable) ++ return -1; ++ ++ if (!enable) ++ return -1; ++ ++ if (disable) { ++ nca.channel = disable->id; ++ nca.package = disable->package->id; ++ nca.type = NCSI_PKT_CMD_DCNT; ++ ret = ncsi_xmit_cmd(&nca); ++ if (ret) ++ netdev_err(ndp->ndev.dev, ++ "Error %d sending DCNT\n", ++ ret); ++ } ++ ++ netdev_info(ndp->ndev.dev, "NCSI: channel %u enables Tx\n", enable->id); ++ ++ nca.channel = enable->id; ++ nca.package = enable->package->id; ++ nca.type = NCSI_PKT_CMD_ECNT; ++ ret = ncsi_xmit_cmd(&nca); ++ if (ret) ++ netdev_err(ndp->ndev.dev, ++ "Error %d sending ECNT\n", ++ ret); ++ ++ return ret; ++} ++ + static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + { +- struct ncsi_dev *nd = &ndp->ndev; +- struct net_device *dev = nd->dev; + struct ncsi_package *np = ndp->active_package; + struct ncsi_channel *nc = ndp->active_channel; + struct ncsi_channel *hot_nc = NULL; ++ struct ncsi_dev *nd = &ndp->ndev; ++ struct net_device *dev = nd->dev; + struct ncsi_cmd_arg nca; + unsigned char index; + unsigned long flags; +@@ -845,20 +1011,29 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + } else if (nd->state == ncsi_dev_state_config_ebf) { + nca.type = NCSI_PKT_CMD_EBF; + nca.dwords[0] = nc->caps[NCSI_CAP_BC].cap; +- nd->state = ncsi_dev_state_config_ecnt; ++ if (ncsi_channel_is_tx(ndp, nc)) ++ nd->state = ncsi_dev_state_config_ecnt; ++ else ++ nd->state = ncsi_dev_state_config_ec; + #if IS_ENABLED(CONFIG_IPV6) + if (ndp->inet6_addr_num > 0 && + (nc->caps[NCSI_CAP_GENERIC].cap & + NCSI_CAP_GENERIC_MC)) + nd->state = ncsi_dev_state_config_egmf; +- else +- nd->state = ncsi_dev_state_config_ecnt; + } else if (nd->state == ncsi_dev_state_config_egmf) { + nca.type = NCSI_PKT_CMD_EGMF; + nca.dwords[0] = nc->caps[NCSI_CAP_MC].cap; +- nd->state = ncsi_dev_state_config_ecnt; ++ if (ncsi_channel_is_tx(ndp, nc)) ++ nd->state = ncsi_dev_state_config_ecnt; ++ else ++ nd->state = ncsi_dev_state_config_ec; + #endif /* CONFIG_IPV6 */ + } else if (nd->state == ncsi_dev_state_config_ecnt) { ++ if (np->preferred_channel && ++ nc != np->preferred_channel) ++ netdev_info(ndp->ndev.dev, ++ "NCSI: Tx failed over to channel %u\n", ++ nc->id); + nca.type = NCSI_PKT_CMD_ECNT; + nd->state = ncsi_dev_state_config_ec; + } else if (nd->state == ncsi_dev_state_config_ec) { +@@ -889,6 +1064,16 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + netdev_dbg(ndp->ndev.dev, "NCSI: channel %u config done\n", + nc->id); + spin_lock_irqsave(&nc->lock, flags); ++ nc->state = NCSI_CHANNEL_ACTIVE; ++ ++ if (ndp->flags & NCSI_DEV_RESET) { ++ /* A reset event happened during config, start it now */ ++ nc->reconfigure_needed = false; ++ spin_unlock_irqrestore(&nc->lock, flags); ++ ncsi_reset_dev(nd); ++ break; ++ } ++ + if (nc->reconfigure_needed) { + /* This channel's configuration has been updated + * part-way during the config state - start the +@@ -909,10 +1094,8 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + + if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) { + hot_nc = nc; +- nc->state = NCSI_CHANNEL_ACTIVE; + } else { + hot_nc = NULL; +- nc->state = NCSI_CHANNEL_INACTIVE; + netdev_dbg(ndp->ndev.dev, + "NCSI: channel %u link down after config\n", + nc->id); +@@ -940,43 +1123,35 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + + static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp) + { +- struct ncsi_package *np, *force_package; +- struct ncsi_channel *nc, *found, *hot_nc, *force_channel; ++ struct ncsi_channel *nc, *found, *hot_nc; + struct ncsi_channel_mode *ncm; +- unsigned long flags; ++ unsigned long flags, cflags; ++ struct ncsi_package *np; ++ bool with_link; + + spin_lock_irqsave(&ndp->lock, flags); + hot_nc = ndp->hot_channel; +- force_channel = ndp->force_channel; +- force_package = ndp->force_package; + spin_unlock_irqrestore(&ndp->lock, flags); + +- /* Force a specific channel whether or not it has link if we have been +- * configured to do so +- */ +- if (force_package && force_channel) { +- found = force_channel; +- ncm = &found->modes[NCSI_MODE_LINK]; +- if (!(ncm->data[2] & 0x1)) +- netdev_info(ndp->ndev.dev, +- "NCSI: Channel %u forced, but it is link down\n", +- found->id); +- goto out; +- } +- +- /* The search is done once an inactive channel with up +- * link is found. ++ /* By default the search is done once an inactive channel with up ++ * link is found, unless a preferred channel is set. ++ * If multi_package or multi_channel are configured all channels in the ++ * whitelist are added to the channel queue. + */ + found = NULL; ++ with_link = false; + NCSI_FOR_EACH_PACKAGE(ndp, np) { +- if (ndp->force_package && np != ndp->force_package) ++ if (!(ndp->package_whitelist & (0x1 << np->id))) + continue; + NCSI_FOR_EACH_CHANNEL(np, nc) { +- spin_lock_irqsave(&nc->lock, flags); ++ if (!(np->channel_whitelist & (0x1 << nc->id))) ++ continue; ++ ++ spin_lock_irqsave(&nc->lock, cflags); + + if (!list_empty(&nc->link) || + nc->state != NCSI_CHANNEL_INACTIVE) { +- spin_unlock_irqrestore(&nc->lock, flags); ++ spin_unlock_irqrestore(&nc->lock, cflags); + continue; + } + +@@ -988,32 +1163,49 @@ static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp) + + ncm = &nc->modes[NCSI_MODE_LINK]; + if (ncm->data[2] & 0x1) { +- spin_unlock_irqrestore(&nc->lock, flags); + found = nc; +- goto out; ++ with_link = true; + } + +- spin_unlock_irqrestore(&nc->lock, flags); ++ /* If multi_channel is enabled configure all valid ++ * channels whether or not they currently have link ++ * so they will have AENs enabled. ++ */ ++ if (with_link || np->multi_channel) { ++ spin_lock_irqsave(&ndp->lock, flags); ++ list_add_tail_rcu(&nc->link, ++ &ndp->channel_queue); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ netdev_dbg(ndp->ndev.dev, ++ "NCSI: Channel %u added to queue (link %s)\n", ++ nc->id, ++ ncm->data[2] & 0x1 ? "up" : "down"); ++ } ++ ++ spin_unlock_irqrestore(&nc->lock, cflags); ++ ++ if (with_link && !np->multi_channel) ++ break; + } ++ if (with_link && !ndp->multi_package) ++ break; + } + +- if (!found) { ++ if (list_empty(&ndp->channel_queue) && found) { ++ netdev_info(ndp->ndev.dev, ++ "NCSI: No channel with link found, configuring channel %u\n", ++ found->id); ++ spin_lock_irqsave(&ndp->lock, flags); ++ list_add_tail_rcu(&found->link, &ndp->channel_queue); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ } else if (!found) { + netdev_warn(ndp->ndev.dev, +- "NCSI: No channel found with link\n"); ++ "NCSI: No channel found to configure!\n"); + ncsi_report_link(ndp, true); + return -ENODEV; + } + +- ncm = &found->modes[NCSI_MODE_LINK]; +- netdev_dbg(ndp->ndev.dev, +- "NCSI: Channel %u added to queue (link %s)\n", +- found->id, ncm->data[2] & 0x1 ? "up" : "down"); +- +-out: +- spin_lock_irqsave(&ndp->lock, flags); +- list_add_tail_rcu(&found->link, &ndp->channel_queue); +- spin_unlock_irqrestore(&ndp->lock, flags); +- + return ncsi_process_next_channel(ndp); + } + +@@ -1050,35 +1242,6 @@ static bool ncsi_check_hwa(struct ncsi_dev_priv *ndp) + return false; + } + +-static int ncsi_enable_hwa(struct ncsi_dev_priv *ndp) +-{ +- struct ncsi_package *np; +- struct ncsi_channel *nc; +- unsigned long flags; +- +- /* Move all available channels to processing queue */ +- spin_lock_irqsave(&ndp->lock, flags); +- NCSI_FOR_EACH_PACKAGE(ndp, np) { +- NCSI_FOR_EACH_CHANNEL(np, nc) { +- WARN_ON_ONCE(nc->state != NCSI_CHANNEL_INACTIVE || +- !list_empty(&nc->link)); +- ncsi_stop_channel_monitor(nc); +- list_add_tail_rcu(&nc->link, &ndp->channel_queue); +- } +- } +- spin_unlock_irqrestore(&ndp->lock, flags); +- +- /* We can have no channels in extremely case */ +- if (list_empty(&ndp->channel_queue)) { +- netdev_err(ndp->ndev.dev, +- "NCSI: No available channels for HWA\n"); +- ncsi_report_link(ndp, false); +- return -ENOENT; +- } +- +- return ncsi_process_next_channel(ndp); +-} +- + static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) + { + struct ncsi_dev *nd = &ndp->ndev; +@@ -1110,70 +1273,28 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) + nd->state = ncsi_dev_state_probe_package; + break; + case ncsi_dev_state_probe_package: +- ndp->pending_req_num = 16; ++ ndp->pending_req_num = 1; + +- /* Select all possible packages */ + nca.type = NCSI_PKT_CMD_SP; + nca.bytes[0] = 1; ++ nca.package = ndp->package_probe_id; + nca.channel = NCSI_RESERVED_CHANNEL; +- for (index = 0; index < 8; index++) { +- nca.package = index; +- ret = ncsi_xmit_cmd(&nca); +- if (ret) +- goto error; +- } +- +- /* Disable all possible packages */ +- nca.type = NCSI_PKT_CMD_DP; +- for (index = 0; index < 8; index++) { +- nca.package = index; +- ret = ncsi_xmit_cmd(&nca); +- if (ret) +- goto error; +- } +- ++ ret = ncsi_xmit_cmd(&nca); ++ if (ret) ++ goto error; + nd->state = ncsi_dev_state_probe_channel; + break; + case ncsi_dev_state_probe_channel: +- if (!ndp->active_package) +- ndp->active_package = list_first_or_null_rcu( +- &ndp->packages, struct ncsi_package, node); +- else if (list_is_last(&ndp->active_package->node, +- &ndp->packages)) +- ndp->active_package = NULL; +- else +- ndp->active_package = list_next_entry( +- ndp->active_package, node); +- +- /* All available packages and channels are enumerated. The +- * enumeration happens for once when the NCSI interface is +- * started. So we need continue to start the interface after +- * the enumeration. +- * +- * We have to choose an active channel before configuring it. +- * Note that we possibly don't have active channel in extreme +- * situation. +- */ ++ ndp->active_package = ncsi_find_package(ndp, ++ ndp->package_probe_id); + if (!ndp->active_package) { +- ndp->flags |= NCSI_DEV_PROBED; +- if (ncsi_check_hwa(ndp)) +- ncsi_enable_hwa(ndp); +- else +- ncsi_choose_active_channel(ndp); +- return; ++ /* No response */ ++ nd->state = ncsi_dev_state_probe_dp; ++ schedule_work(&ndp->work); ++ break; + } +- +- /* Select the active package */ +- ndp->pending_req_num = 1; +- nca.type = NCSI_PKT_CMD_SP; +- nca.bytes[0] = 1; +- nca.package = ndp->active_package->id; +- nca.channel = NCSI_RESERVED_CHANNEL; +- ret = ncsi_xmit_cmd(&nca); +- if (ret) +- goto error; +- + nd->state = ncsi_dev_state_probe_cis; ++ schedule_work(&ndp->work); + break; + case ncsi_dev_state_probe_cis: + ndp->pending_req_num = NCSI_RESERVED_CHANNEL; +@@ -1222,22 +1343,35 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) + case ncsi_dev_state_probe_dp: + ndp->pending_req_num = 1; + +- /* Deselect the active package */ ++ /* Deselect the current package */ + nca.type = NCSI_PKT_CMD_DP; +- nca.package = ndp->active_package->id; ++ nca.package = ndp->package_probe_id; + nca.channel = NCSI_RESERVED_CHANNEL; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + +- /* Scan channels in next package */ +- nd->state = ncsi_dev_state_probe_channel; ++ /* Probe next package */ ++ ndp->package_probe_id++; ++ if (ndp->package_probe_id >= 8) { ++ /* Probe finished */ ++ ndp->flags |= NCSI_DEV_PROBED; ++ break; ++ } ++ nd->state = ncsi_dev_state_probe_package; ++ ndp->active_package = NULL; + break; + default: + netdev_warn(nd->dev, "Wrong NCSI state 0x%0x in enumeration\n", + nd->state); + } + ++ if (ndp->flags & NCSI_DEV_PROBED) { ++ /* Check if all packages have HWA support */ ++ ncsi_check_hwa(ndp); ++ ncsi_choose_active_channel(ndp); ++ } ++ + return; + error: + netdev_err(ndp->ndev.dev, +@@ -1556,6 +1690,7 @@ struct ncsi_dev *ncsi_register_dev(struct net_device *dev, + INIT_LIST_HEAD(&ndp->channel_queue); + INIT_LIST_HEAD(&ndp->vlan_vids); + INIT_WORK(&ndp->work, ncsi_dev_work); ++ ndp->package_whitelist = UINT_MAX; + + /* Initialize private NCSI device */ + spin_lock_init(&ndp->lock); +@@ -1592,26 +1727,19 @@ EXPORT_SYMBOL_GPL(ncsi_register_dev); + int ncsi_start_dev(struct ncsi_dev *nd) + { + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); +- int ret; + + if (nd->state != ncsi_dev_state_registered && + nd->state != ncsi_dev_state_functional) + return -ENOTTY; + + if (!(ndp->flags & NCSI_DEV_PROBED)) { ++ ndp->package_probe_id = 0; + nd->state = ncsi_dev_state_probe; + schedule_work(&ndp->work); + return 0; + } + +- if (ndp->flags & NCSI_DEV_HWA) { +- netdev_info(ndp->ndev.dev, "NCSI: Enabling HWA mode\n"); +- ret = ncsi_enable_hwa(ndp); +- } else { +- ret = ncsi_choose_active_channel(ndp); +- } +- +- return ret; ++ return ncsi_reset_dev(nd); + } + EXPORT_SYMBOL_GPL(ncsi_start_dev); + +@@ -1624,7 +1752,10 @@ void ncsi_stop_dev(struct ncsi_dev *nd) + int old_state; + unsigned long flags; + +- /* Stop the channel monitor and reset channel's state */ ++ /* Stop the channel monitor on any active channels. Don't reset the ++ * channel state so we know which were active when ncsi_start_dev() ++ * is next called. ++ */ + NCSI_FOR_EACH_PACKAGE(ndp, np) { + NCSI_FOR_EACH_CHANNEL(np, nc) { + ncsi_stop_channel_monitor(nc); +@@ -1632,7 +1763,6 @@ void ncsi_stop_dev(struct ncsi_dev *nd) + spin_lock_irqsave(&nc->lock, flags); + chained = !list_empty(&nc->link); + old_state = nc->state; +- nc->state = NCSI_CHANNEL_INACTIVE; + spin_unlock_irqrestore(&nc->lock, flags); + + WARN_ON_ONCE(chained || +@@ -1645,6 +1775,92 @@ void ncsi_stop_dev(struct ncsi_dev *nd) + } + EXPORT_SYMBOL_GPL(ncsi_stop_dev); + ++int ncsi_reset_dev(struct ncsi_dev *nd) ++{ ++ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); ++ struct ncsi_channel *nc, *active, *tmp; ++ struct ncsi_package *np; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ndp->lock, flags); ++ ++ if (!(ndp->flags & NCSI_DEV_RESET)) { ++ /* Haven't been called yet, check states */ ++ switch (nd->state & ncsi_dev_state_major) { ++ case ncsi_dev_state_registered: ++ case ncsi_dev_state_probe: ++ /* Not even probed yet - do nothing */ ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return 0; ++ case ncsi_dev_state_suspend: ++ case ncsi_dev_state_config: ++ /* Wait for the channel to finish its suspend/config ++ * operation; once it finishes it will check for ++ * NCSI_DEV_RESET and reset the state. ++ */ ++ ndp->flags |= NCSI_DEV_RESET; ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return 0; ++ } ++ } else { ++ switch (nd->state) { ++ case ncsi_dev_state_suspend_done: ++ case ncsi_dev_state_config_done: ++ case ncsi_dev_state_functional: ++ /* Ok */ ++ break; ++ default: ++ /* Current reset operation happening */ ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return 0; ++ } ++ } ++ ++ if (!list_empty(&ndp->channel_queue)) { ++ /* Clear any channel queue we may have interrupted */ ++ list_for_each_entry_safe(nc, tmp, &ndp->channel_queue, link) ++ list_del_init(&nc->link); ++ } ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ active = NULL; ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ NCSI_FOR_EACH_CHANNEL(np, nc) { ++ spin_lock_irqsave(&nc->lock, flags); ++ ++ if (nc->state == NCSI_CHANNEL_ACTIVE) { ++ active = nc; ++ nc->state = NCSI_CHANNEL_INVISIBLE; ++ spin_unlock_irqrestore(&nc->lock, flags); ++ ncsi_stop_channel_monitor(nc); ++ break; ++ } ++ ++ spin_unlock_irqrestore(&nc->lock, flags); ++ } ++ if (active) ++ break; ++ } ++ ++ if (!active) { ++ /* Done */ ++ spin_lock_irqsave(&ndp->lock, flags); ++ ndp->flags &= ~NCSI_DEV_RESET; ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return ncsi_choose_active_channel(ndp); ++ } ++ ++ spin_lock_irqsave(&ndp->lock, flags); ++ ndp->flags |= NCSI_DEV_RESET; ++ ndp->active_channel = active; ++ ndp->active_package = active->package; ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ nd->state = ncsi_dev_state_suspend; ++ schedule_work(&ndp->work); ++ return 0; ++} ++ + void ncsi_unregister_dev(struct ncsi_dev *nd) + { + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); +diff --git a/net/ncsi/ncsi-netlink.c b/net/ncsi/ncsi-netlink.c +index 33314381b4f5..5d782445d2fc 100644 +--- a/net/ncsi/ncsi-netlink.c ++++ b/net/ncsi/ncsi-netlink.c +@@ -30,6 +30,9 @@ static const struct nla_policy ncsi_genl_policy[NCSI_ATTR_MAX + 1] = { + [NCSI_ATTR_PACKAGE_ID] = { .type = NLA_U32 }, + [NCSI_ATTR_CHANNEL_ID] = { .type = NLA_U32 }, + [NCSI_ATTR_DATA] = { .type = NLA_BINARY, .len = 2048 }, ++ [NCSI_ATTR_MULTI_FLAG] = { .type = NLA_FLAG }, ++ [NCSI_ATTR_PACKAGE_MASK] = { .type = NLA_U32 }, ++ [NCSI_ATTR_CHANNEL_MASK] = { .type = NLA_U32 }, + }; + + static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex) +@@ -69,7 +72,7 @@ static int ncsi_write_channel_info(struct sk_buff *skb, + nla_put_u32(skb, NCSI_CHANNEL_ATTR_LINK_STATE, m->data[2]); + if (nc->state == NCSI_CHANNEL_ACTIVE) + nla_put_flag(skb, NCSI_CHANNEL_ATTR_ACTIVE); +- if (ndp->force_channel == nc) ++ if (nc == nc->package->preferred_channel) + nla_put_flag(skb, NCSI_CHANNEL_ATTR_FORCED); + + nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MAJOR, nc->version.version); +@@ -114,7 +117,7 @@ static int ncsi_write_package_info(struct sk_buff *skb, + if (!pnest) + return -ENOMEM; + nla_put_u32(skb, NCSI_PKG_ATTR_ID, np->id); +- if (ndp->force_package == np) ++ if ((0x1 << np->id) == ndp->package_whitelist) + nla_put_flag(skb, NCSI_PKG_ATTR_FORCED); + cnest = nla_nest_start(skb, NCSI_PKG_ATTR_CHANNEL_LIST); + if (!cnest) { +@@ -290,49 +293,58 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) + package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); + package = NULL; + +- spin_lock_irqsave(&ndp->lock, flags); +- + NCSI_FOR_EACH_PACKAGE(ndp, np) + if (np->id == package_id) + package = np; + if (!package) { + /* The user has set a package that does not exist */ +- spin_unlock_irqrestore(&ndp->lock, flags); + return -ERANGE; + } + + channel = NULL; +- if (!info->attrs[NCSI_ATTR_CHANNEL_ID]) { +- /* Allow any channel */ +- channel_id = NCSI_RESERVED_CHANNEL; +- } else { ++ if (info->attrs[NCSI_ATTR_CHANNEL_ID]) { + channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); + NCSI_FOR_EACH_CHANNEL(package, nc) +- if (nc->id == channel_id) ++ if (nc->id == channel_id) { + channel = nc; ++ break; ++ } ++ if (!channel) { ++ netdev_info(ndp->ndev.dev, ++ "NCSI: Channel %u does not exist!\n", ++ channel_id); ++ return -ERANGE; ++ } + } + +- if (channel_id != NCSI_RESERVED_CHANNEL && !channel) { +- /* The user has set a channel that does not exist on this +- * package +- */ +- spin_unlock_irqrestore(&ndp->lock, flags); +- netdev_info(ndp->ndev.dev, "NCSI: Channel %u does not exist!\n", +- channel_id); +- return -ERANGE; +- } +- +- ndp->force_package = package; +- ndp->force_channel = channel; ++ spin_lock_irqsave(&ndp->lock, flags); ++ ndp->package_whitelist = 0x1 << package->id; ++ ndp->multi_package = false; + spin_unlock_irqrestore(&ndp->lock, flags); + +- netdev_info(ndp->ndev.dev, "Set package 0x%x, channel 0x%x%s as preferred\n", +- package_id, channel_id, +- channel_id == NCSI_RESERVED_CHANNEL ? " (any)" : ""); ++ spin_lock_irqsave(&package->lock, flags); ++ package->multi_channel = false; ++ if (channel) { ++ package->channel_whitelist = 0x1 << channel->id; ++ package->preferred_channel = channel; ++ } else { ++ /* Allow any channel */ ++ package->channel_whitelist = UINT_MAX; ++ package->preferred_channel = NULL; ++ } ++ spin_unlock_irqrestore(&package->lock, flags); ++ ++ if (channel) ++ netdev_info(ndp->ndev.dev, ++ "Set package 0x%x, channel 0x%x as preferred\n", ++ package_id, channel_id); ++ else ++ netdev_info(ndp->ndev.dev, "Set package 0x%x as preferred\n", ++ package_id); + +- /* Bounce the NCSI channel to set changes */ +- ncsi_stop_dev(&ndp->ndev); +- ncsi_start_dev(&ndp->ndev); ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); + + return 0; + } +@@ -340,6 +352,7 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) + static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) + { + struct ncsi_dev_priv *ndp; ++ struct ncsi_package *np; + unsigned long flags; + + if (!info || !info->attrs) +@@ -353,16 +366,24 @@ static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) + if (!ndp) + return -ENODEV; + +- /* Clear any override */ ++ /* Reset any whitelists and disable multi mode */ + spin_lock_irqsave(&ndp->lock, flags); +- ndp->force_package = NULL; +- ndp->force_channel = NULL; ++ ndp->package_whitelist = UINT_MAX; ++ ndp->multi_package = false; + spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ spin_lock_irqsave(&np->lock, flags); ++ np->multi_channel = false; ++ np->channel_whitelist = UINT_MAX; ++ np->preferred_channel = NULL; ++ spin_unlock_irqrestore(&np->lock, flags); ++ } + netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n"); + +- /* Bounce the NCSI channel to set changes */ +- ncsi_stop_dev(&ndp->ndev); +- ncsi_start_dev(&ndp->ndev); ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); + + return 0; + } +@@ -563,6 +584,138 @@ int ncsi_send_netlink_err(struct net_device *dev, + return nlmsg_unicast(net->genl_sock, skb, snd_portid); + } + ++static int ncsi_set_package_mask_nl(struct sk_buff *msg, ++ struct genl_info *info) ++{ ++ struct ncsi_dev_priv *ndp; ++ unsigned long flags; ++ int rc; ++ ++ if (!info || !info->attrs) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_IFINDEX]) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_PACKAGE_MASK]) ++ return -EINVAL; ++ ++ ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), ++ nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); ++ if (!ndp) ++ return -ENODEV; ++ ++ spin_lock_irqsave(&ndp->lock, flags); ++ if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) { ++ if (ndp->flags & NCSI_DEV_HWA) { ++ ndp->multi_package = true; ++ rc = 0; ++ } else { ++ netdev_err(ndp->ndev.dev, ++ "NCSI: Can't use multiple packages without HWA\n"); ++ rc = -EPERM; ++ } ++ } else { ++ ndp->multi_package = false; ++ rc = 0; ++ } ++ ++ if (!rc) ++ ndp->package_whitelist = ++ nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_MASK]); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ if (!rc) { ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); ++ } ++ ++ return rc; ++} ++ ++static int ncsi_set_channel_mask_nl(struct sk_buff *msg, ++ struct genl_info *info) ++{ ++ struct ncsi_package *np, *package; ++ struct ncsi_channel *nc, *channel; ++ u32 package_id, channel_id; ++ struct ncsi_dev_priv *ndp; ++ unsigned long flags; ++ ++ if (!info || !info->attrs) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_IFINDEX]) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_CHANNEL_MASK]) ++ return -EINVAL; ++ ++ ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), ++ nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); ++ if (!ndp) ++ return -ENODEV; ++ ++ package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); ++ package = NULL; ++ NCSI_FOR_EACH_PACKAGE(ndp, np) ++ if (np->id == package_id) { ++ package = np; ++ break; ++ } ++ if (!package) ++ return -ERANGE; ++ ++ spin_lock_irqsave(&package->lock, flags); ++ ++ channel = NULL; ++ if (info->attrs[NCSI_ATTR_CHANNEL_ID]) { ++ channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); ++ NCSI_FOR_EACH_CHANNEL(np, nc) ++ if (nc->id == channel_id) { ++ channel = nc; ++ break; ++ } ++ if (!channel) { ++ spin_unlock_irqrestore(&package->lock, flags); ++ return -ERANGE; ++ } ++ netdev_dbg(ndp->ndev.dev, ++ "NCSI: Channel %u set as preferred channel\n", ++ channel->id); ++ } ++ ++ package->channel_whitelist = ++ nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_MASK]); ++ if (package->channel_whitelist == 0) ++ netdev_dbg(ndp->ndev.dev, ++ "NCSI: Package %u set to all channels disabled\n", ++ package->id); ++ ++ package->preferred_channel = channel; ++ ++ if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) { ++ package->multi_channel = true; ++ netdev_info(ndp->ndev.dev, ++ "NCSI: Multi-channel enabled on package %u\n", ++ package_id); ++ } else { ++ package->multi_channel = false; ++ } ++ ++ spin_unlock_irqrestore(&package->lock, flags); ++ ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); ++ ++ return 0; ++} ++ + static const struct genl_ops ncsi_ops[] = { + { + .cmd = NCSI_CMD_PKG_INFO, +@@ -589,6 +742,18 @@ static const struct genl_ops ncsi_ops[] = { + .doit = ncsi_send_cmd_nl, + .flags = GENL_ADMIN_PERM, + }, ++ { ++ .cmd = NCSI_CMD_SET_PACKAGE_MASK, ++ .policy = ncsi_genl_policy, ++ .doit = ncsi_set_package_mask_nl, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = NCSI_CMD_SET_CHANNEL_MASK, ++ .policy = ncsi_genl_policy, ++ .doit = ncsi_set_channel_mask_nl, ++ .flags = GENL_ADMIN_PERM, ++ }, + }; + + static struct genl_family ncsi_genl_family __ro_after_init = { +diff --git a/net/ncsi/ncsi-rsp.c b/net/ncsi/ncsi-rsp.c +index 77e07ba3f493..de7737a27889 100644 +--- a/net/ncsi/ncsi-rsp.c ++++ b/net/ncsi/ncsi-rsp.c +@@ -256,7 +256,7 @@ static int ncsi_rsp_handler_dcnt(struct ncsi_request *nr) + if (!ncm->enable) + return 0; + +- ncm->enable = 1; ++ ncm->enable = 0; + return 0; + } + +-- +2.7.4 + |