forked from Minki/linux
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: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
1115439f53
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
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;
|
||||
unsigned long old_data, data;
|
||||
unsigned long flags;
|
||||
|
||||
/* 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,
|
||||
|
@ -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 @@ error:
|
||||
|
||||
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;
|
||||
|
||||
/* Select all possible packages */
|
||||
nca.type = NCSI_PKT_CMD_SP;
|
||||
nca.bytes[0] = 1;
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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.package = ndp->package_probe_id;
|
||||
nca.channel = NCSI_RESERVED_CHANNEL;
|
||||
ret = ncsi_xmit_cmd(&nca);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
nd->state = ncsi_dev_state_probe_channel;
|
||||
break;
|
||||
case ncsi_dev_state_probe_channel:
|
||||
ndp->active_package = ncsi_find_package(ndp,
|
||||
ndp->package_probe_id);
|
||||
if (!ndp->active_package) {
|
||||
/* No response */
|
||||
nd->state = ncsi_dev_state_probe_dp;
|
||||
schedule_work(&ndp->work);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
|
@ -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);
|
||||
|
||||
/* Bounce the NCSI channel to set changes */
|
||||
ncsi_stop_dev(&ndp->ndev);
|
||||
ncsi_start_dev(&ndp->ndev);
|
||||
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);
|
||||
|
||||
/* 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 = {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user