* [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload
@ 2026-05-06 15:15 Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 01/13] dpaa2-switch: add LAG configuration API Ioana Ciornei
` (12 more replies)
0 siblings, 13 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
This patch set adds support in dpaa2-switch for offloading upper bond
devices.
The first patch adds the necessary new APIs for the LAG configuration
while the second patch uses them, both in the prechangeupper phase and
the changeupper one. Which ports can be part of the same LAG group is
configurable at boot time, thus we use the prechangeupper callback in
order to validate that a requested configuration can be offloaded or
not.
Patches 3 and 4 are updating the logic around choosing the FDB that
should be used on a switch port. This is necessary since with the
addition of the LAG offload, we need to take into account all ports
which are under the same bridge, even though not directly.
This set also extends the handling of FDBs and port objects so that they
are handled by the driver even on an offloaded bond device.
Ioana Ciornei (13):
dpaa2-switch: add LAG configuration API
dpaa2-switch: add support for LAG offload
dpaa2-switch: change dpaa2_switch_port_set_fdb() function prototype
dpaa2-switch: extend dpaa2_switch_port_set_fdb() to cover bond
scenarios
dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helper
dpaa2-switch: create a separate dpaa2_switch_port_fdb_event() function
dpaa2-switch: check early if an FDB entry should be added
dpaa2-switch: consolidate unicast and multicast management
dpaa2-switch: offload FDBs added on an upper bond device
dpaa2-switch: offload port objects on an upper bond device
dpaa2-switch: trap all link local reserved addresses to the CPU
dpaa2-switch: add support for imprecise source port
dpaa2-switch: do not error out when the same VLAN is installed
multiple times
.../ethernet/freescale/dpaa2/dpaa2-switch.c | 866 +++++++++++++++---
.../ethernet/freescale/dpaa2/dpaa2-switch.h | 39 +-
.../net/ethernet/freescale/dpaa2/dpsw-cmd.h | 18 +-
drivers/net/ethernet/freescale/dpaa2/dpsw.c | 57 ++
drivers/net/ethernet/freescale/dpaa2/dpsw.h | 20 +
5 files changed, 886 insertions(+), 114 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH net-next 01/13] dpaa2-switch: add LAG configuration API
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 02/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (11 subsequent siblings)
12 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 02/13] dpaa2-switch: add support for LAG offload
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 01/13] dpaa2-switch: add LAG configuration API Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-08 12:39 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 03/13] dpaa2-switch: change dpaa2_switch_port_set_fdb() function prototype Ioana Ciornei
` (10 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../ethernet/freescale/dpaa2/dpaa2-switch.c | 390 +++++++++++++++++-
.../ethernet/freescale/dpaa2/dpaa2-switch.h | 14 +-
2 files changed, 402 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
index 52c1cb9cb7e0..6367873401c0 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)
{
@@ -2195,6 +2206,266 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev,
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;
+
+ 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 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;
+
+ /* 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;
+
+ /* 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:
+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);
+ 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;
+ int err = 0;
+
+ /* 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 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)
+ return err;
+
+ /* 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)
+ return err;
+
+ /* 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)
+ return err;
+
+ /* Recreate the LAG configuration for the LAG group that we left */
+ port_priv->lag = NULL;
+ dpaa2_switch_set_lag_cfg(bond_dev, lag->id, ethsw);
+
+ return 0;
+}
+
static int dpaa2_switch_port_prechangeupper(struct net_device *netdev,
struct netdev_notifier_changeupper_info *info)
{
@@ -2216,6 +2487,9 @@ 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) && info->linking) {
+ return dpaa2_switch_pre_lag_join(netdev, upper_dev,
+ info->upper_info, extack);
}
return 0;
@@ -2240,6 +2514,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 +2597,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 +2606,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 +2865,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 +2885,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 +3672,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 +3700,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 +3808,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 +3867,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 03/13] dpaa2-switch: change dpaa2_switch_port_set_fdb() function prototype
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 01/13] dpaa2-switch: add LAG configuration API Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 02/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-08 11:00 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 04/13] dpaa2-switch: extend dpaa2_switch_port_set_fdb() to cover bond scenarios Ioana Ciornei
` (9 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../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 6367873401c0..f1b4c24b8a47 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);
@@ -2381,7 +2378,7 @@ static int dpaa2_switch_port_bond_join(struct net_device *netdev,
u8 lag_id;
/* 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;
@@ -2416,7 +2413,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);
return err;
}
@@ -2435,7 +2432,7 @@ static int dpaa2_switch_port_bond_leave(struct net_device *netdev,
return err;
/* 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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 04/13] dpaa2-switch: extend dpaa2_switch_port_set_fdb() to cover bond scenarios
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (2 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 03/13] dpaa2-switch: change dpaa2_switch_port_set_fdb() function prototype Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-08 13:50 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helper Ioana Ciornei
` (8 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../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 f1b4c24b8a47..9d3a0aef1568 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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helper
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (3 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 04/13] dpaa2-switch: extend dpaa2_switch_port_set_fdb() to cover bond scenarios Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-08 14:10 ` [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helpe Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 06/13] dpaa2-switch: create a separate dpaa2_switch_port_fdb_event() function Ioana Ciornei
` (7 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../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 9d3a0aef1568..aaa22dc15038 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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 06/13] dpaa2-switch: create a separate dpaa2_switch_port_fdb_event() function
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (4 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helper Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 07/13] dpaa2-switch: check early if an FDB entry should be added Ioana Ciornei
` (6 subsequent siblings)
12 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../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 aaa22dc15038..476ee8b46921 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
@@ -2755,9 +2755,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);
@@ -2765,9 +2764,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;
@@ -2808,6 +2804,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 07/13] dpaa2-switch: check early if an FDB entry should be added
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (5 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 06/13] dpaa2-switch: create a separate dpaa2_switch_port_fdb_event() function Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-08 14:34 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 08/13] dpaa2-switch: consolidate unicast and multicast management Ioana Ciornei
` (5 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
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 476ee8b46921..a057fd85bd27 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
@@ -2725,8 +2725,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);
@@ -2740,8 +2738,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
@@ -2767,6 +2763,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 08/13] dpaa2-switch: consolidate unicast and multicast management
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (6 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 07/13] dpaa2-switch: check early if an FDB entry should be added Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-08 14:46 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 09/13] dpaa2-switch: offload FDBs added on an upper bond device Ioana Ciornei
` (4 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../ethernet/freescale/dpaa2/dpaa2-switch.c | 39 +++++++++++++------
1 file changed, 27 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
index a057fd85bd27..bced37335a9e 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,7 +1937,7 @@ 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;
@@ -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;
@@ -2725,12 +2747,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;
@@ -2738,10 +2756,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 09/13] dpaa2-switch: offload FDBs added on an upper bond device
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (7 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 08/13] dpaa2-switch: consolidate unicast and multicast management Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-08 15:20 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 10/13] dpaa2-switch: offload port objects " Ioana Ciornei
` (3 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../ethernet/freescale/dpaa2/dpaa2-switch.c | 190 ++++++++++++++++--
.../ethernet/freescale/dpaa2/dpaa2-switch.h | 22 ++
2 files changed, 193 insertions(+), 19 deletions(-)
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
index bced37335a9e..cce0af47ca07 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,22 @@ 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);
+
+ if (netif_is_bridge_master(foreign_dev))
+ if (port_priv->fdb->bridge_dev == foreign_dev)
+ return false;
+
+ if (netif_is_bridge_port(foreign_dev))
+ return !dpaa2_switch_port_offloads_bridge_port(port_priv,
+ foreign_dev);
+
+ 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 +2268,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;
@@ -2731,7 +2856,9 @@ 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)
@@ -2739,6 +2866,7 @@ 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 *dev = switchdev_work->dev;
+ struct ethsw_port_priv *port_priv = netdev_priv(dev);
struct switchdev_notifier_fdb_info *fdb_info;
int err;
@@ -2747,16 +2875,26 @@ 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,
+ switchdev_work->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;
}
@@ -2766,33 +2904,40 @@ static void dpaa2_switch_event_work(struct work_struct *work)
dev_put(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)
@@ -2806,16 +2951,16 @@ static int dpaa2_switch_port_fdb_event(struct notifier_block *nb,
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() */
@@ -2823,13 +2968,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;
}
@@ -3917,6 +4067,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 10/13] dpaa2-switch: offload port objects on an upper bond device
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (8 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 09/13] dpaa2-switch: offload FDBs added on an upper bond device Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-11 9:07 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 11/13] dpaa2-switch: trap all link local reserved addresses to the CPU Ioana Ciornei
` (2 subsequent siblings)
12 siblings, 1 reply; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../ethernet/freescale/dpaa2/dpaa2-switch.c | 66 ++++++++++---------
1 file changed, 36 insertions(+), 30 deletions(-)
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
index cce0af47ca07..17a7a64064b5 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
@@ -2060,7 +2060,11 @@ 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;
@@ -2073,11 +2077,19 @@ static int dpaa2_switch_port_mdb_add(struct net_device *netdev,
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,
@@ -2178,9 +2190,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) {
@@ -2191,11 +2204,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));
@@ -2985,37 +3005,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 11/13] dpaa2-switch: trap all link local reserved addresses to the CPU
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (9 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 10/13] dpaa2-switch: offload port objects " Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 13/13] dpaa2-switch: do not error out when the same VLAN is installed multiple times Ioana Ciornei
12 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
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 17a7a64064b5..59ed06ed7ef6 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
@@ -3776,17 +3776,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;
@@ -3797,7 +3795,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,
@@ -3872,7 +3871,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (10 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 11/13] dpaa2-switch: trap all link local reserved addresses to the CPU Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
2026-05-07 11:42 ` Ioana Ciornei
2026-05-11 11:32 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 13/13] dpaa2-switch: do not error out when the same VLAN is installed multiple times Ioana Ciornei
12 siblings, 2 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 10 ++++++++--
drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h | 3 +++
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
index 59ed06ed7ef6..8353d2230c72 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
@@ -3075,9 +3075,12 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq,
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");
@@ -3116,7 +3119,10 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq,
}
}
- skb->dev = netdev;
+ if (DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) && port_priv->lag)
+ skb->dev = port_priv->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..6d7a503f9be7 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(63))
+
extern const struct ethtool_ops dpaa2_switch_port_ethtool_ops;
struct ethsw_core;
--
2.25.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next 13/13] dpaa2-switch: do not error out when the same VLAN is installed multiple times
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
` (11 preceding siblings ...)
2026-05-06 15:15 ` [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port Ioana Ciornei
@ 2026-05-06 15:15 ` Ioana Ciornei
12 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-06 15:15 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
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 <ioana.ciornei@nxp.com>
---
.../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 8353d2230c72..644f720a7ec7 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
@@ -1993,13 +1991,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,
@@ -2115,7 +2108,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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port
2026-05-06 15:15 ` [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port Ioana Ciornei
@ 2026-05-07 11:42 ` Ioana Ciornei
2026-05-11 11:32 ` Ioana Ciornei
1 sibling, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-07 11:42 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:39PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 10 ++++++++--
> drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h | 3 +++
> 2 files changed, 11 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> index 59ed06ed7ef6..8353d2230c72 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(63))
I saw the patchwork test - this breaks the build_32bit test.
I will use BIT_ULL in the next version.
Ioana
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 03/13] dpaa2-switch: change dpaa2_switch_port_set_fdb() function prototype
2026-05-06 15:15 ` [PATCH net-next 03/13] dpaa2-switch: change dpaa2_switch_port_set_fdb() function prototype Ioana Ciornei
@ 2026-05-08 11:00 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-08 11:00 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:30PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> .../ethernet/freescale/dpaa2/dpaa2-switch.c | 33 +++++++++----------
> 1 file changed, 15 insertions(+), 18 deletions(-)
(...)
> @@ -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;
Shahiko notes:
This problem wasn't introduced by this patch, but does this leak
the previous FDB tracking structure? When a port leaves a
bridge or bond (when linking is false), it assigns the newly
allocated fdb to port_priv->fdb and sets in_use to true.
However, the in_use flag of the old FDB is never cleared. If
this port was the last user of the shared FDB, does that FDB
remain permanently leaked? Over time, repeated topology changes
could exhaust all FDBs. Once exhausted,
dpaa2_switch_fdb_get_unused() would return NULL, which seems
like it would cause the leaving port to keep its shared FDB,
potentially leaking traffic between standalone ports and active
bridges.
That is not true. In case port_priv is the last user of the shared FDB,
aka the last port that leaves the last bridge as noted in the code
comment, then dpaa2_switch_fdb_get_unused() will return NULL indicating
this condition. In this case, port_priv will continue to use the current
FDB which is the correct action since it's certain that this is the only
user of the FDB.
(...)
> @@ -2381,7 +2378,7 @@ static int dpaa2_switch_port_bond_join(struct net_device *netdev,
> u8 lag_id;
>
> /* 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);
Sashiko notes:
This problem wasn't introduced by this patch, but if a port
joins a bond that is already enslaved to a bridge, does this
split the bond across different FDBs?
If dpaa2_switch_port_bond_join() is called, it first executes
dpaa2_switch_port_set_fdb(..., bond_dev, true), adopting the
bond's shared FDB.
Immediately after, it calls dpaa2_switch_port_bridge_join(),
which executes dpaa2_switch_port_set_fdb(..., bridge_dev, true).
If the bridge contains another standalone DPAA2 port, the logic
will match that port and execute: if (other_port_priv) {
port_priv->fdb->in_use = false; port_priv->fdb->bridge_dev =
NULL; port_priv->fdb = other_port_priv->fdb; }
Because port_priv->fdb is the bond's shared FDB, doesn't this
incorrectly mark the bond's active FDB as unused while other
bond slaves are still actively using it?
There is no such problem as the one described above.
If a port joins a bond that is already under a bridge, then the joining
port will use the FDB shared by all the ports under that same bridge.
Also, any other bond slaves already under the bridged bond will be
already using the same FDB.
Ioana
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 02/13] dpaa2-switch: add support for LAG offload
2026-05-06 15:15 ` [PATCH net-next 02/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
@ 2026-05-08 12:39 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-08 12:39 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:29PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> .../ethernet/freescale/dpaa2/dpaa2-switch.c | 390 +++++++++++++++++-
> .../ethernet/freescale/dpaa2/dpaa2-switch.h | 14 +-
> 2 files changed, 402 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> index 52c1cb9cb7e0..6367873401c0 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;
> }
(...)
Sashiko notes:
When a port joins a bond that is already attached to a bridge,
this calls dpaa2_switch_port_bridge_join() which calls
dpaa2_switch_port_set_fdb() again. Does this second call
inadvertently find the bond's shared FDB on another port and
mark it as unused (in_use = false)?
Also, when a bonded port joins a bridge here, is it correct that
dpaa2_switch_port_bridge_join() calls
switchdev_bridge_port_offload() passing the physical port
(netdev) as both brport_dev and dev? Switchdev relies on the
bridge port state, which might fail if it is expecting the bond
device itself as the logical bridge port.
The logic around the switchdev_bridge_port_offload() is amended in a
following patch named "dpaa2-switch: add
dpaa2_switch_port_to_bridge_port() helper".
The dpaa2_switch_port_set_fdb() function is also updated in a later
commit so that it takes into account a bond upper.
Will consider reordering the patches.
> + if (err)
> + goto err_bridge_join;
> +
> + return err;
> +
> +err_bridge_join:
> +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);
Sashiko notes:
If an error occurs and we jump to err_egress_flood, we revert
the software FDB pointer. Should we also call
dpsw_set_egress_flood() to revert the hardware state so the port
isn't left physically stranded in the bond's flooding domain?
(...)
> + /* Recreate the LAG configuration for the LAG group that we left */
> + port_priv->lag = NULL;
> + dpaa2_switch_set_lag_cfg(bond_dev, lag->id, ethsw);
Sashiko notes:
If the bond was attached to a bridge, do we also need to clean
up the switchdev bridge offload context here?
dpaa2_switch_port_bond_join() calls
dpaa2_switch_port_bridge_join() which allocates context via
switchdev_bridge_port_offload(), but it does not look like there
is a symmetric teardown here.
That is not true. The switchdev_bridge_port_unoffload() function is
called from dpaa2_switch_port_pre_bridge_leave().
> +
> + return 0;
> +}
> +
> static int dpaa2_switch_port_prechangeupper(struct net_device *netdev,
> struct netdev_notifier_changeupper_info *info)
> {
> @@ -2216,6 +2487,9 @@ 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) && info->linking) {
> + return dpaa2_switch_pre_lag_join(netdev, upper_dev,
> + info->upper_info, extack);
Sashiko notes:
If a port joins a bond that is already enslaved to a bridge,
does this bypass dpaa2_switch_prechangeupper_sanity_checks() for
the indirect bridge? This might allow joining unsupported
topologies like VLAN-unaware bridges.
Good point. Will fix this case by checking if the upper_devis also a
bridge port and in case it is, to call
dpaa2_switch_prechangeupper_sanity_checks() which verifies those
unsupported topologies.
Also, looking at dpaa2_switch_port_set_fdb() and
dpaa2_switch_prechangeupper_sanity_checks(), they iterate over
the bridge's lowers using netdev_for_each_lower_dev(). Since
this only yields direct lowers, will it skip ports under a LAG?
This could cause standalone ports to miss LAG peer ports and
allocate a separate FDB.
Another good point. The netdev_for_each_lower_dev() will have to be
converted to a netdev_walk_all_lower_dev() so that we check all lowers,
not just those immediately adjacent.
(...)
> +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);
Sashiko notes:
When a LAG device is removed from a bridge, this cascades to
dpaa2_switch_port_bridge_leave() for each port. That function
calls dpaa2_switch_port_set_fdb(port_priv, NULL) which allocates
a standalone FDB. Does this inadvertently break the bond in
hardware by placing slave ports into isolated standalone FDBs?
This is yet another reason to reorder the patches so that
dpaa2_switch_port_set_fdb() is updated before this patch.
> + if (err)
> + return err;
Sashiko notes:
If info->linking is false (a teardown operation) and this
returns immediately on the first error, could this permanently
strand the remaining unprocessed ports in the hardware bridge
configuration? The network stack cannot revert an unlinking
operatione
Even if we would continue with the other ports the state of the entire
topology will be inconsistent so I don't know if it's worth going
forward will the remaining ports.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 04/13] dpaa2-switch: extend dpaa2_switch_port_set_fdb() to cover bond scenarios
2026-05-06 15:15 ` [PATCH net-next 04/13] dpaa2-switch: extend dpaa2_switch_port_set_fdb() to cover bond scenarios Ioana Ciornei
@ 2026-05-08 13:50 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-08 13:50 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:31PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> .../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 f1b4c24b8a47..9d3a0aef1568 100644
(...)
> - 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);
Sashiko notes:
Does this sequence introduce a resource leak of FDB entries?
When a bridged bond leaves the bridge, the first port acquires
an unused FDB. Subsequent ports in the bond bypass the unused
pool and directly copy the first port's FDB, leaving surplus
FDBs in the pool.
That is not the correct understanding at all. The FDBs are there as a
means to correctly setup the forwarding domains in a switch, there is no
such thing as "surplus FDBs in the pool"
> +
> + 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;
Sashiko notes:
When the final port in the bridge (e.g., a standalone port)
subsequently leaves, it will find these surplus FDBs and
successfully acquire one. The original bridge FDB is then
completely abandoned, but since its in_use flag is never
explicitly set to false in this path, the FDB is leaked.
If a bond and a standalone port repeatedly join and leave a
bridge, could this exhaust the hardware FDB pool? Once
exhausted, it seems subsequent standalone ports would fail to
acquire exclusive FDBs and might silently share existing FDBs
with other networks.
Not correct at all. The FDBs cannot be "leaked". There is an array
holding them per ethsw_data.
Also, it's ok not to have all FDBs used at a moment in time. In this
exemple above, after an offloaded bond device and a dpaa2 switch port
leave a bridge, it's expected for all the switch ports under the same
bond to use the same FDB, thus leaving unused FDBs.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helpe
2026-05-06 15:15 ` [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helper Ioana Ciornei
@ 2026-05-08 14:10 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-08 14:10 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:32PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> .../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 9d3a0aef1568..aaa22dc15038 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;
Sashiko notes:
Does this code ignore the return value of
dpaa2_switch_port_set_learning()? If the firmware command
fails, the driver will silently ignore it.
This could leave the port's hardware state desynchronized from
the kernel's tracking state (port_priv->learn_ena), which could
lead to forwarding anomalies.
True, but the code was there before this patch set. I am leaning towards
not fixing it in this set since we are already at 13 patches.
>
> @@ -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;
Sashiko notes:
Are we missing a rollback of the port state in the
err_switchdev_offload error path?
The same response as above.
> @@ -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);
Sashiko notes:
Is there a missing switchdev unoffload when a port leaves a bond
device that is part of a bridge?
When a port joins a bond that is part of a bridge,
dpaa2_switch_port_bond_join() calls
dpaa2_switch_port_bridge_join(), incrementing the bridge's
offload_count for the bond device.
When the port is later removed from the bond,
NETDEV_PRECHANGEUPPER is triggered with !info->linking where
upper_dev is the LAG. However,
dpaa2_switch_port_prechangeupper() only handles the
info->linking case, explicitly ignoring the !info->linking case.
Thus, dpaa2_switch_port_pre_bridge_leave() is skipped.
Subsequently, dpaa2_switch_port_bond_leave() resets the port to
standalone mode in hardware but never calls
switchdev_bridge_port_unoffload() or
dpaa2_switch_port_bridge_leave().
Can this completely bypass the software teardown path,
permanently leaking the offload_count reference for the bridge
port and corrupting the switchdev offload state?
Yes, I forgot to also call dpaa2_switch_port_bridge_leave() when leaving
a bond which is a bridge port. Will fix it in the commit managing the
changeupper events, not in this patch.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 07/13] dpaa2-switch: check early if an FDB entry should be added
2026-05-06 15:15 ` [PATCH net-next 07/13] dpaa2-switch: check early if an FDB entry should be added Ioana Ciornei
@ 2026-05-08 14:34 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-08 14:34 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:34PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> 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 476ee8b46921..a057fd85bd27 100644
(...)
> if (!dpaa2_switch_port_dev_check(dev))
> return NOTIFY_DONE;
>
> + if (!fdb_info->added_by_user || fdb_info->is_local)
> + return NOTIFY_DONE;
> +
Sashiko notes:
This problem wasn't introduced by this patch, but looking at the
surrounding context in this function, is there a pre-existing
out-of-bounds read issue?
Could reading port_priv->ethsw_data happen before ensuring the
device belongs to the dpaa2-switch driver?
Since switchdev notifiers are called globally for all network
devices, an event on a device with zero private data (like a
dummy interface) would cause netdev_priv(dev) to point to the
end of the device allocation.
If the allocation ends at a page boundary, would accessing
ethsw_data trigger a KASAN panic or a page fault when an
unprivileged user creates a dummy interface in a network
namespace and adds an FDB entry?
I don't think that can happen since dpaa2_switch_port_fdb_event() is
called using switchdev_handle_fdb_event_to_device() which checks if the
interface is a dpaa2-switch one using the dpaa2_switch_port_dev_check()
callback.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 08/13] dpaa2-switch: consolidate unicast and multicast management
2026-05-06 15:15 ` [PATCH net-next 08/13] dpaa2-switch: consolidate unicast and multicast management Ioana Ciornei
@ 2026-05-08 14:46 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-08 14:46 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:35PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> .../ethernet/freescale/dpaa2/dpaa2-switch.c | 39 +++++++++++++------
> 1 file changed, 27 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> index a057fd85bd27..bced37335a9e 100644
> --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
(...)
> @@ -2725,12 +2747,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);
Sashiko notes:
Does this code leave an orphaned unicast FDB entry in hardware
if dev_mc_add() fails just below this diff context?
When dpaa2_switch_port_fdb_add() is called, it dynamically
routes the addition to either the unicast or multicast hardware
FDB.
However, the rollback path immediately below this hunk still
hardcodes the multicast deletion:
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);
}
If userspace passes a unicast MAC address for an L2 MDB entry
and dev_mc_add() fails, will the rollback attempt to remove the
MAC from the multicast FDB instead of the unicast FDB where it
was just added?
A later commit introduces LAG FDB abstractions, and this missed
error path continues to hardcode the deletion, which might also
bypass the LAG refcounting teardown.
Should the rollback use the newly introduced
dpaa2_switch_port_fdb_del() to ensure symmetrical cleanup?
I cannot see how the "If userspace passes a unicast MAC address for an
L2 MDB entry" statement could be true.
But yes, the rollback can use the newly introduced helper just for
symmetry.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 09/13] dpaa2-switch: offload FDBs added on an upper bond device
2026-05-06 15:15 ` [PATCH net-next 09/13] dpaa2-switch: offload FDBs added on an upper bond device Ioana Ciornei
@ 2026-05-08 15:20 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-08 15:20 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:36PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> .../ethernet/freescale/dpaa2/dpaa2-switch.c | 190 ++++++++++++++++--
> .../ethernet/freescale/dpaa2/dpaa2-switch.h | 22 ++
> 2 files changed, 193 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> index bced37335a9e..cce0af47ca07 100644
(...)
> +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;
> + }
Sashiko noted:
Could this refcounting logic cause FDB entries to leak when
ports are removed from a LAG?
When an FDB entry is added to a bond,
switchdev_handle_fdb_event_to_device() fans out the
SWITCHDEV_FDB_ADD_TO_DEVICE event to all N lower switch ports,
which calls dpaa2_switch_lag_fdb_add() N times and sets the
refcount to N.
If a port leaves the bond, switchdev does not emit delete events
for existing FDBs, so the refcount remains at N. If the FDB
entry is subsequently deleted from the bond, the delete event is
fanned out to only the remaining N-1 ports. This would drop the
refcount to 1 instead of 0, meaning the MAC is never removed
from hardware and the software entry is permanently leaked.
I forgot to pass the proper context and the notifier blocks to the
switchdev_bridge_port_unoffload() call. I think this will fix the issue
raised above.
> +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);
> +
> + if (netif_is_bridge_master(foreign_dev))
> + if (port_priv->fdb->bridge_dev == foreign_dev)
> + return false;
> +
> + if (netif_is_bridge_port(foreign_dev))
> + return !dpaa2_switch_port_offloads_bridge_port(port_priv,
> + foreign_dev);
> +
> + return true;
> +}
Sashiko notes:
Does this change inadvertently break FDB offloading for
standalone ports?
If a user adds an FDB entry directly to a standalone switch port
or a standalone bond, orig_dev is the standalone interface
itself, which is neither a bridge master nor a bridge port.
Because of this, dpaa2_switch_foreign_dev_check() will evaluate
to true and cause dpaa2_switch_port_fdb_event() to return 0,
silently ignoring the FDB addition. Prior to this patch, these
entries were accepted.
No, there is no such think as a standalone switch port or a standalone
bond that also somehow handle switchdev events.
> 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;
Sashiko notes:
Does orig_dev need a reference count here to prevent a
use-after-free?
Since the pointer to orig_dev is stored for the asynchronous
switchdev work, and the work queue later uses it in
call_switchdev_notifiers(), could the network stack unregister
and free the upper bond or bridge device before
dpaa2_switch_event_work() executes?
If so, taking a reference with dev_hold(orig_dev) and releasing
it with dev_put(orig_dev) in the worker might be necessary.
Sure, will fix it.
(...)
> @@ -3917,6 +4067,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);
> }
Sashiko notes:
Will these FDB lists be properly cleaned up when a LAG is
destroyed?
When the last port leaves a bond device, the LAG is marked as
unused (lag->in_use = false), but it doesn't appear that the
driver iterates over lag->fdbs to free the remaining FDB
entries, which could cause a memory leak.
I think switchdev will remove the FDB entries from the ports any time a
bridge port is unoffloaded.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 10/13] dpaa2-switch: offload port objects on an upper bond device
2026-05-06 15:15 ` [PATCH net-next 10/13] dpaa2-switch: offload port objects " Ioana Ciornei
@ 2026-05-11 9:07 ` Ioana Ciornei
0 siblings, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-11 9:07 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:37PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> .../ethernet/freescale/dpaa2/dpaa2-switch.c | 66 ++++++++++---------
> 1 file changed, 36 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> index cce0af47ca07..17a7a64064b5 100644
> --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> @@ -2060,7 +2060,11 @@ 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;
Sashiko notes:
If dev_mc_add() fails further down in this function, the
rollback path unconditionally calls
dpaa2_switch_port_fdb_del_mc():
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);
}
Does this bypass dpaa2_switch_lag_fdb_del() for LAG MDBs?
It looks like this could leak the dpaa2_mac_addr structure
allocated during dpaa2_switch_lag_fdb_add() and leave the
software lag->fdbs list out of sync with the hardware FDB state.
Yes, the cleanup needs to also take into account if the port is under a
LAG or not. Will fix it.
(...)
> @@ -2178,9 +2190,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);
Sashiko notes:
Is it intentional to drop the error check for hardware FDB
deletion here?
Previously, the function returned early if
dpaa2_switch_port_fdb_del() returned an error. By silently
dropping the return value, the driver proceeds to remove the MAC
from the device's multicast list anyway, which might mask
hardware desynchronization regressions from the caller.
Yes, it was intentional. I think there is no point propagating such an
error on the remove path.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port
2026-05-06 15:15 ` [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port Ioana Ciornei
2026-05-07 11:42 ` Ioana Ciornei
@ 2026-05-11 11:32 ` Ioana Ciornei
1 sibling, 0 replies; 24+ messages in thread
From: Ioana Ciornei @ 2026-05-11 11:32 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, netdev; +Cc: linux-kernel
On Wed, May 06, 2026 at 06:15:39PM +0300, Ioana Ciornei wrote:
> 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 <ioana.ciornei@nxp.com>
> ---
> drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 10 ++++++++--
> drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h | 3 +++
> 2 files changed, 11 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> index 59ed06ed7ef6..8353d2230c72 100644
> --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
> @@ -3075,9 +3075,12 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq,
> 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");
Sashiko noted:
This isn't a regression introduced by this commit, but does
jumping to err_free_fd cause a kernel panic here?
Since dpaa2_switch_free_fd() is a TX-specific cleanup function
that expects the first 8 bytes of the DMA buffer to contain a
valid skb pointer, will calling it on an RX buffer read
uninitialized memory and crash when passed to dev_kfree_skb()?
Good point, instead of the dpaa2_switch_free_fd() call there should be a
simple free_pages(). On the other hand, I will not add the fix to this
patch set since it's already quite substantial.
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-05-11 11:32 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-06 15:15 [PATCH net-next 00/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 01/13] dpaa2-switch: add LAG configuration API Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 02/13] dpaa2-switch: add support for LAG offload Ioana Ciornei
2026-05-08 12:39 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 03/13] dpaa2-switch: change dpaa2_switch_port_set_fdb() function prototype Ioana Ciornei
2026-05-08 11:00 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 04/13] dpaa2-switch: extend dpaa2_switch_port_set_fdb() to cover bond scenarios Ioana Ciornei
2026-05-08 13:50 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helper Ioana Ciornei
2026-05-08 14:10 ` [PATCH net-next 05/13] dpaa2-switch: add dpaa2_switch_port_to_bridge_port() helpe Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 06/13] dpaa2-switch: create a separate dpaa2_switch_port_fdb_event() function Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 07/13] dpaa2-switch: check early if an FDB entry should be added Ioana Ciornei
2026-05-08 14:34 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 08/13] dpaa2-switch: consolidate unicast and multicast management Ioana Ciornei
2026-05-08 14:46 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 09/13] dpaa2-switch: offload FDBs added on an upper bond device Ioana Ciornei
2026-05-08 15:20 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 10/13] dpaa2-switch: offload port objects " Ioana Ciornei
2026-05-11 9:07 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 11/13] dpaa2-switch: trap all link local reserved addresses to the CPU Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 12/13] dpaa2-switch: add support for imprecise source port Ioana Ciornei
2026-05-07 11:42 ` Ioana Ciornei
2026-05-11 11:32 ` Ioana Ciornei
2026-05-06 15:15 ` [PATCH net-next 13/13] dpaa2-switch: do not error out when the same VLAN is installed multiple times Ioana Ciornei
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox