From efc1d92eacf03afa6f4d53bf7120e059b6f961f2 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Thu, 28 May 2026 20:34:48 +0300 Subject: [PATCH 1/5] dpaa2-switch: rework FDB management on the bridge leave path On bridge leave, the dpaa2_switch_port_set_fdb() function always allocates a new FDB for the port which is becoming standalone. In case no FDB is found, then the port leaving a bridge will continue to use the current one. The above logic does not cover the case in which there are multiple bridges which have ports from the same DPSW instance. In this case, when the last port leaves bridge #1, it finds an unused FDB to switch to, but the old FDB is not marked as unused. Since the number of FDBs is equal to the number of DPSW interfaces, this will eventually lead to multiple ports sharing the same FDB. Fix this by changing how we are managing the FDBs on the leave path. Instead of directly allocating a new FDB, first verify if the current port is the last one to leave a bridge. If this is the case, then continue to use the current FDB and only allocate another FDB if there are other ports remaining in the bridge. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260528173452.1953102-2-ioana.ciornei@nxp.com Signed-off-by: Jakub Kicinski --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 52c1cb9cb7e0..782fef26b78e 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -54,27 +54,44 @@ dpaa2_switch_filter_block_get_unused(struct ethsw_core *ethsw) static u16 dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, struct net_device *bridge_dev) { + 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; + bool last_fdb_user = true; struct list_head *iter; + int i; /* If we leave a bridge (bridge_dev is NULL), find an unused * FDB and use that. */ if (!bridge_dev) { - fdb = dpaa2_switch_fdb_get_unused(port_priv->ethsw_data); + /* First verify if this is the last port to leave this bridge */ + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (!ethsw->ports[i] || ethsw->ports[i] == port_priv) + continue; + if (ethsw->ports[i]->fdb == port_priv->fdb) { + last_fdb_user = false; + break; + } + } - /* 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. - */ - - if (!fdb) { + /* If this is the last user of the FDB, just keep using it. */ + if (last_fdb_user) { port_priv->fdb->bridge_dev = NULL; return 0; } + /* Since we are not the last port which leaves a bridge, + * acquire a new FDB and use it. The number of FDBs is sized to + * accommodate all switch ports as standalone, each with its + * private FDB, which means that dpaa2_switch_fdb_get_unused() + * must succeed here. WARN if not. + */ + fdb = dpaa2_switch_fdb_get_unused(port_priv->ethsw_data); + if (WARN_ON(!fdb)) + return 0; + port_priv->fdb = fdb; port_priv->fdb->in_use = true; port_priv->fdb->bridge_dev = NULL; From 74c1c9f5c0c30bbd0c2cf87b6e3507e7ea46c13d Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Thu, 28 May 2026 20:34:49 +0300 Subject: [PATCH 2/5] dpaa2-switch: fix the error path in dpaa2_switch_rx() In case of an error in dpaa2_switch_rx(), the dpaa2_switch_free_fd() function is called in order to free the FD. This is incorrect since the dpaa2_switch_free_fd() is intended to be used on Tx frame descriptors, meaning that it expects in the software annotation area of the FD data to find a valid skb pointer on which to call dev_kfree_skb(). Fix this by extracting the dma_unmap_page() from dpaa2_switch_build_linear_skb() directly into the dpaa2_switch_rx() function. This allows us to directly use free_pages() in case of an error before an SKB was created and kfree_skb() afterwards. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260528173452.1953102-3-ioana.ciornei@nxp.com Signed-off-by: Jakub Kicinski --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 782fef26b78e..53a32b21b959 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -2429,18 +2429,13 @@ static int dpaa2_switch_port_blocking_event(struct notifier_block *nb, /* Build a linear skb based on a single-buffer frame descriptor */ static struct sk_buff *dpaa2_switch_build_linear_skb(struct ethsw_core *ethsw, - const struct dpaa2_fd *fd) + const struct dpaa2_fd *fd, + void *fd_vaddr) { u16 fd_offset = dpaa2_fd_get_offset(fd); - dma_addr_t addr = dpaa2_fd_get_addr(fd); u32 fd_length = dpaa2_fd_get_len(fd); struct device *dev = ethsw->dev; struct sk_buff *skb = NULL; - void *fd_vaddr; - - fd_vaddr = dpaa2_iova_to_virt(ethsw->iommu_domain, addr); - dma_unmap_page(dev, addr, DPAA2_SWITCH_RX_BUF_SIZE, - DMA_FROM_DEVICE); skb = build_skb(fd_vaddr, DPAA2_SWITCH_RX_BUF_SIZE + SKB_DATA_ALIGN(sizeof(struct skb_shared_info))); @@ -2466,6 +2461,7 @@ static void dpaa2_switch_tx_conf(struct dpaa2_switch_fq *fq, static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, const struct dpaa2_fd *fd) { + dma_addr_t addr = dpaa2_fd_get_addr(fd); struct ethsw_core *ethsw = fq->ethsw; struct ethsw_port_priv *port_priv; struct net_device *netdev; @@ -2473,10 +2469,14 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, struct sk_buff *skb; u16 vlan_tci, vid; int if_id, err; + void *vaddr; + + vaddr = dpaa2_iova_to_virt(ethsw->iommu_domain, addr); + dma_unmap_page(ethsw->dev, addr, DPAA2_SWITCH_RX_BUF_SIZE, + DMA_FROM_DEVICE); /* get switch ingress interface ID */ if_id = upper_32_bits(dpaa2_fd_get_flc(fd)) & 0x0000FFFF; - if (if_id >= ethsw->sw_attr.num_ifs) { dev_err(ethsw->dev, "Frame received from unknown interface!\n"); goto err_free_fd; @@ -2492,7 +2492,7 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, } } - skb = dpaa2_switch_build_linear_skb(ethsw, fd); + skb = dpaa2_switch_build_linear_skb(ethsw, fd, vaddr); if (unlikely(!skb)) goto err_free_fd; @@ -2510,7 +2510,8 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, err = __skb_vlan_pop(skb, &vlan_tci); if (err) { dev_info(ethsw->dev, "__skb_vlan_pop() returned %d", err); - goto err_free_fd; + kfree_skb(skb); + return; } } @@ -2525,7 +2526,7 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, return; err_free_fd: - dpaa2_switch_free_fd(ethsw, fd); + free_pages((unsigned long)vaddr, 0); } static void dpaa2_switch_detect_features(struct ethsw_core *ethsw) From 9e9f5e2998052c49e506b8307e5bca7584600a3a Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Thu, 28 May 2026 20:34:50 +0300 Subject: [PATCH 3/5] dpaa2-switch: remove duplicated check for the maximum number of VLANs The check for the maximum number of VLANs is exactly duplicated twice in the dpaa2_switch_port_vlans_add() function. Remove one of the instances so that we do not have dead code. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260528173452.1953102-4-ioana.ciornei@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 53a32b21b959..4022635171b5 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -1811,16 +1811,6 @@ int dpaa2_switch_port_vlans_add(struct net_device *netdev, return -EEXIST; } - /* Check if there is space for a new VLAN */ - err = dpsw_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle, - ðsw->sw_attr); - if (err) { - netdev_err(netdev, "dpsw_get_attributes err %d\n", err); - return err; - } - if (attr->max_vlans - attr->num_vlans < 1) - return -ENOSPC; - /* Check if there is space for a new VLAN */ err = dpsw_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle, ðsw->sw_attr); From 9111f02861175854bcbd1d7ca4fff9d2b48bac62 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Thu, 28 May 2026 20:34:51 +0300 Subject: [PATCH 4/5] dpaa2-switch: support VLAN flag changes on existing VIDs The switchdev core notifies the driver on VLAN flag changes on existing VIDs through the changed field of the switchdev_obj_port_vlan structure. Without this patch, the driver was erroring out from the start if the same VID was inserted twice, from its perspective, even though the second call was actually a flag change. $ bridge vlan add dev eth2 vid 30 untagged $ bridge vlan add dev eth2 vid 30 [ 458.589534] fsl_dpaa2_switch dpsw.0 eth2: VLAN 30 already configured This patch fixes the above behavior by, first of all, removing the checks and return of errors on a VLAN already being installed. The patch also moves the sequence of code which checks if there is space for a new VLAN so that the verification is being done only for VLANs not know by the switch and not flag changes. A new parameter is added to the dpaa2_switch_port_add_vlan() function so that we pass the vlan->changed necessary information. Based on this new parameter and the flags value, the untagged flag will be added or removed from a VLAN installed on a port. The same thing is also extended to the PVID configuration. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260528173452.1953102-5-ioana.ciornei@nxp.com Signed-off-by: Jakub Kicinski --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 112 ++++++++++-------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 4022635171b5..505ccaa93ee4 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -298,49 +298,68 @@ static int dpaa2_switch_port_set_pvid(struct ethsw_port_priv *port_priv, u16 pvi } static int dpaa2_switch_port_add_vlan(struct ethsw_port_priv *port_priv, - u16 vid, u16 flags) + u16 vid, u16 flags, bool changed) { struct ethsw_core *ethsw = port_priv->ethsw_data; struct net_device *netdev = port_priv->netdev; 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 hit, this VLAN rule will lead the packet into the FDB table - * specified in the vlan configuration below - */ - vcfg.num_ifs = 1; - vcfg.if_id[0] = port_priv->idx; - vcfg.fdb_id = dpaa2_switch_port_get_fdb_id(port_priv); - vcfg.options |= DPSW_VLAN_ADD_IF_OPT_FDB_ID; - err = dpsw_vlan_add_if(ethsw->mc_io, 0, ethsw->dpsw_handle, vid, &vcfg); - if (err) { - netdev_err(netdev, "dpsw_vlan_add_if err %d\n", err); - return err; - } - - port_priv->vlans[vid] = ETHSW_VLAN_MEMBER; - - if (flags & BRIDGE_VLAN_INFO_UNTAGGED) { - err = dpsw_vlan_add_if_untagged(ethsw->mc_io, 0, - ethsw->dpsw_handle, - vid, &vcfg); + if (!port_priv->vlans[vid]) { + /* If hit, this VLAN rule will lead the packet into the FDB + * table specified in the vlan configuration below + */ + vcfg.num_ifs = 1; + vcfg.if_id[0] = port_priv->idx; + vcfg.fdb_id = dpaa2_switch_port_get_fdb_id(port_priv); + vcfg.options |= DPSW_VLAN_ADD_IF_OPT_FDB_ID; + err = dpsw_vlan_add_if(ethsw->mc_io, 0, ethsw->dpsw_handle, + vid, &vcfg); if (err) { - netdev_err(netdev, - "dpsw_vlan_add_if_untagged err %d\n", err); + netdev_err(netdev, "dpsw_vlan_add_if err %d\n", err); return err; } - port_priv->vlans[vid] |= ETHSW_VLAN_UNTAGGED; + + port_priv->vlans[vid] = ETHSW_VLAN_MEMBER; + } + + memset(&vcfg, 0, sizeof(vcfg)); + vcfg.num_ifs = 1; + vcfg.if_id[0] = port_priv->idx; + + if (flags & BRIDGE_VLAN_INFO_UNTAGGED) { + if (!(port_priv->vlans[vid] & ETHSW_VLAN_UNTAGGED)) { + err = dpsw_vlan_add_if_untagged(ethsw->mc_io, 0, + ethsw->dpsw_handle, + vid, &vcfg); + if (err) { + netdev_err(netdev, + "dpsw_vlan_add_if_untagged err %d\n", + err); + return err; + } + port_priv->vlans[vid] |= ETHSW_VLAN_UNTAGGED; + } + } else if (changed && (port_priv->vlans[vid] & ETHSW_VLAN_UNTAGGED)) { + err = dpsw_vlan_remove_if_untagged(ethsw->mc_io, 0, + ethsw->dpsw_handle, + vid, &vcfg); + if (err) { + netdev_err(netdev, + "dpsw_vlan_remove_if_untagged err %d\n", + err); + } + port_priv->vlans[vid] &= ~ETHSW_VLAN_UNTAGGED; } if (flags & BRIDGE_VLAN_INFO_PVID) { err = dpaa2_switch_port_set_pvid(port_priv, vid); if (err) return err; + } else if (changed && port_priv->vlans[vid] & ETHSW_VLAN_PVID) { + err = dpaa2_switch_port_set_pvid(port_priv, 4095); + if (err) + return err; } return 0; @@ -970,6 +989,7 @@ static int dpaa2_switch_port_vlan_add(struct net_device *netdev, __be16 proto, .obj.orig_dev = netdev, /* This API only allows programming tagged, non-PVID VIDs */ .flags = 0, + .changed = false, }; return dpaa2_switch_port_vlans_add(netdev, &vlan); @@ -1803,25 +1823,19 @@ 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; - } - - /* Check if there is space for a new VLAN */ - err = dpsw_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle, - ðsw->sw_attr); - if (err) { - netdev_err(netdev, "dpsw_get_attributes err %d\n", err); - return err; - } - if (attr->max_vlans - attr->num_vlans < 1) - return -ENOSPC; - if (!port_priv->ethsw_data->vlans[vlan->vid]) { + /* Only check for space in case this is a new VLAN from the + * DPSW perspective + */ + err = dpsw_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle, + ðsw->sw_attr); + if (err) { + netdev_err(netdev, "dpsw_get_attributes err %d\n", err); + return err; + } + if (attr->max_vlans - attr->num_vlans < 1) + return -ENOSPC; + /* this is a new VLAN */ err = dpaa2_switch_add_vlan(port_priv, vlan->vid); if (err) @@ -1830,7 +1844,8 @@ int dpaa2_switch_port_vlans_add(struct net_device *netdev, port_priv->ethsw_data->vlans[vlan->vid] |= ETHSW_VLAN_GLOBAL; } - return dpaa2_switch_port_add_vlan(port_priv, vlan->vid, vlan->flags); + return dpaa2_switch_port_add_vlan(port_priv, vlan->vid, vlan->flags, + vlan->changed); } static int dpaa2_switch_port_lookup_address(struct net_device *netdev, int is_uc, @@ -2146,7 +2161,8 @@ static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) * the dpaa2 switch interfaces are not capable to be VLAN unaware */ return dpaa2_switch_port_add_vlan(port_priv, DEFAULT_VLAN_ID, - BRIDGE_VLAN_INFO_UNTAGGED | BRIDGE_VLAN_INFO_PVID); + BRIDGE_VLAN_INFO_UNTAGGED | BRIDGE_VLAN_INFO_PVID, + false); } static int dpaa2_switch_prevent_bridging_with_8021q_upper(struct net_device *netdev) From e23d7c8c1d4ba435c457d7ffb2669175ec819b07 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Thu, 28 May 2026 20:34:52 +0300 Subject: [PATCH 5/5] dpaa2-switch: fix handling of NAPI on the remove path All the NAPI instances for a DPSW device are attached to the first switch port's net_device but shared by all ports. The NAPI instances get disabled only once the last port goes down. This causes an issue on the .remove() path where each port is unregistered and freed one at a time, causing the NAPI instances to be deleted even though they are not disabled. In order to avoid this, split up the unregister_netdev() calls from the free_netdev() so that we make sure all ports go down before we attempt a deletion of NAPI instances. Also, make the netif_napi_del() explicit as it is on the .probe() path. Signed-off-by: Ioana Ciornei Link: https://patch.msgid.link/20260528173452.1953102-6-ioana.ciornei@nxp.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/dpaa2/dpaa2-switch.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 505ccaa93ee4..a0bf5b50aae5 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -3304,7 +3304,6 @@ static void dpaa2_switch_teardown(struct fsl_mc_device *sw_dev) static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) { - struct ethsw_port_priv *port_priv; struct ethsw_core *ethsw; struct device *dev; int i; @@ -3316,11 +3315,17 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) dpsw_disable(ethsw->mc_io, 0, ethsw->dpsw_handle); - for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { - port_priv = ethsw->ports[i]; - unregister_netdev(port_priv->netdev); + /* Unregister all the netdevs so that they are brought down and the + * shared NAPI instances gets disabled. + */ + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + unregister_netdev(ethsw->ports[i]->netdev); + + for (i = 0; i < DPAA2_SWITCH_RX_NUM_FQS; i++) + netif_napi_del(ðsw->fq[i].napi); + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) dpaa2_switch_remove_port(ethsw, i); - } kfree(ethsw->fdbs); kfree(ethsw->filter_blocks);