Add the necessary APIs to configure and control the LAG support on the DPAA2 switch object. - The dpsw_lag_set() function will be used to either verify that a LAG configuration can be support or to actually apply it in HW. - The dpsw_if_set_lag_state() will get used in the next patches to change the per port LAG state of a specific DPSW interface. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- .../net/ethernet/freescale/dpaa2/dpsw-cmd.h | 18 +++++- drivers/net/ethernet/freescale/dpaa2/dpsw.c | 57 +++++++++++++++++++ drivers/net/ethernet/freescale/dpaa2/dpsw.h | 20 +++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h index 397d55f2bd99..9a2055c64983 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h @@ -12,7 +12,7 @@ /* DPSW Version */ #define DPSW_VER_MAJOR 8 -#define DPSW_VER_MINOR 9 +#define DPSW_VER_MINOR 13 #define DPSW_CMD_BASE_VERSION 1 #define DPSW_CMD_VERSION_2 2 @@ -92,11 +92,14 @@ #define DPSW_CMDID_CTRL_IF_SET_POOLS DPSW_CMD_ID(0x0A1) #define DPSW_CMDID_CTRL_IF_ENABLE DPSW_CMD_ID(0x0A2) #define DPSW_CMDID_CTRL_IF_DISABLE DPSW_CMD_ID(0x0A3) +#define DPSW_CMDID_SET_LAG DPSW_CMD_V2(0x0A4) #define DPSW_CMDID_CTRL_IF_SET_QUEUE DPSW_CMD_ID(0x0A6) #define DPSW_CMDID_SET_EGRESS_FLOOD DPSW_CMD_ID(0x0AC) #define DPSW_CMDID_IF_SET_LEARNING_MODE DPSW_CMD_ID(0x0AD) +#define DPSW_CMDID_IF_SET_LAG_STATE DPSW_CMD_ID(0x0B0) + /* Macros for accessing command fields smaller than 1byte */ #define DPSW_MASK(field) \ GENMASK(DPSW_##field##_SHIFT + DPSW_##field##_SIZE - 1, \ @@ -552,5 +555,18 @@ struct dpsw_cmd_if_reflection { /* only 2 bits from the LSB */ u8 filter; }; + +struct dpsw_cmd_lag { + u8 group_id; + u8 num_ifs; + u8 pad[6]; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +struct dpsw_cmd_if_set_lag_state { + __le16 if_id; + u8 tx_enabled; +}; #pragma pack(pop) #endif /* __FSL_DPSW_CMD_H */ diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.c b/drivers/net/ethernet/freescale/dpaa2/dpsw.c index ab921d75deb2..c3951a9d6e0e 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.c @@ -1659,3 +1659,60 @@ int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, return mc_send_command(mc_io, &cmd); } + +/** + * dpsw_lag_set() - Set LAG configuration + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @cfg: pointer to LAG configuration + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg) +{ + struct fsl_mc_command cmd = { 0 }; + struct dpsw_cmd_lag *cmd_params; + int i = 0; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_SET_LAG, cmd_flags, token); + + cmd_params = (struct dpsw_cmd_lag *)cmd.params; + cmd_params->group_id = cfg->group_id; + cmd_params->num_ifs = cfg->num_ifs; + cmd_params->phase = cfg->phase; + + for (i = 0; i < cfg->num_ifs; i++) + cmd_params->if_id[i] = cfg->if_id[i]; + + return mc_send_command(mc_io, &cmd); +} + +/** + * dpsw_if_set_lag_state() - Change per port LAG state + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @if_id: ID of the switch interface + * @tx_enabled: Value of the per port LAG state + * - 0 if the interface will not be active as part of the LAG group + * - 1 if the interface will be active in the LAG group + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled) +{ + struct dpsw_cmd_if_set_lag_state *cmd_params; + struct fsl_mc_command cmd = { 0 }; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_IF_SET_LAG_STATE, + cmd_flags, token); + + cmd_params = (struct dpsw_cmd_if_set_lag_state *)cmd.params; + cmd_params->if_id = cpu_to_le16(if_id); + cmd_params->tx_enabled = tx_enabled; + + return mc_send_command(mc_io, &cmd); +} diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.h b/drivers/net/ethernet/freescale/dpaa2/dpsw.h index b90bd363f47a..d79021ef3474 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.h @@ -20,6 +20,8 @@ struct fsl_mc_io; #define DPSW_MAX_IF 64 +#define DPSW_MAX_LAG_IFS 8 + int dpsw_open(struct fsl_mc_io *mc_io, u32 cmd_flags, int dpsw_id, u16 *token); int dpsw_close(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token); @@ -788,4 +790,22 @@ int dpsw_if_add_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, u16 if_id, const struct dpsw_reflection_cfg *cfg); + +/* Link Aggregation Group configuration */ + +#define DPSW_LAG_SET_PHASE_APPLY 0 +#define DPSW_LAG_SET_PHASE_CHECK 1 + +struct dpsw_lag_cfg { + u8 group_id; + u8 num_ifs; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg); + +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled); #endif /* __FSL_DPSW_H */ -- 2.25.1 This patch adds the bulk of the changes needed in order to support offloading of an upper bond device. First of all, handling of the NETDEV_CHANGEUPPER and NETDEV_PRECHANGEUPPER events is extended so that the driver is capable to handle joining or leaving an upper bond device. All the restrictions around the LAG offload support are added in the newly added dpaa2_switch_pre_lag_join() function. The same events are extended to also detect if one of our upper bond devices changes its own upper device. In this case, on each lower device that is DPAA2 the corresponding dpaa2_switch_port_[pre]changeupper() function will be called. This will start the process of joining the same FDB as the one used by the bridge device. Setting the 'offload_fwd_mark' field on the skbs is also extended to be setup not only when the port is under a bridge but also under a bond device that is offloaded. Signed-off-by: Ioana Ciornei --- Changes in v2: - Extend dpaa2_switch_prechangeupper_sanity_checks() with netdev_walk_all_lower_dev() so that checks are done on all lower devices of a bridge, even for the lowers of a bridged bond. - Manage better the default VLAN on bond join - Clean-up the error path in dpaa2_switch_port_bond_join() - Call dpaa2_switch_port_bridge_leave() in case a port is leaving a bond which is also a bridged port - Update dpaa2_switch_port_bond_leave() so that in case of any failure the driver tries to cleanup the LAG offload configuration. - Call switchdev_bridge_port_unoffload() in a switch port is leaving a bridge bond device. --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 454 +++++++++++++++++- .../ethernet/freescale/dpaa2/dpaa2-switch.h | 14 +- 2 files changed, 461 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 52c1cb9cb7e0..88f52ac04c0a 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -51,6 +51,17 @@ dpaa2_switch_filter_block_get_unused(struct ethsw_core *ethsw) return NULL; } +static struct dpaa2_switch_lag * +dpaa2_switch_lag_get_unused(struct ethsw_core *ethsw) +{ + int i; + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + if (!ethsw->lags[i].in_use) + return ðsw->lags[i]; + return NULL; +} + static u16 dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, struct net_device *bridge_dev) { @@ -2157,15 +2168,30 @@ static int dpaa2_switch_prevent_bridging_with_8021q_upper(struct net_device *net return 0; } +static int dpaa2_switch_check_dpsw_instance(struct net_device *dev, + struct netdev_nested_priv *priv) +{ + struct ethsw_port_priv *port_priv = (struct ethsw_port_priv *)priv->data; + struct ethsw_port_priv *other_priv = netdev_priv(dev); + + if (!dpaa2_switch_port_dev_check(dev)) + return 0; + + if (other_priv->ethsw_data == port_priv->ethsw_data) + return 0; + + return 1; +} + static int dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - struct ethsw_port_priv *other_port_priv; - struct net_device *other_dev; - struct list_head *iter; + struct netdev_nested_priv data = { + .data = (void *)port_priv, + }; int err; if (!br_vlan_enabled(upper_dev)) { @@ -2180,6 +2206,70 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, return 0; } + err = netdev_walk_all_lower_dev(upper_dev, + dpaa2_switch_check_dpsw_instance, + &data); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Interface from a different DPSW is in the bridge already"); + return -EINVAL; + } + + return 0; +} + +static int dpaa2_switch_pre_lag_join(struct net_device *netdev, + struct net_device *upper_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv; + struct dpaa2_switch_lag *lag = NULL; + struct dpsw_lag_cfg cfg = {0}; + struct net_device *other_dev; + int i, num_ifs = 0, err; + struct list_head *iter; + + if (!(ethsw->features & ETHSW_FEATURE_LAG_OFFLOAD)) { + NL_SET_ERR_MSG_MOD(extack, + "LAG offload is supported only for DPSW >= v8.13"); + return -EOPNOTSUPP; + } + + if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { + NL_SET_ERR_MSG_MOD(extack, + "Can only offload LAG using hash TX type"); + return -EOPNOTSUPP; + } + + if (info->hash_type != NETDEV_LAG_HASH_L23) { + NL_SET_ERR_MSG_MOD(extack, "Can only offload L2+L3 Tx hash"); + return -EOPNOTSUPP; + } + + if (!dpaa2_switch_port_has_mac(port_priv)) { + NL_SET_ERR_MSG_MOD(extack, + "Only switch interfaces connected to MACs can be under a LAG"); + return -EINVAL; + } + + if (vlan_uses_dev(upper_dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot join a LAG upper that has a VLAN"); + return -EOPNOTSUPP; + } + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (!ethsw->lags[i].in_use) + continue; + if (ethsw->lags[i].bond_dev != upper_dev) + continue; + lag = ðsw->lags[i]; + break; + } + netdev_for_each_lower_dev(upper_dev, other_dev, iter) { if (!dpaa2_switch_port_dev_check(other_dev)) continue; @@ -2187,19 +2277,240 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, other_port_priv = netdev_priv(other_dev); if (other_port_priv->ethsw_data != port_priv->ethsw_data) { NL_SET_ERR_MSG_MOD(extack, - "Interface from a different DPSW is in the bridge already"); + "Interface from a different DPSW is in the bond already"); + return -EINVAL; + } + + cfg.if_id[num_ifs++] = other_port_priv->idx; + + if (num_ifs >= DPSW_MAX_LAG_IFS) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add more than 8 DPAA2 switch ports under the same bond"); return -EINVAL; } } + if (lag) { + cfg.group_id = lag->id; + cfg.if_id[num_ifs++] = port_priv->idx; + cfg.num_ifs = num_ifs; + cfg.phase = DPSW_LAG_SET_PHASE_CHECK; + + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot offload LAG configuration"); + return -EOPNOTSUPP; + } + } + return 0; } +static void dpaa2_switch_port_set_lag_group(struct ethsw_port_priv *port_priv, + struct net_device *bond_dev) +{ + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv = NULL; + struct dpaa2_switch_lag *lag = NULL; + struct net_device *other_dev; + struct list_head *iter; + + netdev_for_each_lower_dev(bond_dev, other_dev, iter) { + if (!dpaa2_switch_port_dev_check(other_dev)) + continue; + + other_port_priv = netdev_priv(other_dev); + if (!other_port_priv->lag) + continue; + + if (other_port_priv->lag->bond_dev == bond_dev) { + port_priv->lag = other_port_priv->lag; + return; + } + } + + /* This is the first interface to be added under a bond device. Find an + * unused LAG group. No need to check for NULL since there are the same + * amount of DPSW ports as LAG groups, meaning that each port can have + * its own LAG group. + */ + lag = dpaa2_switch_lag_get_unused(ethsw); + lag->in_use = true; + lag->bond_dev = bond_dev; + port_priv->lag = lag; +} + +static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, + struct ethsw_core *ethsw) +{ + struct dpaa2_switch_lag *lag = ðsw->lags[lag_id - 1]; + struct ethsw_port_priv *other_port_priv = NULL; + struct dpsw_lag_cfg cfg = {0}; + u8 num_ifs = 0; + int i; + + cfg.group_id = lag_id; + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + other_port_priv = ethsw->ports[i]; + + if (!other_port_priv) + continue; + if (!other_port_priv->lag) + continue; + if (other_port_priv->lag->bond_dev != bond_dev) + continue; + + /* No need to check against DPSW_MAX_LAG_IFS since this + * was done in the prechangeupper stage. The flow will + * not reach this point in case there are more DPAA2 + * switch ports under the same bond than we can accept. + */ + cfg.if_id[num_ifs++] = other_port_priv->idx; + } + + cfg.num_ifs = num_ifs; + + /* No more interfaces under this LAG group, mark it as not in use */ + if (!num_ifs) { + lag->bond_dev = NULL; + lag->in_use = false; + } + + return dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); +} + +static int dpaa2_switch_port_bond_join(struct net_device *netdev, + struct net_device *bond_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct dpaa2_switch_fdb *old_fdb = port_priv->fdb; + struct net_device *bridge_dev; + int err = 0; + u8 lag_id; + + /* Delete the default VLAN, we might change our FDB in this operation */ + err = dpaa2_switch_port_del_vlan(port_priv, DEFAULT_VLAN_ID); + if (err) + return err; + + /* Setup the egress flood policy (broadcast, unknown unicast) */ + dpaa2_switch_port_set_fdb(port_priv, bond_dev); + err = dpaa2_switch_fdb_set_egress_flood(ethsw, port_priv->fdb->fdb_id); + if (err) + goto err_egress_flood; + + /* Recreate the egress flood domain of the FDB that we just left. */ + err = dpaa2_switch_fdb_set_egress_flood(ethsw, old_fdb->fdb_id); + if (err) + goto err_egress_flood; + + /* Setup the port_priv->lag pointer for this switch port */ + dpaa2_switch_port_set_lag_group(port_priv, bond_dev); + + /* Create the LAG configuration and apply it in MC */ + lag_id = port_priv->lag->id; + err = dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); + if (err) + goto err_lag_cfg; + + err = dpaa2_switch_port_add_vlan(port_priv, DEFAULT_VLAN_ID, + BRIDGE_VLAN_INFO_UNTAGGED | + BRIDGE_VLAN_INFO_PVID); + if (err) + goto err_vlan_add; + + /* If the bond device is a switch port, then join the bridge as well */ + bridge_dev = netdev_master_upper_dev_get(bond_dev); + if (!bridge_dev || !netif_is_bridge_master(bridge_dev)) + return 0; + + err = dpaa2_switch_port_bridge_join(netdev, bridge_dev, extack); + if (err) + goto err_bridge_join; + + return err; + +err_bridge_join: + dpaa2_switch_port_del_vlan(port_priv, DEFAULT_VLAN_ID); +err_vlan_add: +err_lag_cfg: + port_priv->lag = NULL; + dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); +err_egress_flood: + dpaa2_switch_port_set_fdb(port_priv, NULL); + dpaa2_switch_port_add_vlan(port_priv, DEFAULT_VLAN_ID, + BRIDGE_VLAN_INFO_UNTAGGED | + BRIDGE_VLAN_INFO_PVID); + return err; +} + +static int dpaa2_switch_port_bond_leave(struct net_device *netdev, + struct net_device *bond_dev) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct dpaa2_switch_fdb *old_fdb = port_priv->fdb; + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct dpaa2_switch_lag *lag = port_priv->lag; + struct net_device *bridge_dev; + int err = 0; + + /* In case the bond is a bridge port, leave the upper bridge as well */ + bridge_dev = netdev_master_upper_dev_get(bond_dev); + if (bridge_dev && netif_is_bridge_master(bridge_dev)) { + err = dpaa2_switch_port_bridge_leave(netdev); + if (err) + goto lag_cleanup; + } + + /* Delete the default VLAN, we might change our FDB in this operation */ + err = dpaa2_switch_port_del_vlan(port_priv, DEFAULT_VLAN_ID); + if (err) + goto lag_cleanup; + + /* Setup the FDB for this port which is now standalone */ + dpaa2_switch_port_set_fdb(port_priv, NULL); + + /* Setup the egress flood policy (broadcast, unknown unicast). + * When the port is not under a bond, only the CTRL interface is part + * of the flooding domain besides the actual port. + */ + err = dpaa2_switch_fdb_set_egress_flood(ethsw, port_priv->fdb->fdb_id); + if (err) + goto lag_cleanup; + + /* Recreate the egress flood domain of the FDB that we just left. */ + err = dpaa2_switch_fdb_set_egress_flood(ethsw, old_fdb->fdb_id); + if (err) + goto lag_cleanup; + + /* Add the VLAN 1 as PVID when not under a bond. We need this since + * the dpaa2 switch interfaces are not capable to be VLAN unaware + */ + err = dpaa2_switch_port_add_vlan(port_priv, DEFAULT_VLAN_ID, + BRIDGE_VLAN_INFO_UNTAGGED | + BRIDGE_VLAN_INFO_PVID); + if (err) + goto lag_cleanup; + +lag_cleanup: + /* Recreate the LAG configuration for the LAG group that we left. In + * case any step failed, at least we free up a LAG resource. + */ + port_priv->lag = NULL; + dpaa2_switch_set_lag_cfg(bond_dev, lag->id, ethsw); + + return err; +} + static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, struct netdev_notifier_changeupper_info *info) { + struct net_device *upper_dev, *br; struct netlink_ext_ack *extack; - struct net_device *upper_dev; int err; if (!dpaa2_switch_port_dev_check(netdev)) @@ -2216,6 +2527,23 @@ static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, if (!info->linking) dpaa2_switch_port_pre_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (!info->linking && netif_is_bridge_port(upper_dev)) { + dpaa2_switch_port_pre_bridge_leave(netdev); + return 0; + } + + if (netif_is_bridge_port(upper_dev)) { + br = netdev_master_upper_dev_get(upper_dev); + err = dpaa2_switch_prechangeupper_sanity_checks(netdev, + br, + extack); + if (err) + return err; + } + + return dpaa2_switch_pre_lag_join(netdev, upper_dev, + info->upper_info, extack); } return 0; @@ -2240,6 +2568,80 @@ static int dpaa2_switch_port_changeupper(struct net_device *netdev, extack); else return dpaa2_switch_port_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (info->linking) + return dpaa2_switch_port_bond_join(netdev, upper_dev, + info->upper_info, + extack); + else + return dpaa2_switch_port_bond_leave(netdev, upper_dev); + } + + return 0; +} + +static int +dpaa2_switch_lag_prechangeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_prechangeupper(lower, info); + if (err) + return err; + } + + return err; +} + +static int +dpaa2_switch_lag_changeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_changeupper(lower, info); + if (err) + return err; + } + + return 0; +} + +static int +dpaa2_switch_port_changelowerstate(struct net_device *netdev, + struct netdev_lag_lower_state_info *linfo) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + int err; + + if (!port_priv->lag) + return 0; + + err = dpsw_if_set_lag_state(ethsw->mc_io, 0, ethsw->dpsw_handle, + port_priv->idx, linfo->tx_enabled ? 1 : 0); + if (err) { + netdev_err(netdev, "dpsw_if_set_lag_state() = %d\n", err); + return err; } return 0; @@ -2249,6 +2651,7 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + struct netdev_notifier_changelowerstate_info *info; int err = 0; switch (event) { @@ -2257,13 +2660,29 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_prechangeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; case NETDEV_CHANGEUPPER: err = dpaa2_switch_port_changeupper(netdev, ptr); if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_changeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; + case NETDEV_CHANGELOWERSTATE: + info = ptr; + if (!dpaa2_switch_port_dev_check(netdev)) + break; + + err = dpaa2_switch_port_changelowerstate(netdev, + info->lower_state_info); + return notifier_from_errno(err); } return NOTIFY_DONE; @@ -2500,8 +2919,11 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, skb->dev = netdev; skb->protocol = eth_type_trans(skb, skb->dev); - /* Setup the offload_fwd_mark only if the port is under a bridge */ + /* Setup the offload_fwd_mark only if the port is under a bridge + * or under a bond device that is offloaded. + */ skb->offload_fwd_mark = !!(port_priv->fdb->bridge_dev); + skb->offload_fwd_mark |= !!(port_priv->lag); netif_receive_skb(skb); @@ -2517,6 +2939,9 @@ static void dpaa2_switch_detect_features(struct ethsw_core *ethsw) if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 6)) ethsw->features |= ETHSW_FEATURE_MAC_ADDR; + + if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 13)) + ethsw->features |= ETHSW_FEATURE_LAG_OFFLOAD; } static int dpaa2_switch_setup_fqs(struct ethsw_core *ethsw) @@ -3301,6 +3726,7 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) kfree(ethsw->fdbs); kfree(ethsw->filter_blocks); kfree(ethsw->ports); + kfree(ethsw->lags); dpaa2_switch_teardown(sw_dev); @@ -3328,6 +3754,7 @@ static int dpaa2_switch_probe_port(struct ethsw_core *ethsw, port_priv = netdev_priv(port_netdev); port_priv->netdev = port_netdev; port_priv->ethsw_data = ethsw; + port_priv->lag = NULL; mutex_init(&port_priv->mac_lock); @@ -3435,6 +3862,19 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) goto err_free_fdbs; } + ethsw->lags = kcalloc(ethsw->sw_attr.num_ifs, sizeof(*ethsw->lags), + GFP_KERNEL); + if (!ethsw->lags) { + err = -ENOMEM; + goto err_free_filter; + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + ethsw->lags[i].bond_dev = NULL; + ethsw->lags[i].ethsw = ethsw; + ethsw->lags[i].id = i + 1; + ethsw->lags[i].in_use = 0; + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { err = dpaa2_switch_probe_port(ethsw, i); if (err) @@ -3481,6 +3921,8 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) err_free_netdev: for (i--; i >= 0; i--) dpaa2_switch_remove_port(ethsw, i); + kfree(ethsw->lags); +err_free_filter: kfree(ethsw->filter_blocks); err_free_fdbs: kfree(ethsw->fdbs); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index 42b3ca73f55d..56debbdefd13 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -41,7 +41,8 @@ #define ETHSW_MAX_FRAME_LENGTH (DPAA2_MFL - VLAN_ETH_HLEN - ETH_FCS_LEN) #define ETHSW_L2_MAX_FRM(mtu) ((mtu) + VLAN_ETH_HLEN + ETH_FCS_LEN) -#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_LAG_OFFLOAD BIT(1) /* Number of receive queues (one RX and one TX_CONF) */ #define DPAA2_SWITCH_RX_NUM_FQS 2 @@ -105,6 +106,13 @@ struct dpaa2_switch_fdb { bool in_use; }; +struct dpaa2_switch_lag { + struct ethsw_core *ethsw; + struct net_device *bond_dev; + bool in_use; + u8 id; +}; + struct dpaa2_switch_acl_entry { struct list_head list; u16 prio; @@ -163,6 +171,8 @@ struct ethsw_port_priv { struct dpaa2_mac *mac; /* Protects against changes to port_priv->mac */ struct mutex mac_lock; + + struct dpaa2_switch_lag *lag; }; /* Switch data */ @@ -190,6 +200,8 @@ struct ethsw_core { struct dpaa2_switch_fdb *fdbs; struct dpaa2_switch_filter_block *filter_blocks; u16 mirror_port; + + struct dpaa2_switch_lag *lags; }; static inline int dpaa2_switch_get_index(struct ethsw_core *ethsw, -- 2.25.1 Since there dpaa2_switch_port_set_fdb() never fails and its return value was never checked, change its prototype to return void. Also, instead of determining if the DPAA2 port is joining or leaving an upper based on the value of the 'bridge_dev' parameter, add the 'linking' parameter to explicitly specify the action. This will enable us to pass the upper device that we are joining/leaving in all possible cases. This will get used in the next patches to determine what kind of device the upper is: a bridge or a bond. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 88f52ac04c0a..91be5a12a006 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -62,18 +62,17 @@ dpaa2_switch_lag_get_unused(struct ethsw_core *ethsw) return NULL; } -static u16 dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, - struct net_device *bridge_dev) +static void dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, + struct net_device *upper_dev, + bool linking) { struct ethsw_port_priv *other_port_priv = NULL; struct dpaa2_switch_fdb *fdb; struct net_device *other_dev; struct list_head *iter; - /* If we leave a bridge (bridge_dev is NULL), find an unused - * FDB and use that. - */ - if (!bridge_dev) { + /* If we leave a bridge, find an unused FDB and use that. */ + if (!linking) { fdb = dpaa2_switch_fdb_get_unused(port_priv->ethsw_data); /* If there is no unused FDB, we must be the last port that @@ -83,13 +82,13 @@ static u16 dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, if (!fdb) { port_priv->fdb->bridge_dev = NULL; - return 0; + return; } port_priv->fdb = fdb; port_priv->fdb->in_use = true; port_priv->fdb->bridge_dev = NULL; - return 0; + return; } /* The below call to netdev_for_each_lower_dev() demands the RTNL lock @@ -101,7 +100,7 @@ static u16 dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, /* If part of a bridge, use the FDB of the first dpaa2 switch interface * to be present in that bridge */ - netdev_for_each_lower_dev(bridge_dev, other_dev, iter) { + netdev_for_each_lower_dev(upper_dev, other_dev, iter) { if (!dpaa2_switch_port_dev_check(other_dev)) continue; @@ -127,9 +126,7 @@ static u16 dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, } /* Keep track of the new upper bridge device */ - port_priv->fdb->bridge_dev = bridge_dev; - - return 0; + port_priv->fdb->bridge_dev = upper_dev; } static void dpaa2_switch_fdb_get_flood_cfg(struct ethsw_core *ethsw, u16 fdb_id, @@ -2040,7 +2037,7 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, if (err) return err; - dpaa2_switch_port_set_fdb(port_priv, upper_dev); + dpaa2_switch_port_set_fdb(port_priv, upper_dev, true); /* Inherit the initial bridge port learning state */ learn_ena = br_port_flag_is_set(netdev, BR_LEARNING); @@ -2066,7 +2063,7 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, err_switchdev_offload: err_egress_flood: - dpaa2_switch_port_set_fdb(port_priv, NULL); + dpaa2_switch_port_set_fdb(port_priv, upper_dev, false); return err; } @@ -2113,7 +2110,7 @@ static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) if (err) netdev_err(netdev, "Unable to clear RX VLANs from old FDB table, err (%d)\n", err); - dpaa2_switch_port_set_fdb(port_priv, NULL); + dpaa2_switch_port_set_fdb(port_priv, port_priv->fdb->bridge_dev, false); /* Restore all RX VLANs into the new FDB table that we just joined */ err = vlan_for_each(netdev, dpaa2_switch_port_restore_rxvlan, netdev); @@ -2398,7 +2395,7 @@ static int dpaa2_switch_port_bond_join(struct net_device *netdev, return err; /* Setup the egress flood policy (broadcast, unknown unicast) */ - dpaa2_switch_port_set_fdb(port_priv, bond_dev); + dpaa2_switch_port_set_fdb(port_priv, bond_dev, true); err = dpaa2_switch_fdb_set_egress_flood(ethsw, port_priv->fdb->fdb_id); if (err) goto err_egress_flood; @@ -2441,7 +2438,7 @@ static int dpaa2_switch_port_bond_join(struct net_device *netdev, port_priv->lag = NULL; dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); err_egress_flood: - dpaa2_switch_port_set_fdb(port_priv, NULL); + dpaa2_switch_port_set_fdb(port_priv, bond_dev, false); dpaa2_switch_port_add_vlan(port_priv, DEFAULT_VLAN_ID, BRIDGE_VLAN_INFO_UNTAGGED | BRIDGE_VLAN_INFO_PVID); @@ -2472,7 +2469,7 @@ static int dpaa2_switch_port_bond_leave(struct net_device *netdev, goto lag_cleanup; /* Setup the FDB for this port which is now standalone */ - dpaa2_switch_port_set_fdb(port_priv, NULL); + dpaa2_switch_port_set_fdb(port_priv, bond_dev, false); /* Setup the egress flood policy (broadcast, unknown unicast). * When the port is not under a bond, only the CTRL interface is part -- 2.25.1 The dpaa2_switch_port_set_fdb() function is responsible with determining what FDB should be used by a port as a consequence of changing its upper device. This patch extends the function to also cover the circumstances in which a DPAA2 switch port offloads a bond device. This will allow us, for example, to setup the same FDB on all DPAA2 switch ports which are under the same bridge, even though not directly but rather through an upper bond device which is bridged. How the function does this is by first determining a DPAA2 port is already under the same bridge and if so, choosing its FDB. To cover the entire hierarchy in depth, we add an extra walk through all the lowers of a bridged bond device. When leaving an upper device, the DPAA2 switch port must find a new FDB to use. If before it just searched for an unused FDB to go along with its new standalone status, now it first checks if the port is still part of a LAG and then uses the FDB of any port that already left the same bridge. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 102 +++++++++++++----- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 91be5a12a006..8af8b1f769d4 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -66,28 +66,59 @@ static void dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, struct net_device *upper_dev, bool linking) { + struct ethsw_core *ethsw = port_priv->ethsw_data; struct ethsw_port_priv *other_port_priv = NULL; - struct dpaa2_switch_fdb *fdb; - struct net_device *other_dev; - struct list_head *iter; + struct net_device *other_dev, *other_dev2; + u16 fdb_id_old = port_priv->fdb->fdb_id; + struct dpaa2_switch_fdb *fdb = NULL; + struct list_head *iter, *iter2; + int i; - /* If we leave a bridge, find an unused FDB and use that. */ + /* If we leave a an upper device, be it a bond or a bridge, find an + * unused FDB and use that. + */ if (!linking) { - fdb = dpaa2_switch_fdb_get_unused(port_priv->ethsw_data); - - /* If there is no unused FDB, we must be the last port that - * leaves the last bridge, all the others are standalone. We - * can just keep the FDB that we already have. + /* This port leaves a bridge, but it's still under a bond. + * Search for the first port under the same bond which already + * left the bridge. */ + if (netif_is_bridge_master(upper_dev) && port_priv->lag) { + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + other_port_priv = ethsw->ports[i]; + if (!other_port_priv) + continue; + + if (other_port_priv == port_priv) + continue; + + /* Found a port which is under the same bond + * device but already left the bridge. Use + * this port's FDB. + */ + if (other_port_priv->lag == port_priv->lag && + other_port_priv->fdb->fdb_id != fdb_id_old) { + fdb = other_port_priv->fdb; + break; + } + } + } - if (!fdb) { - port_priv->fdb->bridge_dev = NULL; - return; + /* Try to get hold of an unused FDB to use */ + if (!fdb) + fdb = dpaa2_switch_fdb_get_unused(port_priv->ethsw_data); + + if (fdb) { + port_priv->fdb = fdb; + port_priv->fdb->in_use = true; } - port_priv->fdb = fdb; - port_priv->fdb->in_use = true; - port_priv->fdb->bridge_dev = NULL; + if (netif_is_bridge_master(upper_dev)) + port_priv->fdb->bridge_dev = NULL; + + /* In case all FDBs are already in use, we must be the last + * port that becomes standalone. We can just keep the FDB that + * we already have. Nothing more to do in this case. + */ return; } @@ -97,18 +128,40 @@ static void dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, */ ASSERT_RTNL(); - /* If part of a bridge, use the FDB of the first dpaa2 switch interface - * to be present in that bridge + /* In case we are joining an upper device, be it a bridge device or a + * bond device, we will use the FDB of the first DPAA2 switch interface + * that is already present under the same upper device. For this to + * happen we have to extend our search so that we can find any DPAA2 + * interface that is a lower of a bond bridged port */ + other_port_priv = NULL; netdev_for_each_lower_dev(upper_dev, other_dev, iter) { - if (!dpaa2_switch_port_dev_check(other_dev)) - continue; + if (netif_is_lag_master(other_dev)) { + /* Search through all the lowers of the bridged lag */ + netdev_for_each_lower_dev(other_dev, other_dev2, iter2) { + if (!dpaa2_switch_port_dev_check(other_dev2)) + continue; + if (other_dev2 == port_priv->netdev) + continue; + + /* Skip the port if it's the same upper */ + other_port_priv = netdev_priv(other_dev2); + if (other_port_priv->lag == port_priv->lag) { + other_port_priv = NULL; + continue; + } + break; + } - if (other_dev == port_priv->netdev) - continue; + if (other_port_priv) + break; + } else if (dpaa2_switch_port_dev_check(other_dev)) { + if (other_dev == port_priv->netdev) + continue; - other_port_priv = netdev_priv(other_dev); - break; + other_port_priv = netdev_priv(other_dev); + break; + } } /* The current port is about to change its FDB to the one used by the @@ -126,7 +179,8 @@ static void dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, } /* Keep track of the new upper bridge device */ - port_priv->fdb->bridge_dev = upper_dev; + if (netif_is_bridge_master(upper_dev)) + port_priv->fdb->bridge_dev = upper_dev; } static void dpaa2_switch_fdb_get_flood_cfg(struct ethsw_core *ethsw, u16 fdb_id, -- 2.25.1 With the addition of offloading support for upper bond devices we have to let the switchdev framework know if a specific bridge port is offloaded or not, even if that port is bond device. For this to happen, create the dpaa2_switch_port_to_bridge_port function which will determine the bridge port corresponding to a particulat DPAA2 switch interface and use it in the switchdev_bridge_port_offload call. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 8af8b1f769d4..aa6a6731dafa 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2076,6 +2076,18 @@ static int dpaa2_switch_port_attr_set_event(struct net_device *netdev, return notifier_from_errno(err); } +static struct net_device * +dpaa2_switch_port_to_bridge_port(struct ethsw_port_priv *port_priv) +{ + if (!port_priv->fdb->bridge_dev) + return NULL; + + if (port_priv->lag) + return port_priv->lag->bond_dev; + + return port_priv->netdev; +} + static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) @@ -2083,6 +2095,7 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct ethsw_port_priv *port_priv = netdev_priv(netdev); struct dpaa2_switch_fdb *old_fdb = port_priv->fdb; struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brport_dev; bool learn_ena; int err; @@ -2094,7 +2107,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, dpaa2_switch_port_set_fdb(port_priv, upper_dev, true); /* Inherit the initial bridge port learning state */ - learn_ena = br_port_flag_is_set(netdev, BR_LEARNING); + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + learn_ena = br_port_flag_is_set(brport_dev, BR_LEARNING); err = dpaa2_switch_port_set_learning(port_priv, learn_ena); port_priv->learn_ena = learn_ena; @@ -2108,7 +2122,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, if (err) goto err_egress_flood; - err = switchdev_bridge_port_offload(netdev, netdev, NULL, + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + err = switchdev_bridge_port_offload(brport_dev, netdev, NULL, NULL, NULL, false, extack); if (err) goto err_switchdev_offload; @@ -2143,7 +2158,12 @@ static int dpaa2_switch_port_restore_rxvlan(struct net_device *vdev, int vid, vo static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) { - switchdev_bridge_port_unoffload(netdev, NULL, NULL, NULL); + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct net_device *brport_dev; + + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + + switchdev_bridge_port_unoffload(brport_dev, NULL, NULL, NULL); } static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) -- 2.25.1 Create a separate dpaa2_switch_port_fdb_event() function that will only handle the FDB related events. With this change, the dpaa2_switch_port_event() notifier handler can be written in a way that it's easier to follow. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index aa6a6731dafa..2d03d7420d0c 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2809,9 +2809,8 @@ static void dpaa2_switch_event_work(struct work_struct *work) dev_put(dev); } -/* Called under rcu_read_lock() */ -static int dpaa2_switch_port_event(struct notifier_block *nb, - unsigned long event, void *ptr) +static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, + unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct ethsw_port_priv *port_priv = netdev_priv(dev); @@ -2819,9 +2818,6 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, struct switchdev_notifier_fdb_info *fdb_info = ptr; struct ethsw_core *ethsw = port_priv->ethsw_data; - if (event == SWITCHDEV_PORT_ATTR_SET) - return dpaa2_switch_port_attr_set_event(dev, ptr); - if (!dpaa2_switch_port_dev_check(dev)) return NOTIFY_DONE; @@ -2862,6 +2858,23 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, return NOTIFY_BAD; } +/* Called under rcu_read_lock() */ +static int dpaa2_switch_port_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + + switch (event) { + case SWITCHDEV_PORT_ATTR_SET: + return dpaa2_switch_port_attr_set_event(dev, ptr); + case SWITCHDEV_FDB_ADD_TO_DEVICE: + case SWITCHDEV_FDB_DEL_TO_DEVICE: + return dpaa2_switch_port_fdb_event(nb, event, ptr); + default: + return NOTIFY_DONE; + } +} + static int dpaa2_switch_port_obj_event(unsigned long event, struct net_device *netdev, struct switchdev_notifier_port_obj_info *port_obj_info) -- 2.25.1 Instead of waiting until the last moment to check if an FDB entry should be added to HW, move the check earlier (before even scheduling the work item) so that we don't just waste time. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 2d03d7420d0c..f910a07095be 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2779,8 +2779,6 @@ static void dpaa2_switch_event_work(struct work_struct *work) switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; if (is_unicast_ether_addr(fdb_info->addr)) err = dpaa2_switch_port_fdb_add_uc(netdev_priv(dev), fdb_info->addr); @@ -2794,8 +2792,6 @@ static void dpaa2_switch_event_work(struct work_struct *work) &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; if (is_unicast_ether_addr(fdb_info->addr)) dpaa2_switch_port_fdb_del_uc(netdev_priv(dev), fdb_info->addr); else @@ -2821,6 +2817,9 @@ static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, if (!dpaa2_switch_port_dev_check(dev)) return NOTIFY_DONE; + if (!fdb_info->added_by_user || fdb_info->is_local) + return NOTIFY_DONE; + switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) return NOTIFY_BAD; -- 2.25.1 This patch consolidates the unicast and multicast management by creating two new functions - dpaa2_switch_port_fdb_[add|del]() - which can be used for either uc or mc addresses. Having this common entrypoint for both types of addresses will help us in the next patches to streamline the same addresses but on LAG ports. Signed-off-by: Ioana Ciornei --- Changes in v2: - The rollback in dpaa2_switch_port_mdb_add() uses the newly introduced dpaa2_switch_port_fdb_del() helper instead of the _mc counterpart. --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index f910a07095be..f8aa65463386 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -551,6 +551,28 @@ static int dpaa2_switch_port_fdb_del_mc(struct ethsw_port_priv *port_priv, return err; } +static int dpaa2_switch_port_fdb_add(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + int err; + + if (is_unicast_ether_addr(addr)) + err = dpaa2_switch_port_fdb_add_uc(port_priv, addr); + else + err = dpaa2_switch_port_fdb_add_mc(port_priv, addr); + + return err; +} + +static int dpaa2_switch_port_fdb_del(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + if (is_unicast_ether_addr(addr)) + return dpaa2_switch_port_fdb_del_uc(port_priv, addr); + else + return dpaa2_switch_port_fdb_del_mc(port_priv, addr); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1915,14 +1937,14 @@ static int dpaa2_switch_port_mdb_add(struct net_device *netdev, if (dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) return -EEXIST; - err = dpaa2_switch_port_fdb_add_mc(port_priv, mdb->addr); + err = dpaa2_switch_port_fdb_add(port_priv, mdb->addr); if (err) return err; err = dev_mc_add(netdev, mdb->addr); if (err) { netdev_err(netdev, "dev_mc_add err %d\n", err); - dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); + dpaa2_switch_port_fdb_del(port_priv, mdb->addr); } return err; @@ -2033,7 +2055,7 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, if (!dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) return -ENOENT; - err = dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); + err = dpaa2_switch_port_fdb_del(port_priv, mdb->addr); if (err) return err; @@ -2779,12 +2801,8 @@ static void dpaa2_switch_event_work(struct work_struct *work) switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - if (is_unicast_ether_addr(fdb_info->addr)) - err = dpaa2_switch_port_fdb_add_uc(netdev_priv(dev), - fdb_info->addr); - else - err = dpaa2_switch_port_fdb_add_mc(netdev_priv(dev), - fdb_info->addr); + err = dpaa2_switch_port_fdb_add(netdev_priv(dev), + fdb_info->addr); if (err) break; fdb_info->offloaded = true; @@ -2792,10 +2810,7 @@ static void dpaa2_switch_event_work(struct work_struct *work) &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - if (is_unicast_ether_addr(fdb_info->addr)) - dpaa2_switch_port_fdb_del_uc(netdev_priv(dev), fdb_info->addr); - else - dpaa2_switch_port_fdb_del_mc(netdev_priv(dev), fdb_info->addr); + dpaa2_switch_port_fdb_del(netdev_priv(dev), fdb_info->addr); break; } -- 2.25.1 This patch adds support for offloading FDB entries added on upper bond devices. First of all, the call to switchdev_bridge_port_offload() is updated so that the notifier blocks needed for FDB events replay are available to the bridge core. Using switchdev_handle_*() helpers is also necessary because each FDB event needs to be fanned out to any DPAA2 switch lower device. This triggers another change in the return type used by the dpaa2_switch_port_fdb_event() - from notifier types to regular errno types. Handling of the SWITCHDEV_FDB_ADD_TO_DEVICE/SWITCHDEV_FDB_DEL_TO_DEVICE events is updated so that the newly dpaa2_switch_lag_fdb_add() / dpaa2_switch_lag_fdb_del() functions are called anytime a port is under a bond device. This will allow us to manage refcounting on FDB entries which are added on the upper bond devices. The DPAA2 switch uses shared-VLAN learning which means that the vid parameter is not used when adding an FDB entry to HW. The current behavior when dealing with FDB entries with the same MAC address but different VLANs is to add the entry to HW every time while removal will get done on the first 'bridge fdb del' command issued by the user. The same behavior is kept also for FDBs added on bond devices by keeping the refcount on the {vid, addr} pair while the HW operation disregards entirely the vid parameter. Signed-off-by: Ioana Ciornei --- Changes in v2: - Update dpaa2_switch_foreign_dev_check() so that we check if between the switch port and the foreign net_device is an offloaded path. Before this change we also checked if the foreign_dev was offloaded or not by the switch port. - Update the switchdev_bridge_port_unoffload() by passing it the proper context and the notifier blocks. - Add dev_hold() and dev_put() calls for orig_dev --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 206 ++++++++++++++++-- .../ethernet/freescale/dpaa2/dpaa2-switch.h | 22 ++ 2 files changed, 208 insertions(+), 20 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index f8aa65463386..46c148a8aec3 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -25,6 +25,9 @@ #define DEFAULT_VLAN_ID 1 +static struct notifier_block dpaa2_switch_port_switchdev_nb; +static struct notifier_block dpaa2_switch_port_switchdev_blocking_nb; + static u16 dpaa2_switch_port_get_fdb_id(struct ethsw_port_priv *port_priv) { return port_priv->fdb->fdb_id; @@ -62,6 +65,25 @@ dpaa2_switch_lag_get_unused(struct ethsw_core *ethsw) return NULL; } +static struct ethsw_port_priv * +dpaa2_switch_lag_get_primary(struct dpaa2_switch_lag *lag) +{ + struct ethsw_core *ethsw = lag->ethsw; + struct ethsw_port_priv *port_priv; + int i; + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + port_priv = ethsw->ports[i]; + if (!port_priv) + continue; + + if (port_priv->lag == lag) + return port_priv; + } + + return NULL; +} + static void dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, struct net_device *upper_dev, bool linking) @@ -573,6 +595,91 @@ static int dpaa2_switch_port_fdb_del(struct ethsw_port_priv *port_priv, return dpaa2_switch_port_fdb_del_mc(port_priv, addr); } +static struct dpaa2_mac_addr * +dpaa2_switch_mac_addr_find(struct list_head *addr_list, + const unsigned char *addr, u16 vid) +{ + struct dpaa2_mac_addr *a; + + list_for_each_entry(a, addr_list, list) + if (ether_addr_equal(a->addr, addr) && a->vid == vid) + return a; + + return NULL; +} + +static int dpaa2_switch_lag_fdb_add(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv; + struct dpaa2_mac_addr *a; + int err = 0; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (a) { + refcount_inc(&a->refcount); + goto out; + } + + port_priv = dpaa2_switch_lag_get_primary(lag); + if (!port_priv) { + err = -ENOENT; + goto out; + } + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) { + err = -ENOMEM; + goto out; + } + + err = dpaa2_switch_port_fdb_add(port_priv, addr); + if (err) { + kfree(a); + goto out; + } + + ether_addr_copy(a->addr, addr); + a->vid = vid; + refcount_set(&a->refcount, 1); + list_add_tail(&a->list, &lag->fdbs); + +out: + mutex_unlock(&lag->fdb_lock); + + return err; +} + +static void dpaa2_switch_lag_fdb_del(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv; + struct dpaa2_mac_addr *a; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (!a) + goto out; + + port_priv = dpaa2_switch_lag_get_primary(lag); + if (!port_priv) + goto out; + + if (!refcount_dec_and_test(&a->refcount)) + goto out; + + dpaa2_switch_port_fdb_del(port_priv, addr); + + list_del(&a->list); + kfree(a); + +out: + mutex_unlock(&lag->fdb_lock); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1520,6 +1627,32 @@ bool dpaa2_switch_port_dev_check(const struct net_device *netdev) return netdev->netdev_ops == &dpaa2_switch_port_ops; } +static bool dpaa2_switch_foreign_dev_check(const struct net_device *dev, + const struct net_device *foreign_dev) +{ + struct ethsw_port_priv *port_priv = netdev_priv(dev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port; + int i; + + if (netif_is_bridge_master(foreign_dev)) + if (port_priv->fdb->bridge_dev == foreign_dev) + return false; + + if (netif_is_bridge_port(foreign_dev)) { + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + other_port = ethsw->ports[i]; + + if (!other_port || !other_port->lag) + continue; + if (other_port->lag->bond_dev == foreign_dev) + return false; + } + } + + return true; +} + static int dpaa2_switch_port_connect_mac(struct ethsw_port_priv *port_priv) { struct fsl_mc_device *dpsw_port_dev, *dpmac_dev; @@ -2145,8 +2278,10 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, goto err_egress_flood; brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); - err = switchdev_bridge_port_offload(brport_dev, netdev, NULL, - NULL, NULL, false, extack); + err = switchdev_bridge_port_offload(brport_dev, netdev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb, + false, extack); if (err) goto err_switchdev_offload; @@ -2185,7 +2320,9 @@ static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); - switchdev_bridge_port_unoffload(brport_dev, NULL, NULL, NULL); + switchdev_bridge_port_unoffload(brport_dev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb); } static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) @@ -2785,14 +2922,18 @@ struct ethsw_switchdev_event_work { struct work_struct work; struct switchdev_notifier_fdb_info fdb_info; struct net_device *dev; + struct net_device *orig_dev; unsigned long event; + u16 vid; }; static void dpaa2_switch_event_work(struct work_struct *work) { struct ethsw_switchdev_event_work *switchdev_work = container_of(work, struct ethsw_switchdev_event_work, work); + struct net_device *orig_dev = switchdev_work->orig_dev; struct net_device *dev = switchdev_work->dev; + struct ethsw_port_priv *port_priv = netdev_priv(dev); struct switchdev_notifier_fdb_info *fdb_info; int err; @@ -2801,16 +2942,25 @@ static void dpaa2_switch_event_work(struct work_struct *work) switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - err = dpaa2_switch_port_fdb_add(netdev_priv(dev), - fdb_info->addr); + if (port_priv->lag) + err = dpaa2_switch_lag_fdb_add(port_priv->lag, + fdb_info->addr, + switchdev_work->vid); + else + err = dpaa2_switch_port_fdb_add(netdev_priv(dev), + fdb_info->addr); if (err) break; fdb_info->offloaded = true; - call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev, + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, orig_dev, &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - dpaa2_switch_port_fdb_del(netdev_priv(dev), fdb_info->addr); + if (port_priv->lag) + dpaa2_switch_lag_fdb_del(port_priv->lag, fdb_info->addr, + switchdev_work->vid); + else + dpaa2_switch_port_fdb_del(port_priv, fdb_info->addr); break; } @@ -2818,35 +2968,43 @@ static void dpaa2_switch_event_work(struct work_struct *work) kfree(switchdev_work->fdb_info.addr); kfree(switchdev_work); dev_put(dev); + dev_put(orig_dev); } -static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, - unsigned long event, void *ptr) +static int +dpaa2_switch_port_fdb_event(struct net_device *dev, + struct net_device *orig_dev, + unsigned long event, const void *ctx, + const struct switchdev_notifier_fdb_info *fdb_info) { - struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct ethsw_port_priv *port_priv = netdev_priv(dev); struct ethsw_switchdev_event_work *switchdev_work; - struct switchdev_notifier_fdb_info *fdb_info = ptr; struct ethsw_core *ethsw = port_priv->ethsw_data; - if (!dpaa2_switch_port_dev_check(dev)) - return NOTIFY_DONE; + if (ctx && ctx != port_priv) + return 0; + + /* For the moment, do nothing with entries towards foreign devices */ + if (dpaa2_switch_foreign_dev_check(dev, orig_dev)) + return 0; if (!fdb_info->added_by_user || fdb_info->is_local) - return NOTIFY_DONE; + return 0; switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) - return NOTIFY_BAD; + return -ENOMEM; INIT_WORK(&switchdev_work->work, dpaa2_switch_event_work); switchdev_work->dev = dev; switchdev_work->event = event; + switchdev_work->orig_dev = orig_dev; + switchdev_work->vid = fdb_info->vid; switch (event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - memcpy(&switchdev_work->fdb_info, ptr, + memcpy(&switchdev_work->fdb_info, fdb_info, sizeof(switchdev_work->fdb_info)); switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); if (!switchdev_work->fdb_info.addr) @@ -2857,19 +3015,20 @@ static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, /* Take a reference on the device to avoid being freed. */ dev_hold(dev); + dev_hold(orig_dev); break; default: kfree(switchdev_work); - return NOTIFY_DONE; + return 0; } queue_work(ethsw->workqueue, &switchdev_work->work); - return NOTIFY_DONE; + return 0; err_addr_alloc: kfree(switchdev_work); - return NOTIFY_BAD; + return -ENOMEM; } /* Called under rcu_read_lock() */ @@ -2877,13 +3036,18 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { case SWITCHDEV_PORT_ATTR_SET: return dpaa2_switch_port_attr_set_event(dev, ptr); case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - return dpaa2_switch_port_fdb_event(nb, event, ptr); + err = switchdev_handle_fdb_event_to_device(dev, event, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_foreign_dev_check, + dpaa2_switch_port_fdb_event); + return notifier_from_errno(err); default: return NOTIFY_DONE; } @@ -3971,6 +4135,8 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) ethsw->lags[i].ethsw = ethsw; ethsw->lags[i].id = i + 1; ethsw->lags[i].in_use = 0; + mutex_init(ðsw->lags[i].fdb_lock); + INIT_LIST_HEAD(ðsw->lags[i].fdbs); } for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index 56debbdefd13..96d780980d77 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -100,6 +100,13 @@ struct dpaa2_switch_fq { u32 fqid; }; +struct dpaa2_mac_addr { + unsigned char addr[ETH_ALEN]; + u16 vid; + refcount_t refcount; + struct list_head list; +}; + struct dpaa2_switch_fdb { struct net_device *bridge_dev; u16 fdb_id; @@ -111,6 +118,9 @@ struct dpaa2_switch_lag { struct net_device *bond_dev; bool in_use; u8 id; + /* Protects the list of fdbs installed on this LAG */ + struct mutex fdb_lock; + struct list_head fdbs; }; struct dpaa2_switch_acl_entry { @@ -286,4 +296,16 @@ int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block, int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block, struct ethsw_port_priv *port_priv); + +static inline bool +dpaa2_switch_port_offloads_bridge_port(struct ethsw_port_priv *port_priv, + const struct net_device *dev) +{ + if (port_priv->lag && port_priv->lag->bond_dev == dev) + return true; + if (port_priv->netdev == dev) + return true; + return false; +} + #endif /* __ETHSW_H */ -- 2.25.1 This patch adds support for offloading port objects, VLANs and MDBs, added on upper bond devices. First of all, the use of the switchdev_handle_*() replication helpers is introduced for the SWITCHDEV_PORT_OBJ_ADD/SWITCHDEV_PORT_OBJ_DEL events. With this change, setting up the 'port_obj_info->handled = true' is not needed anymore since it's now handled by the new helpers. In the DPAA2 architecture, there is no difference in adding a FDB or MDB which points towards a LAG port. Unlike other architectures, we do not need to populate all the possible destinations which are under the LAG, we only have to specify a single queueing destination (QDID) which represents the LAG. This all means that handling of MDBs in bond devices needs to have refcount mechanism as with the FDBs. This mechanism is triggered by calling the dpaa2_switch_lag_fdb_add() / dpaa2_switch_lag_fdb_del() functions which were added in the previous patch. Signed-off-by: Ioana Ciornei --- Changes in v2: - In case dev_mc_add() fails, remove the MDB address from HW with the proper function, dpaa2_switch_lag_fdb_del() or dpaa2_switch_port_fdb_del(), depending on the LAG offload state. --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 72 +++++++++++-------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 46c148a8aec3..e8c00a269b98 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2070,24 +2070,40 @@ static int dpaa2_switch_port_mdb_add(struct net_device *netdev, if (dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) return -EEXIST; - err = dpaa2_switch_port_fdb_add(port_priv, mdb->addr); + if (port_priv->lag) + err = dpaa2_switch_lag_fdb_add(port_priv->lag, mdb->addr, + mdb->vid); + else + err = dpaa2_switch_port_fdb_add(port_priv, mdb->addr); if (err) return err; err = dev_mc_add(netdev, mdb->addr); if (err) { netdev_err(netdev, "dev_mc_add err %d\n", err); - dpaa2_switch_port_fdb_del(port_priv, mdb->addr); + if (port_priv->lag) + dpaa2_switch_lag_fdb_del(port_priv->lag, mdb->addr, + mdb->vid); + else + dpaa2_switch_port_fdb_del(port_priv, mdb->addr); } return err; } -static int dpaa2_switch_port_obj_add(struct net_device *netdev, - const struct switchdev_obj *obj) +static int dpaa2_switch_port_obj_add(struct net_device *netdev, const void *ctx, + const struct switchdev_obj *obj, + struct netlink_ext_ack *extack) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_add(netdev, @@ -2188,9 +2204,10 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, if (!dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) return -ENOENT; - err = dpaa2_switch_port_fdb_del(port_priv, mdb->addr); - if (err) - return err; + if (port_priv->lag) + dpaa2_switch_lag_fdb_del(port_priv->lag, mdb->addr, mdb->vid); + else + dpaa2_switch_port_fdb_del(port_priv, mdb->addr); err = dev_mc_del(netdev, mdb->addr); if (err) { @@ -2201,11 +2218,18 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, return err; } -static int dpaa2_switch_port_obj_del(struct net_device *netdev, +static int dpaa2_switch_port_obj_del(struct net_device *netdev, const void *ctx, const struct switchdev_obj *obj) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_del(netdev, SWITCHDEV_OBJ_PORT_VLAN(obj)); @@ -3053,37 +3077,23 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, } } -static int dpaa2_switch_port_obj_event(unsigned long event, - struct net_device *netdev, - struct switchdev_notifier_port_obj_info *port_obj_info) -{ - int err = -EOPNOTSUPP; - - if (!dpaa2_switch_port_dev_check(netdev)) - return NOTIFY_DONE; - - switch (event) { - case SWITCHDEV_PORT_OBJ_ADD: - err = dpaa2_switch_port_obj_add(netdev, port_obj_info->obj); - break; - case SWITCHDEV_PORT_OBJ_DEL: - err = dpaa2_switch_port_obj_del(netdev, port_obj_info->obj); - break; - } - - port_obj_info->handled = true; - return notifier_from_errno(err); -} - static int dpaa2_switch_port_blocking_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { case SWITCHDEV_PORT_OBJ_ADD: + err = switchdev_handle_port_obj_add(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_add); + return notifier_from_errno(err); case SWITCHDEV_PORT_OBJ_DEL: - return dpaa2_switch_port_obj_event(event, dev, ptr); + err = switchdev_handle_port_obj_del(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_del); + return notifier_from_errno(err); case SWITCHDEV_PORT_ATTR_SET: return dpaa2_switch_port_attr_set_event(dev, ptr); } -- 2.25.1 Do not trap only STP frames to the control interface but rather trap all link local reserved addresses. This will still be done by looking at the destination MAC address but keeping in mind to not take into account the last byte. This change will benefit LACP frames which now will reach the control interface. While at it, change the prototype of the dpaa2_switch_port_trap_mac_addr() function so that we directly pass a 'const u8 *' so that it matches the ether_addr_copy() used. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index e8c00a269b98..98cf404d4ac8 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -3848,17 +3848,15 @@ static int dpaa2_switch_init(struct fsl_mc_device *sw_dev) return err; } -/* Add an ACL to redirect frames with specific destination MAC address to - * control interface - */ +/* Add an ACL to redirect frames to control interface based on the dst MAC */ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, - const char *mac) + const u8 *mac, const u8 *mask) { struct dpaa2_switch_acl_entry acl_entry = {0}; /* Match on the destination MAC address */ ether_addr_copy(acl_entry.key.match.l2_dest_mac, mac); - eth_broadcast_addr(acl_entry.key.mask.l2_dest_mac); + ether_addr_copy(acl_entry.key.mask.l2_dest_mac, mask); /* Trap to CPU */ acl_entry.cfg.precedence = 0; @@ -3869,7 +3867,8 @@ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) { - const char stpa[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mac[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mask[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; struct switchdev_obj_port_vlan vlan = { .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, .vid = DEFAULT_VLAN_ID, @@ -3944,7 +3943,7 @@ static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) if (err) return err; - err = dpaa2_switch_port_trap_mac_addr(port_priv, stpa); + err = dpaa2_switch_port_trap_mac_addr(port_priv, ll_mac, ll_mask); if (err) return err; -- 2.25.1 Switch ports configured as part of a LAG group are not able to provide a precise source port for all packets which reach the control interface. The only frames which will have a precise source port are those that are explicitly trapped, for example STP and LCAP frames. For any other frames (for example, those which are flooded) we can only know the ingress LAG group. Take into account the DPAA2_ETHSW_FLC_IMPRECISE_IF_ID bit and based on its value target the bond device or the specific source netdevice. Signed-off-by: Ioana Ciornei --- Changes in v2: - Fix 32bit build by using BIT_ULL - Take a reference to port_priv->lag instead of reading it multiple times. --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 12 ++++++++++-- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 98cf404d4ac8..0acebb386486 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -3142,14 +3142,18 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, { struct ethsw_core *ethsw = fq->ethsw; struct ethsw_port_priv *port_priv; + struct dpaa2_switch_lag *lag; struct net_device *netdev; struct vlan_ethhdr *hdr; struct sk_buff *skb; u16 vlan_tci, vid; int if_id, err; + u64 flc; + + flc = dpaa2_fd_get_flc(fd); /* get switch ingress interface ID */ - if_id = upper_32_bits(dpaa2_fd_get_flc(fd)) & 0x0000FFFF; + if_id = DPAA2_ETHSW_FLC_IF_ID(flc); if (if_id >= ethsw->sw_attr.num_ifs) { dev_err(ethsw->dev, "Frame received from unknown interface!\n"); @@ -3188,7 +3192,11 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, } } - skb->dev = netdev; + lag = port_priv->lag; + if (DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) && lag) + skb->dev = lag->bond_dev; + else + skb->dev = netdev; skb->protocol = eth_type_trans(skb, skb->dev); /* Setup the offload_fwd_mark only if the port is under a bridge diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index 96d780980d77..d481183e8698 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -87,6 +87,9 @@ #define DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE 256 +#define DPAA2_ETHSW_FLC_IF_ID(flc) (((flc) >> 32) & GENMASK(15, 0)) +#define DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) ((flc) & BIT_ULL(63)) + extern const struct ethtool_ops dpaa2_switch_port_ethtool_ops; struct ethsw_core; -- 2.25.1 With the addition of the LAG offload support it has become apparent that depending on the order in which a bridged bond setup is created, the driver could be requested to add the same VLAN twice, once through the 802.1q filters and then through switchdev. This is because any VLANs already installed on the bond device when the bond_enslave() operation happens will also be installed on the switch port through the .ndo_vlan_rx_add_vid() callback. Once the bond device becomes offloaded, the same VLANs will get replayed and installed through switchdev. $ ip link set dev eth4 master bond1 [ 165.008131] fsl_dpaa2_switch dpsw.0 eth4: configuring for inband/usxgmii link mode [ 165.021020] 8021q: adding VLAN 0 to HW filter on device eth4 [ 165.083351] fsl_dpaa2_switch dpsw.0 eth4: VLAN 100 already configured RTNETLINK answers: File exists Avoid this by not erroring out when the same VLAN is installed or removed multiple times so that we avoid the above issue. Also remove the netdev_err() since there isn't anything that the user can do to prevent this behavior. Signed-off-by: Ioana Ciornei --- Changes in v2: - none --- .../net/ethernet/freescale/dpaa2/dpaa2-switch.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 0acebb386486..b7b07ff8c60a 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -372,10 +372,8 @@ static int dpaa2_switch_port_add_vlan(struct ethsw_port_priv *port_priv, struct dpsw_vlan_if_cfg vcfg = {0}; int err; - if (port_priv->vlans[vid]) { - netdev_err(netdev, "VLAN %d already configured\n", vid); - return -EEXIST; - } + if (port_priv->vlans[vid]) + return 0; /* If hit, this VLAN rule will lead the packet into the FDB table * specified in the vlan configuration below @@ -2003,13 +2001,8 @@ int dpaa2_switch_port_vlans_add(struct net_device *netdev, struct dpsw_attr *attr = ðsw->sw_attr; int err = 0; - /* Make sure that the VLAN is not already configured - * on the switch port - */ - if (port_priv->vlans[vlan->vid] & ETHSW_VLAN_MEMBER) { - netdev_err(netdev, "VLAN %d already configured\n", vlan->vid); - return -EEXIST; - } + if (port_priv->vlans[vlan->vid] & ETHSW_VLAN_MEMBER) + return 0; /* Check if there is space for a new VLAN */ err = dpsw_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle, @@ -2129,7 +2122,7 @@ static int dpaa2_switch_port_del_vlan(struct ethsw_port_priv *port_priv, u16 vid int i, err; if (!port_priv->vlans[vid]) - return -ENOENT; + return 0; if (port_priv->vlans[vid] & ETHSW_VLAN_PVID) { /* If we are deleting the PVID of a port, use VLAN 4095 instead -- 2.25.1