summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-06-03 05:13:20 +0300
committerJakub Kicinski <kuba@kernel.org>2026-06-03 05:13:20 +0300
commit2d5c859f8e7173adc189ff2afe6ed1a7025ff322 (patch)
treeaf7fc619f288a4a752fbe13ca52a1f50668fde0d
parentcfa5274a5dc2a23b957da5dc806d2ac0c7a66af0 (diff)
parente23d7c8c1d4ba435c457d7ffb2669175ec819b07 (diff)
downloadlinux-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.c179
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 = &ethsw->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,
- &ethsw->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,
- &ethsw->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,
+ &ethsw->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(&ethsw->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);