mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
9.7 KiB
447 lines
9.7 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* Copyright (C) 2018-2021, Intel Corporation. */ |
|
|
|
/* Link Aggregation code */ |
|
|
|
#include "ice.h" |
|
#include "ice_lag.h" |
|
|
|
/** |
|
* ice_lag_nop_handler - no-op Rx handler to disable LAG |
|
* @pskb: pointer to skb pointer |
|
*/ |
|
rx_handler_result_t ice_lag_nop_handler(struct sk_buff __always_unused **pskb) |
|
{ |
|
return RX_HANDLER_PASS; |
|
} |
|
|
|
/** |
|
* ice_lag_set_primary - set PF LAG state as Primary |
|
* @lag: LAG info struct |
|
*/ |
|
static void ice_lag_set_primary(struct ice_lag *lag) |
|
{ |
|
struct ice_pf *pf = lag->pf; |
|
|
|
if (!pf) |
|
return; |
|
|
|
if (lag->role != ICE_LAG_UNSET && lag->role != ICE_LAG_BACKUP) { |
|
dev_warn(ice_pf_to_dev(pf), "%s: Attempt to be Primary, but incompatible state.\n", |
|
netdev_name(lag->netdev)); |
|
return; |
|
} |
|
|
|
lag->role = ICE_LAG_PRIMARY; |
|
} |
|
|
|
/** |
|
* ice_lag_set_backup - set PF LAG state to Backup |
|
* @lag: LAG info struct |
|
*/ |
|
static void ice_lag_set_backup(struct ice_lag *lag) |
|
{ |
|
struct ice_pf *pf = lag->pf; |
|
|
|
if (!pf) |
|
return; |
|
|
|
if (lag->role != ICE_LAG_UNSET && lag->role != ICE_LAG_PRIMARY) { |
|
dev_dbg(ice_pf_to_dev(pf), "%s: Attempt to be Backup, but incompatible state\n", |
|
netdev_name(lag->netdev)); |
|
return; |
|
} |
|
|
|
lag->role = ICE_LAG_BACKUP; |
|
} |
|
|
|
/** |
|
* ice_display_lag_info - print LAG info |
|
* @lag: LAG info struct |
|
*/ |
|
static void ice_display_lag_info(struct ice_lag *lag) |
|
{ |
|
const char *name, *peer, *upper, *role, *bonded, *master; |
|
struct device *dev = &lag->pf->pdev->dev; |
|
|
|
name = lag->netdev ? netdev_name(lag->netdev) : "unset"; |
|
peer = lag->peer_netdev ? netdev_name(lag->peer_netdev) : "unset"; |
|
upper = lag->upper_netdev ? netdev_name(lag->upper_netdev) : "unset"; |
|
master = lag->master ? "TRUE" : "FALSE"; |
|
bonded = lag->bonded ? "BONDED" : "UNBONDED"; |
|
|
|
switch (lag->role) { |
|
case ICE_LAG_NONE: |
|
role = "NONE"; |
|
break; |
|
case ICE_LAG_PRIMARY: |
|
role = "PRIMARY"; |
|
break; |
|
case ICE_LAG_BACKUP: |
|
role = "BACKUP"; |
|
break; |
|
case ICE_LAG_UNSET: |
|
role = "UNSET"; |
|
break; |
|
default: |
|
role = "ERROR"; |
|
} |
|
|
|
dev_dbg(dev, "%s %s, peer:%s, upper:%s, role:%s, master:%s\n", name, |
|
bonded, peer, upper, role, master); |
|
} |
|
|
|
/** |
|
* ice_lag_info_event - handle NETDEV_BONDING_INFO event |
|
* @lag: LAG info struct |
|
* @ptr: opaque data pointer |
|
* |
|
* ptr is to be cast to (netdev_notifier_bonding_info *) |
|
*/ |
|
static void ice_lag_info_event(struct ice_lag *lag, void *ptr) |
|
{ |
|
struct net_device *event_netdev, *netdev_tmp; |
|
struct netdev_notifier_bonding_info *info; |
|
struct netdev_bonding_info *bonding_info; |
|
const char *lag_netdev_name; |
|
|
|
event_netdev = netdev_notifier_info_to_dev(ptr); |
|
info = ptr; |
|
lag_netdev_name = netdev_name(lag->netdev); |
|
bonding_info = &info->bonding_info; |
|
|
|
if (event_netdev != lag->netdev || !lag->bonded || !lag->upper_netdev) |
|
return; |
|
|
|
if (bonding_info->master.bond_mode != BOND_MODE_ACTIVEBACKUP) { |
|
netdev_dbg(lag->netdev, "Bonding event recv, but mode not active/backup\n"); |
|
goto lag_out; |
|
} |
|
|
|
if (strcmp(bonding_info->slave.slave_name, lag_netdev_name)) { |
|
netdev_dbg(lag->netdev, "Bonding event recv, but slave info not for us\n"); |
|
goto lag_out; |
|
} |
|
|
|
rcu_read_lock(); |
|
for_each_netdev_in_bond_rcu(lag->upper_netdev, netdev_tmp) { |
|
if (!netif_is_ice(netdev_tmp)) |
|
continue; |
|
|
|
if (netdev_tmp && netdev_tmp != lag->netdev && |
|
lag->peer_netdev != netdev_tmp) { |
|
dev_hold(netdev_tmp); |
|
lag->peer_netdev = netdev_tmp; |
|
} |
|
} |
|
rcu_read_unlock(); |
|
|
|
if (bonding_info->slave.state) |
|
ice_lag_set_backup(lag); |
|
else |
|
ice_lag_set_primary(lag); |
|
|
|
lag_out: |
|
ice_display_lag_info(lag); |
|
} |
|
|
|
/** |
|
* ice_lag_link - handle LAG link event |
|
* @lag: LAG info struct |
|
* @info: info from the netdev notifier |
|
*/ |
|
static void |
|
ice_lag_link(struct ice_lag *lag, struct netdev_notifier_changeupper_info *info) |
|
{ |
|
struct net_device *netdev_tmp, *upper = info->upper_dev; |
|
struct ice_pf *pf = lag->pf; |
|
int peers = 0; |
|
|
|
if (lag->bonded) |
|
dev_warn(ice_pf_to_dev(pf), "%s Already part of a bond\n", |
|
netdev_name(lag->netdev)); |
|
|
|
rcu_read_lock(); |
|
for_each_netdev_in_bond_rcu(upper, netdev_tmp) |
|
peers++; |
|
rcu_read_unlock(); |
|
|
|
if (lag->upper_netdev != upper) { |
|
dev_hold(upper); |
|
lag->upper_netdev = upper; |
|
} |
|
|
|
ice_clear_sriov_cap(pf); |
|
ice_clear_rdma_cap(pf); |
|
|
|
lag->bonded = true; |
|
lag->role = ICE_LAG_UNSET; |
|
|
|
/* if this is the first element in an LAG mark as master */ |
|
lag->master = !!(peers == 1); |
|
} |
|
|
|
/** |
|
* ice_lag_unlink - handle unlink event |
|
* @lag: LAG info struct |
|
* @info: info from netdev notification |
|
*/ |
|
static void |
|
ice_lag_unlink(struct ice_lag *lag, |
|
struct netdev_notifier_changeupper_info *info) |
|
{ |
|
struct net_device *netdev_tmp, *upper = info->upper_dev; |
|
struct ice_pf *pf = lag->pf; |
|
bool found = false; |
|
|
|
if (!lag->bonded) { |
|
netdev_dbg(lag->netdev, "bonding unlink event on non-LAG netdev\n"); |
|
return; |
|
} |
|
|
|
/* determine if we are in the new LAG config or not */ |
|
rcu_read_lock(); |
|
for_each_netdev_in_bond_rcu(upper, netdev_tmp) { |
|
if (netdev_tmp == lag->netdev) { |
|
found = true; |
|
break; |
|
} |
|
} |
|
rcu_read_unlock(); |
|
|
|
if (found) |
|
return; |
|
|
|
if (lag->upper_netdev) { |
|
dev_put(lag->upper_netdev); |
|
lag->upper_netdev = NULL; |
|
} |
|
|
|
if (lag->peer_netdev) { |
|
dev_put(lag->peer_netdev); |
|
lag->peer_netdev = NULL; |
|
} |
|
|
|
ice_set_sriov_cap(pf); |
|
ice_set_rdma_cap(pf); |
|
lag->bonded = false; |
|
lag->role = ICE_LAG_NONE; |
|
} |
|
|
|
/** |
|
* ice_lag_changeupper_event - handle LAG changeupper event |
|
* @lag: LAG info struct |
|
* @ptr: opaque pointer data |
|
* |
|
* ptr is to be cast into netdev_notifier_changeupper_info |
|
*/ |
|
static void ice_lag_changeupper_event(struct ice_lag *lag, void *ptr) |
|
{ |
|
struct netdev_notifier_changeupper_info *info; |
|
struct net_device *netdev; |
|
|
|
info = ptr; |
|
netdev = netdev_notifier_info_to_dev(ptr); |
|
|
|
/* not for this netdev */ |
|
if (netdev != lag->netdev) |
|
return; |
|
|
|
if (!info->upper_dev) { |
|
netdev_dbg(netdev, "changeupper rcvd, but no upper defined\n"); |
|
return; |
|
} |
|
|
|
netdev_dbg(netdev, "bonding %s\n", info->linking ? "LINK" : "UNLINK"); |
|
|
|
if (!netif_is_lag_master(info->upper_dev)) { |
|
netdev_dbg(netdev, "changeupper rcvd, but not master. bail\n"); |
|
return; |
|
} |
|
|
|
if (info->linking) |
|
ice_lag_link(lag, info); |
|
else |
|
ice_lag_unlink(lag, info); |
|
|
|
ice_display_lag_info(lag); |
|
} |
|
|
|
/** |
|
* ice_lag_changelower_event - handle LAG changelower event |
|
* @lag: LAG info struct |
|
* @ptr: opaque data pointer |
|
* |
|
* ptr to be cast to netdev_notifier_changelowerstate_info |
|
*/ |
|
static void ice_lag_changelower_event(struct ice_lag *lag, void *ptr) |
|
{ |
|
struct net_device *netdev = netdev_notifier_info_to_dev(ptr); |
|
|
|
if (netdev != lag->netdev) |
|
return; |
|
|
|
netdev_dbg(netdev, "bonding info\n"); |
|
|
|
if (!netif_is_lag_port(netdev)) |
|
netdev_dbg(netdev, "CHANGELOWER rcvd, but netdev not in LAG. Bail\n"); |
|
} |
|
|
|
/** |
|
* ice_lag_event_handler - handle LAG events from netdev |
|
* @notif_blk: notifier block registered by this netdev |
|
* @event: event type |
|
* @ptr: opaque data containing notifier event |
|
*/ |
|
static int |
|
ice_lag_event_handler(struct notifier_block *notif_blk, unsigned long event, |
|
void *ptr) |
|
{ |
|
struct net_device *netdev = netdev_notifier_info_to_dev(ptr); |
|
struct ice_lag *lag; |
|
|
|
lag = container_of(notif_blk, struct ice_lag, notif_block); |
|
|
|
if (!lag->netdev) |
|
return NOTIFY_DONE; |
|
|
|
/* Check that the netdev is in the working namespace */ |
|
if (!net_eq(dev_net(netdev), &init_net)) |
|
return NOTIFY_DONE; |
|
|
|
switch (event) { |
|
case NETDEV_CHANGEUPPER: |
|
ice_lag_changeupper_event(lag, ptr); |
|
break; |
|
case NETDEV_CHANGELOWERSTATE: |
|
ice_lag_changelower_event(lag, ptr); |
|
break; |
|
case NETDEV_BONDING_INFO: |
|
ice_lag_info_event(lag, ptr); |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return NOTIFY_DONE; |
|
} |
|
|
|
/** |
|
* ice_register_lag_handler - register LAG handler on netdev |
|
* @lag: LAG struct |
|
*/ |
|
static int ice_register_lag_handler(struct ice_lag *lag) |
|
{ |
|
struct device *dev = ice_pf_to_dev(lag->pf); |
|
struct notifier_block *notif_blk; |
|
|
|
notif_blk = &lag->notif_block; |
|
|
|
if (!notif_blk->notifier_call) { |
|
notif_blk->notifier_call = ice_lag_event_handler; |
|
if (register_netdevice_notifier(notif_blk)) { |
|
notif_blk->notifier_call = NULL; |
|
dev_err(dev, "FAIL register LAG event handler!\n"); |
|
return -EINVAL; |
|
} |
|
dev_dbg(dev, "LAG event handler registered\n"); |
|
} |
|
return 0; |
|
} |
|
|
|
/** |
|
* ice_unregister_lag_handler - unregister LAG handler on netdev |
|
* @lag: LAG struct |
|
*/ |
|
static void ice_unregister_lag_handler(struct ice_lag *lag) |
|
{ |
|
struct device *dev = ice_pf_to_dev(lag->pf); |
|
struct notifier_block *notif_blk; |
|
|
|
notif_blk = &lag->notif_block; |
|
if (notif_blk->notifier_call) { |
|
unregister_netdevice_notifier(notif_blk); |
|
dev_dbg(dev, "LAG event handler unregistered\n"); |
|
} |
|
} |
|
|
|
/** |
|
* ice_init_lag - initialize support for LAG |
|
* @pf: PF struct |
|
* |
|
* Alloc memory for LAG structs and initialize the elements. |
|
* Memory will be freed in ice_deinit_lag |
|
*/ |
|
int ice_init_lag(struct ice_pf *pf) |
|
{ |
|
struct device *dev = ice_pf_to_dev(pf); |
|
struct ice_lag *lag; |
|
struct ice_vsi *vsi; |
|
int err; |
|
|
|
pf->lag = kzalloc(sizeof(*lag), GFP_KERNEL); |
|
if (!pf->lag) |
|
return -ENOMEM; |
|
lag = pf->lag; |
|
|
|
vsi = ice_get_main_vsi(pf); |
|
if (!vsi) { |
|
dev_err(dev, "couldn't get main vsi, link aggregation init fail\n"); |
|
err = -EIO; |
|
goto lag_error; |
|
} |
|
|
|
lag->pf = pf; |
|
lag->netdev = vsi->netdev; |
|
lag->role = ICE_LAG_NONE; |
|
lag->bonded = false; |
|
lag->peer_netdev = NULL; |
|
lag->upper_netdev = NULL; |
|
lag->notif_block.notifier_call = NULL; |
|
|
|
err = ice_register_lag_handler(lag); |
|
if (err) { |
|
dev_warn(dev, "INIT LAG: Failed to register event handler\n"); |
|
goto lag_error; |
|
} |
|
|
|
ice_display_lag_info(lag); |
|
|
|
dev_dbg(dev, "INIT LAG complete\n"); |
|
return 0; |
|
|
|
lag_error: |
|
kfree(lag); |
|
pf->lag = NULL; |
|
return err; |
|
} |
|
|
|
/** |
|
* ice_deinit_lag - Clean up LAG |
|
* @pf: PF struct |
|
* |
|
* Clean up kernel LAG info and free memory |
|
* This function is meant to only be called on driver remove/shutdown |
|
*/ |
|
void ice_deinit_lag(struct ice_pf *pf) |
|
{ |
|
struct ice_lag *lag; |
|
|
|
lag = pf->lag; |
|
|
|
if (!lag) |
|
return; |
|
|
|
if (lag->pf) |
|
ice_unregister_lag_handler(lag); |
|
|
|
if (lag->upper_netdev) |
|
dev_put(lag->upper_netdev); |
|
|
|
if (lag->peer_netdev) |
|
dev_put(lag->peer_netdev); |
|
|
|
kfree(lag); |
|
|
|
pf->lag = NULL; |
|
}
|
|
|