summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch
diff options
context:
space:
mode:
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.patch1425
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
+