Commit 1115439f authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'ncsi-Allow-enabling-multiple-packages-and-channels'



Samuel Mendoza-Jonas says:

====================
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.

Changes in v2:
- Updated use of the channel lock in ncsi_reset_dev(), making the
channel invisible and leaving the monitor check to
ncsi_stop_channel_monitor().
- Fixed ncsi_channel_is_tx() to consider the state of channels in other
packages.
Changes in v3:
- Fixed bisectability bug in patch 1
- Consider channels on all packages in a few places when multi-package
is enabled.
- Avoid doubling up reset operations, and check the current driver state
before reset to let any running operations complete.
- Reorganise the LSC handler slightly to avoid enabling Tx twice.
Changes in v4:
- Fix failover in the single-channel case
- Better handle ncsi_reset_dev() entry during a current config/suspend
operation.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 2391b003 8d951a75
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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
+17 −2
Original line number Diff line number Diff line
@@ -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);
+59 −16
Original line number Diff line number Diff line
@@ -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,23 +85,61 @@ 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)
	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;
	}

	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;
				}
			}
		}
	}

	/* 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,
			       struct ncsi_aen_pkt_hdr *h)
+369 −153

File changed.

Preview size limit exceeded, changes collapsed.

+199 −34
Original line number Diff line number Diff line
@@ -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_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",
		if (!channel) {
			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 = {
Loading