diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-06-03 05:13:20 +0300 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-06-03 05:13:20 +0300 |
| commit | 2d5c859f8e7173adc189ff2afe6ed1a7025ff322 (patch) | |
| tree | af7fc619f288a4a752fbe13ca52a1f50668fde0d | |
| parent | cfa5274a5dc2a23b957da5dc806d2ac0c7a66af0 (diff) | |
| parent | e23d7c8c1d4ba435c457d7ffb2669175ec819b07 (diff) | |
| download | linux-2d5c859f8e7173adc189ff2afe6ed1a7025ff322.tar.xz | |
Merge branch 'dpaa2-switch-various-improvements'
Ioana Ciornei says:
====================
dpaa2-switch: various improvements
This patch set is a comprised of improvements and fixes for
long-standing bugs which were only caught by sashiko while reviewing the
LAG support patches for the dpaa2-switch:
https://lore.kernel.org/all/20260512131554.952971-1-ioana.ciornei@nxp.com/
In order to not just add to the already big set, I am submitting these
before any v3 of the LAG support patches.
The individual patches tackle FDB and VLAN management in the
dpaa2-switch driver as well as removal of some duplicated code. The
error path of the dpaa2_switch_rx() is also improved.
====================
Link: https://patch.msgid.link/20260528173452.1953102-1-ioana.ciornei@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 179 |
1 files changed, 104 insertions, 75 deletions
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 52c1cb9cb7e0..a0bf5b50aae5 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); - - /* 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. - */ + /* 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 (!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; @@ -281,49 +298,68 @@ set_tci_error: } 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 (!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 err %d\n", err); + return err; + } + + port_priv->vlans[vid] = ETHSW_VLAN_MEMBER; } - /* If hit, this VLAN rule will lead the packet into the FDB table - * specified in the vlan configuration below - */ + memset(&vcfg, 0, sizeof(vcfg)); 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] & 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_add_if_untagged err %d\n", err); - return err; + "dpsw_vlan_remove_if_untagged err %d\n", + err); } - port_priv->vlans[vid] |= ETHSW_VLAN_UNTAGGED; + 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; @@ -953,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); @@ -1786,35 +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; - - /* 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) @@ -1823,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, @@ -2139,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) @@ -2412,18 +2435,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))); @@ -2449,6 +2467,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; @@ -2456,10 +2475,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; @@ -2475,7 +2498,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; @@ -2493,7 +2516,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; } } @@ -2508,7 +2532,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) @@ -3280,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; @@ -3292,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); |
