* [PATCH net-next 0/9] net: dsa: netc: add bridge mode support
@ 2026-05-27 10:02 wei.fang
2026-05-27 10:02 ` [PATCH net-next 1/9] net: enetc: add interfaces to manage FDB entries wei.fang
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
This series adds bridge mode support to the NETC DSA switch driver,
covering both VLAN-aware and VLAN-unaware operation.
The NETC switch manages forwarding through a set of hardware tables
accessed via NTMP: the FDB table (FDBT), VLAN filter table (VFT), egress
treatment table (ETT), and egress count table (ECT). The series extends
the NTMP layer with the operations required for bridging, then builds the
DSA bridge callbacks on top.
Since all switch ports share the VFT, so only one VLAN-aware bridge is
supported.
FDB aging is managed in software. A periodic delayed work sweeps the
table using the hardware activity element mechanism, with a default aging
time of 300 seconds matching the IEEE 802.1Q standard. Per-port entries
are also flushed immediately on bridge leave and link-down events.
Wei Fang (9):
net: enetc: add interfaces to manage FDB entries
net: enetc: add "Update" and "Delete" operations to VLAN filter table
net: enetc: add interfaces to manage egress treatment table
net: enetc: add "Update" operation to the egress count table
net: dsa: netc: initialize the group bitmap of ETT and ECT
net: enetc: add helpers to set/clear table bitmap
net: dsa: netc: add VLAN filter table and egress treatment management
net: dsa: netc: add bridge mode support
net: dsa: netc: implement dynamic FDB entry aging
drivers/net/dsa/netc/netc_main.c | 882 +++++++++++++++++-
drivers/net/dsa/netc/netc_switch.h | 34 +
drivers/net/dsa/netc/netc_switch_hw.h | 6 +
drivers/net/ethernet/freescale/enetc/ntmp.c | 431 +++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 14 +
include/linux/fsl/ntmp.h | 60 ++
6 files changed, 1413 insertions(+), 14 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net-next 1/9] net: enetc: add interfaces to manage FDB entries
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 2/9] net: enetc: add "Update" and "Delete" operations to VLAN filter table wei.fang
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
Add four interfaces to manage entries in the FDB table:
ntmp_fdbt_delete_entry_by_keye(): Delete the specified FDB entry using
key element data. It can be used to delete dynamic FDB entries as well
as static FDB entries.
ntmp_fdbt_update_activity_element(): Update the activity element of all
dynamic FDB entries. For each entry, if its activity flag is not set,
which means no packet has matched this entry since the last update, the
activity counter is incremented. Otherwise, both the activity flag and
activity counter are reset. The activity counter is used to track how
long an FDB entry has been inactive, which is useful for implementing
an aging mechanism.
ntmp_fdbt_delete_aging_entries(): Delete all dynamic FDB entries whose
activity flag is not set and whose activity counter is greater than or
equal to the specified threshold. This is used to remove stale entries
that have been inactive for too long.
ntmp_fdbt_delete_port_dynamic_entries(): Delete all dynamic FDB entries
associated with the specified switch port. This is typically called when
a port goes down or is removed from a bridge.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 206 ++++++++++++++++++++
include/linux/fsl/ntmp.h | 5 +
2 files changed, 211 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index f71cad943424..2e79515e1772 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -31,6 +31,7 @@
#define NTMP_GEN_UA_STSEU BIT(1)
/* Specific Update Actions for some tables */
+#define FDBT_UA_ACTEU BIT(1)
#define BPT_UA_BPSEU BIT(1)
/* Query Action: 0: Full query. 1: Query entry ID, the fields after entry
@@ -722,6 +723,49 @@ int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id)
}
EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_entry);
+/**
+ * ntmp_fdbt_delete_entry_by_keye - delete the specified FDB entry using
+ * key element data
+ * @user: target ntmp_user struct
+ * @keye: key element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_entry_by_keye(struct ntmp_user *user,
+ const struct fdbt_keye_data *keye)
+{
+ struct fdbt_req_qd *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0);
+ req->ak.exact.keye = *keye;
+
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_DELETE, NTMP_AM_EXACT_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev, "Failed to delete %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_entry_by_keye);
+
/**
* ntmp_fdbt_search_port_entry - Search the FDB entry on the specified
* port based on RESUME_ENTRY_ID
@@ -793,6 +837,168 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
}
EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+/**
+ * ntmp_fdbt_update_activity_element - update the aging time of all the
+ * dynamic entries in the FDB table.
+ * @user: target ntmp_user struct
+ *
+ * A single activity update management could be used to process all the
+ * dynamic entries in the FDB table. When hardware process an activity
+ * update management command for an entry in the FDB table and the entry
+ * does not have its activity flag set, the activity counter is incremented.
+ * However, if the activity flag is set, then both the activity flag and
+ * activity counter are reset. Software can issue the activity update
+ * management commands at predefined times and the value of the activity
+ * counter can then be used to estimate the period of how long an FDB
+ * entry has been inactive.
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_update_activity_element(struct ntmp_user *user)
+{
+ struct fdbt_req_ua *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, FDBT_UA_ACTEU);
+ req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ /* For activity update, the access method must be search */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_UPDATE, NTMP_AM_SEARCH);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update activity of %s, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_update_activity_element);
+
+/**
+ * ntmp_fdbt_delete_aging_entries - delete all the aging dynamic entries
+ * in the FDB table
+ * @user: target ntmp_user struct
+ * @act_cnt: the target value of the activity counter
+ *
+ * The matching rule is that the activity flag is not set and the activity
+ * counter is greater than or equal to act_cnt
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_aging_entries(struct ntmp_user *user, u8 act_cnt)
+{
+ struct fdbt_req_qd *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ u32 cfg = FDBT_DYNAMIC;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ if (act_cnt > FDBT_ACT_CNT)
+ return -EINVAL;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0);
+ req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ req->ak.search.cfge.cfg = cpu_to_le32(cfg);
+ req->ak.search.acte = act_cnt;
+ /* Exact match with ACTE_DATA[ACT_FLAG] AND
+ * match >= ACTE_DATA[ACT_CNT]
+ */
+ req->ak.search.acte_mc = FDBT_ACTE_MC;
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ /* For activity update, the access method must be search */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_DELETE, NTMP_AM_SEARCH);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to delete aging entries of %s, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_aging_entries);
+
+/**
+ * ntmp_fdbt_delete_port_dynamic_entries - delete all the aging dynamic
+ * entries of the specified switch port
+ * @user: target ntmp_user struct
+ * @port: the specified switch port ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port)
+{
+ struct fdbt_req_qd *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ u32 cfg = FDBT_DYNAMIC;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0);
+ req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port));
+ req->ak.search.cfge.cfg = cpu_to_le32(cfg);
+ /* Match CFGE_DATA[DYNAMIC & PORT_BITMAP] field */
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_DELETE, NTMP_AM_SEARCH);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to delete dynamic %s entries on port %d, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_port_dynamic_entries);
+
/**
* ntmp_vft_add_entry - add an entry into the VLAN filter table
* @user: target ntmp_user struct
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 88166f9ad3a2..f18556b072c8 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -260,9 +260,14 @@ int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct fdbt_cfge_data *cfge);
int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id);
+int ntmp_fdbt_delete_entry_by_keye(struct ntmp_user *user,
+ const struct fdbt_keye_data *keye);
int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
u32 *resume_entry_id,
struct fdbt_entry_data *entry);
+int ntmp_fdbt_update_activity_element(struct ntmp_user *user);
+int ntmp_fdbt_delete_aging_entries(struct ntmp_user *user, u8 act_cnt);
+int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port);
int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 2/9] net: enetc: add "Update" and "Delete" operations to VLAN filter table
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
2026-05-27 10:02 ` [PATCH net-next 1/9] net: enetc: add interfaces to manage FDB entries wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 3/9] net: enetc: add interfaces to manage egress treatment table wei.fang
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
Add two interfaces to manage entries in the VLAN filter table:
ntmp_vft_update_entry(): Update the configuration element data of the
specified VLAN filter entry based on the given VLAN ID. It uses the
exact key access method to locate the entry.
ntmp_vft_delete_entry(): Delete the VLAN filter entry corresponding to
the specified VLAN ID. It also uses the exact key access method to
identify the target entry.
In addition, introduce struct vft_req_qd to describe the request data
buffer format for Query and Delete actions of the VLAN filter table,
which contains a common request data header and a VLAN access key.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 91 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 6 ++
include/linux/fsl/ntmp.h | 3 +
3 files changed, 100 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 2e79515e1772..85abf9303bac 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -1046,6 +1046,97 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
}
EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+/**
+ * ntmp_vft_update_entry - update the configuration element data of the
+ * specified VLAN filter entry
+ * @user: target ntmp_user struct
+ * @vid: VLAN ID
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge)
+{
+ struct netc_swcbd swcbd;
+ struct vft_req_ua *req;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, NTMP_GEN_UA_CFGEU);
+ req->ak.exact.vid = cpu_to_le16(vid);
+ req->cfge = *cfge;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID,
+ NTMP_CMD_UPDATE, NTMP_AM_EXACT_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update %s entry, vid: %u, err: %pe\n",
+ ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_vft_update_entry);
+
+/**
+ * ntmp_vft_delete_entry - delete the VLAN filter entry based on the
+ * specified VLAN ID
+ * @user: target ntmp_user struct
+ * @vid: VLAN ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid)
+{
+ struct netc_swcbd swcbd;
+ struct vft_req_qd *req;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, 0);
+ req->ak.exact.vid = cpu_to_le16(vid);
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID,
+ NTMP_CMD_DELETE, NTMP_AM_EXACT_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to delete %s entry, vid: %u, err: %pe\n",
+ ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_vft_delete_entry);
+
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge)
{
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 0a9b87286105..ec2a1cdb0768 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -211,6 +211,12 @@ struct vft_req_ua {
struct vft_cfge_data cfge;
};
+/* VLAN Filter Table Request Data Buffer Format of Query and Delete actions */
+struct vft_req_qd {
+ struct ntmp_cmn_req_data crd;
+ union vft_access_key ak;
+};
+
/* Buffer Pool Table Request Data Buffer Format of Update action */
struct bpt_req_update {
struct ntmp_req_by_eid rbe;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index f18556b072c8..f34151cc160e 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -270,6 +270,9 @@ int ntmp_fdbt_delete_aging_entries(struct ntmp_user *user, u8 act_cnt);
int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port);
int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
+int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge);
+int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge);
#else
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 3/9] net: enetc: add interfaces to manage egress treatment table
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
2026-05-27 10:02 ` [PATCH net-next 1/9] net: enetc: add interfaces to manage FDB entries wei.fang
2026-05-27 10:02 ` [PATCH net-next 2/9] net: enetc: add "Update" and "Delete" operations to VLAN filter table wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 4/9] net: enetc: add "Update" operation to the egress count table wei.fang
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
Each entry in the egress treatment table contains the egress packet
processing actions to be applied to a grouping or scope of packets
exiting on a particular egress port of the switch. A scope of packets,
for example, could be the packets exiting a particular VLAN, matching
a particular 802.1Q bridge forwarding entry or belonging to a stream
identified at ingress. The egress treatment table is implemented as a
linear array of entries accessed using an index (0,1, 2, ..., n) that
uniquely identifies an entry within the array.
The egress treatment table only supports access vid entry ID, which is
assigned by the software. It supports Add, Update, Delete and Query
operations. Note that only Query operation is not supported yet.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 65 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 8 +++
include/linux/fsl/ntmp.h | 21 ++++++
3 files changed, 94 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 85abf9303bac..6f81f90af7b0 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -24,6 +24,7 @@
#define NTMP_IPFT_ID 13
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
+#define NTMP_ETT_ID 33
#define NTMP_BPT_ID 41
/* Generic Update Actions for most tables */
@@ -284,6 +285,8 @@ static const char *ntmp_table_name(int tbl_id)
return "FDB Table";
case NTMP_VFT_ID:
return "VLAN Filter Table";
+ case NTMP_ETT_ID:
+ return "Egress Treatment Table";
case NTMP_BPT_ID:
return "Buffer Pool Table";
default:
@@ -1137,6 +1140,68 @@ int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid)
}
EXPORT_SYMBOL_GPL(ntmp_vft_delete_entry);
+/**
+ * ntmp_ett_add_or_update_entry - add a new entry to the egress treatment
+ * table or update the configuration element data of the specified entry
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ * @add: true means add operation, false means update operation
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ett_add_or_update_entry(struct ntmp_user *user, u32 entry_id,
+ bool add, const struct ett_cfge_data *cfge)
+{
+ int cmd = add ? NTMP_CMD_ADD : NTMP_CMD_UPDATE;
+ struct netc_swcbd swcbd;
+ struct ett_req_ua *req;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd_eid(&req->rbe, user->tbl.ett_ver, 0,
+ NTMP_GEN_UA_CFGEU, entry_id);
+ req->cfge = *cfge;
+
+ /* Request header */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0),
+ NTMP_ETT_ID, cmd, NTMP_AM_ENTRY_ID);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to %s %s entry 0x%x, err :%pe\n",
+ add ? "add" : "update", ntmp_table_name(NTMP_ETT_ID),
+ entry_id, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ett_add_or_update_entry);
+
+/**
+ * ntmp_ett_delete_entry - delete the specified egress treatment table entry
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ return ntmp_delete_entry_by_id(user, NTMP_ETT_ID, user->tbl.ett_ver,
+ entry_id, NTMP_EID_REQ_LEN, 0);
+}
+EXPORT_SYMBOL_GPL(ntmp_ett_delete_entry);
+
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge)
{
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index ec2a1cdb0768..0309f98b297a 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -217,6 +217,14 @@ struct vft_req_qd {
union vft_access_key ak;
};
+/* Egress Treatment Table Request Data Buffer Format of Update and Add
+ * actions
+ */
+struct ett_req_ua {
+ struct ntmp_req_by_eid rbe;
+ struct ett_cfge_data cfge;
+};
+
/* Buffer Pool Table Request Data Buffer Format of Update action */
struct bpt_req_update {
struct ntmp_req_by_eid rbe;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index f34151cc160e..d433b2317123 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -36,6 +36,7 @@ struct netc_tbl_vers {
u8 vft_ver;
u8 bpt_ver;
u8 ipft_ver;
+ u8 ett_ver;
};
struct netc_swcbd {
@@ -214,6 +215,23 @@ struct vft_cfge_data {
__le32 et_eid;
};
+struct ett_cfge_data {
+ __le16 efm_cfg;
+#define ETT_EFM_MODE GENMASK(1, 0)
+#define ETT_ESQA GENMASK(5, 4)
+#define ETT_ECA GENMASK(8, 6)
+#define ETT_ECA_INC 1
+#define ETT_EFM_LEN_CHANGE GENMASK(15, 9)
+#define ETT_FRM_LEN_DEL_VLAN 0x7c
+#define ETT_FRM_LEN_DEL_RTAG 0x7a
+#define ETT_FRM_LEN_DEL_VLAN_RTAG 0x76
+ __le16 efm_data_len;
+#define ETT_EFM_DATA_LEN GENMASK(10, 0)
+ __le32 efm_eid;
+ __le32 ec_eid;
+ __le32 esqa_tgt_eid;
+};
+
struct bpt_bpse_data {
__le32 amount_used;
__le32 amount_used_hwm;
@@ -273,6 +291,9 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid);
+int ntmp_ett_add_or_update_entry(struct ntmp_user *user, u32 entry_id,
+ bool add, const struct ett_cfge_data *cfge);
+int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge);
#else
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 4/9] net: enetc: add "Update" operation to the egress count table
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
` (2 preceding siblings ...)
2026-05-27 10:02 ` [PATCH net-next 3/9] net: enetc: add interfaces to manage egress treatment table wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 5/9] net: dsa: netc: initialize the group bitmap of ETT and ECT wei.fang
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
The egress count table is a static bounded index table, egress related
statistics are maintained in this table. The table is implemented as a
linear array of entries accessed using an index (0, 1, 2, ..., n) that
uniquely identifies an entry within the array. Egress Counter Entry ID
(EC_EID) is used as an index to an entry in this table. The EC_EID is
specified in the egress treatment table.
Egress count table entries are always present and enabled. The table
only supports access via entry ID, which is assigned by the software.
And it supports Update, Query and Query followed by Update operations.
Currently, only Update operation is supported.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 45 +++++++++++++++++++++
include/linux/fsl/ntmp.h | 7 ++++
2 files changed, 52 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 6f81f90af7b0..60e2ed68ed62 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -25,6 +25,7 @@
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
#define NTMP_ETT_ID 33
+#define NTMP_ECT_ID 39
#define NTMP_BPT_ID 41
/* Generic Update Actions for most tables */
@@ -33,6 +34,7 @@
/* Specific Update Actions for some tables */
#define FDBT_UA_ACTEU BIT(1)
+#define ECT_UA_STSEU BIT(0)
#define BPT_UA_BPSEU BIT(1)
/* Query Action: 0: Full query. 1: Query entry ID, the fields after entry
@@ -287,6 +289,8 @@ static const char *ntmp_table_name(int tbl_id)
return "VLAN Filter Table";
case NTMP_ETT_ID:
return "Egress Treatment Table";
+ case NTMP_ECT_ID:
+ return "Egress Count Table";
case NTMP_BPT_ID:
return "Buffer Pool Table";
default:
@@ -1202,6 +1206,47 @@ int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id)
}
EXPORT_SYMBOL_GPL(ntmp_ett_delete_entry);
+/**
+ * ntmp_ect_update_entry - reset the statistics element data of the
+ * specified egress counter table entry
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id)
+{
+ struct ntmp_req_by_eid *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd_eid(req, user->tbl.ect_ver, 0, ECT_UA_STSEU, entry_id);
+
+ /* Request header */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0),
+ NTMP_ECT_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update %s entry 0x%x, err: %pe\n",
+ ntmp_table_name(NTMP_ECT_ID), entry_id, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ect_update_entry);
+
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge)
{
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index d433b2317123..e3d4ea015391 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -37,6 +37,7 @@ struct netc_tbl_vers {
u8 bpt_ver;
u8 ipft_ver;
u8 ett_ver;
+ u8 ect_ver;
};
struct netc_swcbd {
@@ -232,6 +233,11 @@ struct ett_cfge_data {
__le32 esqa_tgt_eid;
};
+struct ect_stse_data {
+ __le64 enq_frm_cnt;
+ __le64 rej_frm_cnt;
+};
+
struct bpt_bpse_data {
__le32 amount_used;
__le32 amount_used_hwm;
@@ -294,6 +300,7 @@ int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid);
int ntmp_ett_add_or_update_entry(struct ntmp_user *user, u32 entry_id,
bool add, const struct ett_cfge_data *cfge);
int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id);
+int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge);
#else
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 5/9] net: dsa: netc: initialize the group bitmap of ETT and ECT
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
` (3 preceding siblings ...)
2026-05-27 10:02 ` [PATCH net-next 4/9] net: enetc: add "Update" operation to the egress count table wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 6/9] net: enetc: add helpers to set/clear table bitmap wei.fang
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
The Egress Treatment Table (ETT) and Egress Count Table (ECT) are both
index tables whose entry IDs are allocated by software. Every num_ports
entries form a group, where each entry in the group corresponds to one
port. To facilitate group allocation and management, initialize the group
index bitmaps for both tables based on hardware capabilities reported by
ETTCAPR and ECTCAPR registers.
The bitmap size per table is calculated as the total number of hardware
entries divided by the number of available ports, which gives the number
of groups available for software allocation. A set bit in the bitmap
represents a group index that has been allocated.
These bitmaps will be used by subsequent patches that add VLAN support.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/dsa/netc/netc_main.c | 90 ++++++++++++++++++++++++++-
drivers/net/dsa/netc/netc_switch_hw.h | 6 ++
include/linux/fsl/ntmp.h | 7 +++
3 files changed, 102 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index fa7dd307ce13..d4475ad7ed6c 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -323,16 +323,104 @@ static void netc_remove_all_cbdrs(struct netc_switch *priv)
ntmp_free_cbdr(&ntmp->ring[i]);
}
+static u32 netc_num_available_ports(struct netc_switch *priv)
+{
+ struct dsa_port *dp;
+ u32 num_ports = 0;
+
+ dsa_switch_for_each_available_port(dp, priv->ds)
+ num_ports++;
+
+ return num_ports;
+}
+
+static int netc_init_ntmp_bitmap_sizes(struct netc_switch *priv)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct netc_switch_regs *regs = &priv->regs;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 val;
+
+ if (!num_ports)
+ return -EINVAL;
+
+ val = netc_base_rd(regs, NETC_ETTCAPR);
+ ntmp->ett_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports;
+ if (!ntmp->ett_bitmap_size)
+ return -EINVAL;
+
+ val = netc_base_rd(regs, NETC_ECTCAPR);
+ ntmp->ect_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports;
+ if (!ntmp->ect_bitmap_size)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int netc_init_ntmp_bitmaps(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ ntmp->ett_gid_bitmap = bitmap_zalloc(ntmp->ett_bitmap_size,
+ GFP_KERNEL);
+ if (!ntmp->ett_gid_bitmap)
+ return -ENOMEM;
+
+ ntmp->ect_gid_bitmap = bitmap_zalloc(ntmp->ect_bitmap_size,
+ GFP_KERNEL);
+ if (!ntmp->ect_gid_bitmap)
+ goto free_ett_gid_bitmap;
+
+ return 0;
+
+free_ett_gid_bitmap:
+ bitmap_free(ntmp->ett_gid_bitmap);
+ ntmp->ett_gid_bitmap = NULL;
+
+ return -ENOMEM;
+}
+
+static void netc_free_ntmp_bitmaps(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ bitmap_free(ntmp->ect_gid_bitmap);
+ ntmp->ect_gid_bitmap = NULL;
+
+ bitmap_free(ntmp->ett_gid_bitmap);
+ ntmp->ett_gid_bitmap = NULL;
+}
+
static int netc_init_ntmp_user(struct netc_switch *priv)
{
+ int err;
+
netc_init_ntmp_tbl_versions(priv);
- return netc_init_all_cbdrs(priv);
+ err = netc_init_ntmp_bitmap_sizes(priv);
+ if (err)
+ return err;
+
+ err = netc_init_ntmp_bitmaps(priv);
+ if (err)
+ return err;
+
+ err = netc_init_all_cbdrs(priv);
+ if (err)
+ goto free_ntmp_bitmaps;
+
+ return 0;
+
+free_ntmp_bitmaps:
+ netc_free_ntmp_bitmaps(priv);
+
+ return err;
}
static void netc_free_ntmp_user(struct netc_switch *priv)
{
netc_remove_all_cbdrs(priv);
+ netc_free_ntmp_bitmaps(priv);
}
static void netc_switch_dos_default_config(struct netc_switch *priv)
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index 1d976882a6cc..1404ae41c7bc 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -36,6 +36,12 @@
#define DOSL3CR_SAMEADDR BIT(0)
#define DOSL3CR_IPSAMCC BIT(1)
+#define NETC_ETTCAPR 0x18c4
+#define NETC_ECTCAPR 0x18ec
+/* Index table NUM_ENTRIES mask */
+#define NETC_NUM_ENTRIES GENMASK(15, 0)
+#define NETC_GET_NUM_ENTRIES(v) FIELD_GET(NETC_NUM_ENTRIES, (v))
+
/* Hash table memory capability register, the memory is shared by
* the following tables:
*
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index e3d4ea015391..b9d4a29abd23 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -3,6 +3,7 @@
#ifndef __NETC_NTMP_H
#define __NETC_NTMP_H
+#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/if_ether.h>
@@ -70,6 +71,12 @@ struct ntmp_user {
struct device *dev;
struct netc_cbdr *ring;
struct netc_tbl_vers tbl;
+
+ /* NTMP table bitmaps for resource management */
+ u32 ett_bitmap_size;
+ u32 ect_bitmap_size;
+ unsigned long *ett_gid_bitmap; /* only valid for switch */
+ unsigned long *ect_gid_bitmap; /* only valid for switch */
};
struct maft_entry_data {
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 6/9] net: enetc: add helpers to set/clear table bitmap
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
` (4 preceding siblings ...)
2026-05-27 10:02 ` [PATCH net-next 5/9] net: dsa: netc: initialize the group bitmap of ETT and ECT wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 7/9] net: dsa: netc: add VLAN filter table and egress treatment management wei.fang
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
NTMP index tables require software to allocate and manage entry IDs.
Add two bitmap helper functions to facilitate this management:
ntmp_lookup_free_eid(): finds the first zero bit in the given bitmap,
sets it to mark the entry as in-use, and returns the corresponding entry
ID. Returns NTMP_NULL_ENTRY_ID if no free entry is available.
ntmp_clear_eid_bitmap(): clears the bit associated with the given entry
ID in the bitmap to mark the entry as free. It is a no-op if the entry
ID is NTMP_NULL_ENTRY_ID.
Both functions are exported for use by other modules, such as the NETC
switch driver which needs to manage group index bitmaps for the Egress
Treatment Table (ETT) and Egress Count Table (ECT).
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 24 +++++++++++++++++++++
include/linux/fsl/ntmp.h | 2 ++
2 files changed, 26 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 60e2ed68ed62..98f8e41e0267 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -47,6 +47,30 @@
#define RSST_STSE_DATA_SIZE(n) ((n) * 8)
#define RSST_CFGE_DATA_SIZE(n) (n)
+u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size)
+{
+ u32 entry_id;
+
+ entry_id = find_first_zero_bit(bitmap, size);
+ if (entry_id == size)
+ return NTMP_NULL_ENTRY_ID;
+
+ /* Set the bit once we found it */
+ set_bit(entry_id, bitmap);
+
+ return entry_id;
+}
+EXPORT_SYMBOL_GPL(ntmp_lookup_free_eid);
+
+void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id)
+{
+ if (entry_id == NTMP_NULL_ENTRY_ID)
+ return;
+
+ clear_bit(entry_id, bitmap);
+}
+EXPORT_SYMBOL_GPL(ntmp_clear_eid_bitmap);
+
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
{
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index b9d4a29abd23..a678f1e1ee42 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -271,6 +271,8 @@ struct bpt_cfge_data {
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
void ntmp_free_cbdr(struct netc_cbdr *cbdr);
+u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size);
+void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id);
/* NTMP APIs */
int ntmp_maft_add_entry(struct ntmp_user *user, u32 entry_id,
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 7/9] net: dsa: netc: add VLAN filter table and egress treatment management
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
` (5 preceding siblings ...)
2026-05-27 10:02 ` [PATCH net-next 6/9] net: enetc: add helpers to set/clear table bitmap wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 8/9] net: dsa: netc: add bridge mode support wei.fang
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
Implement the DSA .port_vlan_add and .port_vlan_del operations to
enable VLAN-aware bridge offloading on the NETC switch.
VLAN membership is maintained in the VLAN Filter Table (VFT). Adding
the first port to a VLAN creates a new VFT entry with hardware MAC
learning and flood-on-miss forwarding; subsequent ports update the
existing entry's membership bitmap. Removing the last port deletes
the entry.
Egress tagging is handled through the Egress Treatment Table (ETT).
Each VLAN is allocated a group of ETT entries, one per available port.
Ports are assigned a sequential ett_offset during initialisation, used
to address each port's entry within the group. Untagged ports configure
the ETT to strip the outer VLAN tag; tagged ports pass frames through
unmodified. Each ETT group is optionally paired with an Egress Counter
Table (ECT) group for per-port frame counting, allocated on a
best-effort basis.
A software shadow list serialised by vft_lock tracks active VLAN state
across both port membership and egress tagging. VID 0 is used for single
port mode and is ignored by both callbacks.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/dsa/netc/netc_main.c | 423 +++++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_switch.h | 24 ++
include/linux/fsl/ntmp.h | 15 +
3 files changed, 462 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index d4475ad7ed6c..4db42c888470 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -37,6 +37,27 @@ static void netc_destroy_fdb_list(struct netc_switch *priv)
netc_del_fdb_entry(entry);
}
+static struct netc_vlan_entry *
+netc_lookup_vlan_entry(struct netc_switch *priv, u16 vid)
+{
+ struct netc_vlan_entry *entry;
+
+ hlist_for_each_entry(entry, &priv->vlan_list, node)
+ if (entry->vid == vid)
+ return entry;
+
+ return NULL;
+}
+
+static void netc_destroy_vlan_list(struct netc_switch *priv)
+{
+ struct netc_vlan_entry *entry;
+ struct hlist_node *tmp;
+
+ hlist_for_each_entry_safe(entry, tmp, &priv->vlan_list, node)
+ netc_del_vlan_entry(entry);
+}
+
static enum dsa_tag_protocol
netc_get_tag_protocol(struct dsa_switch *ds, int port,
enum dsa_tag_protocol mprot)
@@ -222,6 +243,7 @@ static int netc_init_all_ports(struct netc_switch *priv)
struct device *dev = priv->dev;
struct netc_port *np;
struct dsa_port *dp;
+ int ett_offset = 0;
int err;
priv->ports = devm_kcalloc(dev, priv->info->num_ports,
@@ -251,6 +273,8 @@ static int netc_init_all_ports(struct netc_switch *priv)
dsa_switch_for_each_available_port(dp, priv->ds) {
np = priv->ports[dp->index];
np->dp = dp;
+ np->ett_offset = ett_offset++;
+ priv->port_bitmap |= BIT(dp->index);
err = netc_port_get_info_from_dt(np, dp->dn, dev);
if (err)
@@ -831,6 +855,8 @@ static int netc_setup(struct dsa_switch *ds)
INIT_HLIST_HEAD(&priv->fdb_list);
mutex_init(&priv->fdbt_lock);
+ INIT_HLIST_HEAD(&priv->vlan_list);
+ mutex_init(&priv->vft_lock);
netc_switch_fixed_config(priv);
@@ -858,6 +884,7 @@ static int netc_setup(struct dsa_switch *ds)
* hardware state.
*/
mutex_destroy(&priv->fdbt_lock);
+ mutex_destroy(&priv->vft_lock);
netc_free_ntmp_user(priv);
return err;
@@ -867,6 +894,8 @@ static void netc_destroy_all_lists(struct netc_switch *priv)
{
netc_destroy_fdb_list(priv);
mutex_destroy(&priv->fdbt_lock);
+ netc_destroy_vlan_list(priv);
+ mutex_destroy(&priv->vft_lock);
}
static void netc_free_host_flood_rules(struct netc_switch *priv)
@@ -1025,6 +1054,369 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv)
priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
}
+static int netc_add_or_update_ett_entry(struct netc_switch *priv,
+ bool add, bool untagged,
+ u32 ett_eid, u32 ect_eid)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 vuda_sqta = FMTEID_VUDA_SQTA;
+ struct ett_cfge_data cfge = {};
+ u16 efm_cfg = 0;
+
+ if (ect_eid != NTMP_NULL_ENTRY_ID) {
+ /* Increase egress frame counter */
+ efm_cfg |= FIELD_PREP(ETT_ECA, ETT_ECA_INC);
+ cfge.ec_eid = cpu_to_le32(ect_eid);
+ }
+
+ /* If egress rule is VLAN untagged */
+ if (untagged) {
+ /* delete outer VLAN tag */
+ vuda_sqta |= FIELD_PREP(FMTEID_VUDA, FMTEID_VUDA_DEL_OTAG);
+ /* length change: twos-complement notation */
+ efm_cfg |= FIELD_PREP(ETT_EFM_LEN_CHANGE,
+ ETT_FRM_LEN_DEL_VLAN);
+ }
+
+ cfge.efm_eid = cpu_to_le32(vuda_sqta);
+ cfge.efm_cfg = cpu_to_le16(efm_cfg);
+
+ return ntmp_ett_add_or_update_entry(ntmp, ett_eid, add, &cfge);
+}
+
+static int netc_add_ett_group_entries(struct netc_switch *priv,
+ u32 untagged_port_bitmap,
+ u32 ett_base_eid,
+ u32 ect_base_eid)
+{
+ struct netc_port **ports = priv->ports;
+ u32 ett_eid, ect_eid;
+ bool untagged;
+ int i, err;
+
+ for (i = 0; i < priv->info->num_ports; i++) {
+ if (!ports[i]->dp)
+ continue;
+
+ untagged = !!(untagged_port_bitmap & BIT(i));
+ ett_eid = ett_base_eid + ports[i]->ett_offset;
+ ect_eid = NTMP_NULL_ENTRY_ID;
+ if (ect_base_eid != NTMP_NULL_ENTRY_ID)
+ ect_eid = ect_base_eid + ports[i]->ett_offset;
+
+ err = netc_add_or_update_ett_entry(priv, true, untagged,
+ ett_eid, ect_eid);
+ if (err)
+ goto clear_ett_entries;
+ }
+
+ return 0;
+
+clear_ett_entries:
+ while (--i >= 0) {
+ if (!ports[i]->dp)
+ continue;
+
+ ett_eid = ett_base_eid + ports[i]->ett_offset;
+ ntmp_ett_delete_entry(&priv->ntmp, ett_eid);
+ }
+
+ return err;
+}
+
+static int netc_add_vlan_egress_rule(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 ect_eid = NTMP_NULL_ENTRY_ID;
+ u32 ett_eid, ett_gid, ect_gid;
+ int err;
+
+ /* Step 1: Find available egress counter table entries and update
+ * these entries.
+ */
+ ect_gid = ntmp_lookup_free_eid(ntmp->ect_gid_bitmap,
+ ntmp->ect_bitmap_size);
+ if (ect_gid == NTMP_NULL_ENTRY_ID) {
+ dev_info(priv->dev,
+ "No egress counter table entries available\n");
+ } else {
+ ect_eid = ect_gid * num_ports;
+ for (int i = 0; i < num_ports; i++)
+ /* Reset the counters of the entry. There is no need
+ * to check the return value, the only issue is that
+ * the entry's counter might be inaccurate, but it
+ * will not affect the functionality.
+ */
+ ntmp_ect_update_entry(ntmp, ect_eid + i);
+ }
+
+ /* Step 2: Find available egress treatment table entries and add
+ * these entries.
+ */
+ ett_gid = ntmp_lookup_free_eid(ntmp->ett_gid_bitmap,
+ ntmp->ett_bitmap_size);
+ if (ett_gid == NTMP_NULL_ENTRY_ID) {
+ dev_err(priv->dev,
+ "No egress treatment table entries available\n");
+ err = -ENOSPC;
+ goto clear_ect_gid;
+ }
+
+ ett_eid = ett_gid * num_ports;
+ err = netc_add_ett_group_entries(priv, entry->untagged_port_bitmap,
+ ett_eid, ect_eid);
+ if (err)
+ goto clear_ett_gid;
+
+ entry->cfge.et_eid = cpu_to_le32(ett_eid);
+ entry->ect_gid = ect_gid;
+
+ return 0;
+
+clear_ett_gid:
+ ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+
+clear_ect_gid:
+ if (ect_gid != NTMP_NULL_ENTRY_ID)
+ ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, ect_gid);
+
+ return err;
+}
+
+static void netc_delete_vlan_egress_rule(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 ett_eid, ett_gid;
+
+ ett_eid = le32_to_cpu(entry->cfge.et_eid);
+ if (ett_eid == NTMP_NULL_ENTRY_ID)
+ return;
+
+ ett_gid = ett_eid / num_ports;
+ ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+ for (int i = 0; i < num_ports; i++)
+ ntmp_ett_delete_entry(ntmp, ett_eid + i);
+
+ if (entry->ect_gid == NTMP_NULL_ENTRY_ID)
+ return;
+
+ ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, entry->ect_gid);
+}
+
+static int netc_port_update_vlan_egress_rule(struct netc_port *np,
+ struct netc_vlan_entry *entry)
+{
+ bool untagged = !!(entry->untagged_port_bitmap & BIT(np->dp->index));
+ u32 num_ports = netc_num_available_ports(np->switch_priv);
+ u32 ett_eid = le32_to_cpu(entry->cfge.et_eid);
+ struct netc_switch *priv = np->switch_priv;
+ u32 ect_eid = NTMP_NULL_ENTRY_ID;
+ int err;
+
+ if (ett_eid == NTMP_NULL_ENTRY_ID)
+ return 0;
+
+ if (entry->ect_gid != NTMP_NULL_ENTRY_ID) {
+ /* Each ETT entry maps to an ECT entry if ect_gid is not NULL
+ * entry ID. The offset of the ECT entry corresponding to the
+ * port in the group is equal to ett_offset.
+ */
+ ect_eid = entry->ect_gid * num_ports + np->ett_offset;
+ ntmp_ect_update_entry(&priv->ntmp, ect_eid);
+ }
+
+ ett_eid += np->ett_offset;
+ err = netc_add_or_update_ett_entry(priv, false, untagged,
+ ett_eid, ect_eid);
+ if (err)
+ dev_err(priv->dev,
+ "Failed to update VLAN %u egress rule on port %d\n",
+ entry->vid, np->dp->index);
+
+ return err;
+}
+
+static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
+ bool untagged)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ u32 index = np->dp->index;
+ u32 bitmap_stg;
+ int err;
+ u16 cfg;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->vid = vid;
+ entry->ect_gid = NTMP_NULL_ENTRY_ID;
+
+ bitmap_stg = BIT(index) | VFT_STG_ID(0);
+ cfg = FIELD_PREP(VFT_MLO, MLO_HW) |
+ FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD);
+
+ cfge = &entry->cfge;
+ cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ cfge->bitmap_stg = cpu_to_le32(bitmap_stg);
+ cfge->fid = cpu_to_le16(vid);
+ cfge->cfg = cpu_to_le16(cfg);
+ cfge->eta_port_bitmap = cpu_to_le32(priv->port_bitmap);
+
+ if (untagged)
+ entry->untagged_port_bitmap = BIT(index);
+
+ err = netc_add_vlan_egress_rule(priv, entry);
+ if (err)
+ goto free_vlan_entry;
+
+ err = ntmp_vft_add_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to add VLAN %u entry on port %d\n",
+ vid, index);
+ goto delete_vlan_egress_rule;
+ }
+
+ netc_add_vlan_entry(priv, entry);
+
+ return 0;
+
+delete_vlan_egress_rule:
+ netc_delete_vlan_egress_rule(priv, entry);
+free_vlan_entry:
+ kfree(entry);
+
+ return err;
+}
+
+static bool netc_port_vlan_egress_rule_changed(struct netc_vlan_entry *entry,
+ int port, bool untagged)
+{
+ bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port));
+
+ return old_untagged != untagged;
+}
+
+static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid,
+ bool untagged)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ int port = np->dp->index;
+ bool changed;
+ int err = 0;
+
+ mutex_lock(&priv->vft_lock);
+
+ entry = netc_lookup_vlan_entry(priv, vid);
+ if (!entry) {
+ err = netc_port_add_vlan_entry(np, vid, untagged);
+ goto unlock_vft;
+ }
+
+ /* Check whether the egress VLAN rule is changed */
+ changed = netc_port_vlan_egress_rule_changed(entry, port, untagged);
+ if (changed) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ err = netc_port_update_vlan_egress_rule(np, entry);
+ if (err) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ goto unlock_vft;
+ }
+ }
+
+ cfge = &entry->cfge;
+ if (cfge->bitmap_stg & cpu_to_le32(BIT(port)))
+ goto unlock_vft;
+
+ cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+ err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to update VLAN %u entry on port %d\n",
+ vid, port);
+
+ goto restore_bitmap_stg;
+ }
+
+ mutex_unlock(&priv->vft_lock);
+
+ return 0;
+
+restore_bitmap_stg:
+ cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+ if (changed) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ /* Recover the corresponding ETT entry. It doesn't matter
+ * if it fails because the bit corresponding to the port
+ * in the port bitmap of the VFT entry is not set. so the
+ * frame will not match that ETT entry.
+ */
+ if (netc_port_update_vlan_egress_rule(np, entry))
+ entry->untagged_port_bitmap ^= BIT(port);
+ }
+unlock_vft:
+ mutex_unlock(&priv->vft_lock);
+
+ return err;
+}
+
+static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ int port = np->dp->index;
+ u32 vlan_port_bitmap;
+ int err = 0;
+
+ mutex_lock(&priv->vft_lock);
+
+ entry = netc_lookup_vlan_entry(priv, vid);
+ if (!entry)
+ goto unlock_vft;
+
+ cfge = &entry->cfge;
+ vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP,
+ le32_to_cpu(cfge->bitmap_stg));
+ /* If the VLAN only belongs to the current port */
+ if (vlan_port_bitmap == BIT(port)) {
+ err = ntmp_vft_delete_entry(&priv->ntmp, vid);
+ if (err)
+ goto unlock_vft;
+
+ netc_delete_vlan_egress_rule(priv, entry);
+ netc_del_vlan_entry(entry);
+
+ goto unlock_vft;
+ }
+
+ if (!(vlan_port_bitmap & BIT(port)))
+ goto unlock_vft;
+
+ cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+ err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+ goto unlock_vft;
+ }
+
+ entry->untagged_port_bitmap &= ~BIT(port);
+
+unlock_vft:
+ mutex_unlock(&priv->vft_lock);
+
+ return err;
+}
+
static int netc_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phy)
{
@@ -1297,6 +1689,35 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
netc_port_remove_host_flood(np, old_host_flood);
}
+static int netc_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ bool untagged;
+
+ /* The 8021q layer may attempt to change NETC_STANDALONE_PVID
+ * (VID 0), so we need to ignore it.
+ */
+ if (vlan->vid == NETC_STANDALONE_PVID)
+ return 0;
+
+ untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+
+ return netc_port_set_vlan_entry(np, vlan->vid, untagged);
+}
+
+static int netc_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ if (vlan->vid == NETC_STANDALONE_PVID)
+ return 0;
+
+ return netc_port_del_vlan_entry(np, vlan->vid);
+}
+
static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
@@ -1575,6 +1996,8 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_mdb_add = netc_port_mdb_add,
.port_mdb_del = netc_port_mdb_del,
.port_set_host_flood = netc_port_set_host_flood,
+ .port_vlan_add = netc_port_vlan_add,
+ .port_vlan_del = netc_port_vlan_del,
.get_pause_stats = netc_port_get_pause_stats,
.get_rmon_stats = netc_port_get_rmon_stats,
.get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 4fbd12825b67..9ff334301fbc 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -74,6 +74,7 @@ struct netc_port {
struct dsa_port *dp;
struct clk *ref_clk; /* RGMII/RMII reference clock */
struct mii_bus *emdio;
+ int ett_offset;
u16 enable:1;
u16 uc:1;
@@ -94,6 +95,14 @@ struct netc_fdb_entry {
struct hlist_node node;
};
+struct netc_vlan_entry {
+ u16 vid;
+ u32 ect_gid;
+ u32 untagged_port_bitmap;
+ struct vft_cfge_data cfge;
+ struct hlist_node node;
+};
+
struct netc_port_stat {
int reg;
char name[ETH_GSTRING_LEN] __nonstring;
@@ -108,10 +117,13 @@ struct netc_switch {
const struct netc_switch_info *info;
struct netc_switch_regs regs;
struct netc_port **ports;
+ u32 port_bitmap; /* bitmap of available ports */
struct ntmp_user ntmp;
struct hlist_head fdb_list;
struct mutex fdbt_lock; /* FDB table lock */
+ struct hlist_head vlan_list;
+ struct mutex vft_lock; /* VLAN filter table lock */
/* Switch hardware capabilities */
u32 htmcapr_num_words;
@@ -153,6 +165,18 @@ static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry)
kfree(entry);
}
+static inline void netc_add_vlan_entry(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ hlist_add_head(&entry->node, &priv->vlan_list);
+}
+
+static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry)
+{
+ hlist_del(&entry->node);
+ kfree(entry);
+}
+
int netc_switch_platform_probe(struct netc_switch *priv);
/* ethtool APIs */
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index a678f1e1ee42..7677e7151fd9 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -267,6 +267,21 @@ struct bpt_cfge_data {
__le32 fc_ports;
};
+union ntmp_fmt_eid {
+ __le32 index;
+#define FMTEID_INDEX GENMASK(12, 0)
+ __le32 vuda_sqta;
+#define FMTEID_VUDA GENMASK(1, 0)
+#define FMTEID_VUDA_DEL_OTAG 2
+#define FMTEID_SQTA GENMASK(4, 2)
+#define FMTEID_SQTA_DEL 2
+#define FMTEID_VUDA_SQTA BIT(13)
+ __le32 vara_vid;
+#define FMTEID_VID GENMASK(11, 0)
+#define FMTEID_VARA GENMASK(13, 12)
+#define FMTEID_VARA_VID BIT(14)
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 8/9] net: dsa: netc: add bridge mode support
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
` (6 preceding siblings ...)
2026-05-27 10:02 ` [PATCH net-next 7/9] net: dsa: netc: add VLAN filter table and egress treatment management wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-27 10:02 ` [PATCH net-next 9/9] net: dsa: netc: implement dynamic FDB entry aging wei.fang
2026-05-29 1:53 ` [PATCH net-next 0/9] net: dsa: netc: add bridge mode support Wei Fang
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
Wire up the port_bridge_join, port_bridge_leave and port_vlan_filtering
DSA callbacks to support both VLAN-unaware and VLAN-aware bridge modes.
For VLAN-unaware bridges, each bridge instance is assigned a dedicated
internal PVID via NETC_VLAN_UNAWARE_PVID(bridge.num), counting down
from VID 4095. A VFT entry is created for this PVID with hardware MAC
learning and flood-on-miss forwarding enabled. The CPU port is included
as a VFT member so frames can reach the host. The reserved VID range is
blocked in port_vlan_add to prevent user-space conflicts.
Only one VLAN-aware bridge is supported at a time; this constraint is
enforced in port_bridge_join and port_vlan_filtering. The per-port PVID
is tracked in software and written to the BPDVR register whenever VLAN
filtering is active.
FDB operations are extended to the bridge database: when vid is zero the
VLAN-unaware PVID for the bridge is substituted. Dynamic entries learned
autonomously by the hardware are handled by falling back to a
key-element-data delete via ntmp_fdbt_delete_entry_by_keye() when an
entry is absent from the software shadow list. Internal PVIDs are
translated back to VID 0 in port_fdb_dump before reporting to
user-space.
Host flood rules are removed from the ingress port filter table when a
port joins a bridge to avoid bypassing FDB lookup and MAC learning.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/dsa/netc/netc_main.c | 297 +++++++++++++++++++++++++++--
drivers/net/dsa/netc/netc_switch.h | 2 +
2 files changed, 282 insertions(+), 17 deletions(-)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 4db42c888470..1fe3b43e0459 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -694,13 +694,18 @@ static int netc_port_del_fdb_entry(struct netc_port *np,
mutex_lock(&priv->fdbt_lock);
entry = netc_lookup_fdb_entry(priv, addr, vid);
- if (unlikely(!entry))
- /* Currently only single port mode is supported, MAC learning
- * is disabled, so there is no dynamically learned FDB entry.
- * We need to support deleting dynamically FDB entry when the
- * bridge mode is supported.
- */
+ /* If the entry is NULL, the specified FDB entry might be a dynamic
+ * entry, so try to delete it through the key element data.
+ */
+ if (!entry) {
+ struct fdbt_keye_data keye = {};
+
+ ether_addr_copy(keye.mac_addr, addr);
+ keye.fid = cpu_to_le16(vid);
+ err = ntmp_fdbt_delete_entry_by_keye(ntmp, &keye);
+
goto unlock_fdbt;
+ }
cfge = &entry->cfge;
if (unlikely(!(cfge->port_bitmap & cpu_to_le32(BIT(port)))))
@@ -1259,6 +1264,16 @@ static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
entry->ect_gid = NTMP_NULL_ENTRY_ID;
bitmap_stg = BIT(index) | VFT_STG_ID(0);
+ /* If the VID is a VLAN-unaware PVID, the CPU port needs to be
+ * a member of this VLAN.
+ */
+ if (dsa_port_is_user(np->dp) &&
+ vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+ struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+ bitmap_stg |= BIT(cpu_dp->index);
+ }
+
cfg = FIELD_PREP(VFT_MLO, MLO_HW) |
FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD);
@@ -1296,11 +1311,16 @@ static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
return err;
}
-static bool netc_port_vlan_egress_rule_changed(struct netc_vlan_entry *entry,
+static bool netc_port_vlan_egress_rule_changed(struct netc_switch *priv,
+ struct netc_vlan_entry *entry,
int port, bool untagged)
{
bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port));
+ /* VLAN-unaware VIDs have no egress rules, so return 'false' */
+ if (entry->vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges))
+ return false;
+
return old_untagged != untagged;
}
@@ -1323,7 +1343,8 @@ static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid,
}
/* Check whether the egress VLAN rule is changed */
- changed = netc_port_vlan_egress_rule_changed(entry, port, untagged);
+ changed = netc_port_vlan_egress_rule_changed(priv, entry, port,
+ untagged);
if (changed) {
entry->untagged_port_bitmap ^= BIT(port);
err = netc_port_update_vlan_egress_rule(np, entry);
@@ -1387,6 +1408,17 @@ static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid)
cfge = &entry->cfge;
vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP,
le32_to_cpu(cfge->bitmap_stg));
+ /* If the VID is a VLAN-unaware PVID, we need to clear the CPU
+ * port bit of vlan_port_bitmap, so that the VLAN entry can be
+ * deleted if no user ports use this VLAN.
+ */
+ if (dsa_port_is_user(np->dp) &&
+ vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+ struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+ vlan_port_bitmap &= ~BIT(cpu_dp->index);
+ }
+
/* If the VLAN only belongs to the current port */
if (vlan_port_bitmap == BIT(port)) {
err = ntmp_vft_delete_entry(&priv->ntmp, vid);
@@ -1494,17 +1526,53 @@ static int netc_port_max_mtu(struct dsa_switch *ds, int port)
return NETC_MAX_FRAME_LEN - VLAN_ETH_HLEN - ETH_FCS_LEN;
}
+static struct net_device *netc_classify_db(struct dsa_db db)
+{
+ switch (db.type) {
+ case DSA_DB_PORT:
+ return NULL;
+ case DSA_DB_BRIDGE:
+ return db.bridge.dev;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+}
+
+static u16 netc_vlan_unaware_pvid(struct dsa_switch *ds,
+ const struct net_device *br_ndev)
+{
+ struct dsa_port *dp;
+ int br_num = -1;
+
+ if (!br_ndev)
+ return NETC_STANDALONE_PVID;
+
+ dsa_switch_for_each_available_port(dp, ds) {
+ if (dsa_port_bridge_dev_get(dp) == br_ndev) {
+ br_num = dp->bridge->num;
+ break;
+ }
+ }
+
+ /* The br_num is supposed to be 1 ~ ds->max_num_bridges */
+ if (WARN_ON(br_num <= 0))
+ return NETC_STANDALONE_PVID;
+
+ return NETC_VLAN_UNAWARE_PVID(br_num);
+}
+
static int netc_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
+ struct net_device *br_ndev = netc_classify_db(db);
struct netc_port *np = NETC_PORT(ds, port);
- /* Currently, only support standalone port mode, so only
- * NETC_STANDALONE_PVID (= 0) is supported here.
- */
- if (vid != NETC_STANDALONE_PVID)
- return -EOPNOTSUPP;
+ if (IS_ERR(br_ndev))
+ return PTR_ERR(br_ndev);
+
+ if (!vid)
+ vid = netc_vlan_unaware_pvid(ds, br_ndev);
return netc_port_set_fdb_entry(np, addr, vid);
}
@@ -1513,10 +1581,14 @@ static int netc_port_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
+ struct net_device *br_ndev = netc_classify_db(db);
struct netc_port *np = NETC_PORT(ds, port);
- if (vid != NETC_STANDALONE_PVID)
- return -EOPNOTSUPP;
+ if (IS_ERR(br_ndev))
+ return PTR_ERR(br_ndev);
+
+ if (!vid)
+ vid = netc_vlan_unaware_pvid(ds, br_ndev);
return netc_port_del_fdb_entry(np, addr, vid);
}
@@ -1552,6 +1624,8 @@ static int netc_port_fdb_dump(struct dsa_switch *ds, int port,
cfg = le32_to_cpu(cfge->cfg);
is_static = (cfg & FDBT_DYNAMIC) ? false : true;
vid = le16_to_cpu(keye->fid);
+ if (vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges))
+ vid = 0;
err = cb(keye->mac_addr, vid, is_static, data);
if (err)
@@ -1668,6 +1742,19 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
struct netc_port *np = NETC_PORT(ds, port);
struct ipft_entry_data *old_host_flood;
+ /* Do not add host flood rule to ingress port filter table when
+ * the port has joined a bridge. Otherwise, the ingress frames
+ * will bypass FDB table lookup and MAC learning, so the frames
+ * will be redirected directly to the CPU port.
+ */
+ if (dsa_port_bridge_dev_get(np->dp)) {
+ netc_port_remove_host_flood(np, np->host_flood);
+ np->host_flood = NULL;
+ netc_port_wr(np, NETC_PIPFCR, 0);
+
+ return;
+ }
+
if (np->uc == uc && np->mc == mc)
return;
@@ -1689,12 +1776,81 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
netc_port_remove_host_flood(np, old_host_flood);
}
+static int netc_single_vlan_aware_bridge(struct dsa_switch *ds,
+ struct netlink_ext_ack *extack)
+{
+ struct net_device *br_ndev = NULL;
+ struct dsa_port *dp;
+
+ dsa_switch_for_each_available_port(dp, ds) {
+ struct net_device *port_br = dsa_port_bridge_dev_get(dp);
+
+ if (!port_br || !br_vlan_enabled(port_br))
+ continue;
+
+ if (!br_ndev) {
+ br_ndev = port_br;
+ continue;
+ }
+
+ if (br_ndev == port_br)
+ continue;
+
+ NL_SET_ERR_MSG_MOD(extack,
+ "Only one VLAN-aware bridge is supported");
+
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int netc_port_vlan_filtering(struct dsa_switch *ds,
+ int port, bool vlan_aware,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *br_ndev;
+ u32 pvid, val;
+ int err;
+
+ err = netc_single_vlan_aware_bridge(ds, extack);
+ if (err)
+ return err;
+
+ br_ndev = dsa_port_bridge_dev_get(np->dp);
+ pvid = netc_vlan_unaware_pvid(ds, br_ndev);
+ if (vlan_aware) {
+ err = netc_port_del_vlan_entry(np, pvid);
+ if (err)
+ return err;
+
+ pvid = np->pvid;
+ } else {
+ err = netc_port_set_vlan_entry(np, pvid, false);
+ if (err)
+ return err;
+ }
+
+ val = (vlan_aware ? 0 : BPDVR_RXVAM) | (pvid & BPDVR_VID);
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM | BPDVR_VID, val);
+
+ return 0;
+}
+
+static void netc_port_set_pvid(struct netc_port *np, u16 pvid)
+{
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_VID, pvid & BPDVR_VID);
+}
+
static int netc_port_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
{
struct netc_port *np = NETC_PORT(ds, port);
+ struct dsa_port *dp = np->dp;
bool untagged;
+ int err;
/* The 8021q layer may attempt to change NETC_STANDALONE_PVID
* (VID 0), so we need to ignore it.
@@ -1702,20 +1858,123 @@ static int netc_port_vlan_add(struct dsa_switch *ds, int port,
if (vlan->vid == NETC_STANDALONE_PVID)
return 0;
+ if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "VID %d~4095 reserved for VLAN-unaware bridge",
+ NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges));
+ return -EOPNOTSUPP;
+ }
+
untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+ err = netc_port_set_vlan_entry(np, vlan->vid, untagged);
+ if (err)
+ return err;
+
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
+ np->pvid = vlan->vid;
+ if (dsa_port_is_vlan_filtering(dp))
+ netc_port_set_pvid(np, vlan->vid);
+
+ return 0;
+ }
+
+ if (np->pvid != vlan->vid)
+ return 0;
- return netc_port_set_vlan_entry(np, vlan->vid, untagged);
+ /* Delete PVID */
+ np->pvid = 0;
+ if (dsa_port_is_vlan_filtering(dp))
+ netc_port_set_pvid(np, 0);
+
+ return 0;
}
static int netc_port_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct netc_port *np = NETC_PORT(ds, port);
+ int err;
if (vlan->vid == NETC_STANDALONE_PVID)
return 0;
- return netc_port_del_vlan_entry(np, vlan->vid);
+ err = netc_port_del_vlan_entry(np, vlan->vid);
+ if (err)
+ return err;
+
+ if (np->pvid == vlan->vid) {
+ np->pvid = 0;
+
+ if (dsa_port_is_vlan_filtering(np->dp))
+ netc_port_set_pvid(np, 0);
+ }
+
+ return 0;
+}
+
+static int netc_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ u16 vlan_unaware_pvid;
+ int err;
+
+ err = netc_single_vlan_aware_bridge(ds, extack);
+ if (err)
+ return err;
+
+ netc_port_set_mlo(np, MLO_NOT_OVERRIDE);
+
+ if (br_vlan_enabled(bridge.dev))
+ goto out;
+
+ vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+ err = netc_port_set_vlan_entry(np, vlan_unaware_pvid, false);
+ if (err) {
+ netc_port_set_mlo(np, MLO_DISABLE);
+ return err;
+ }
+
+ netc_port_set_pvid(np, vlan_unaware_pvid);
+
+out:
+ netc_port_remove_host_flood(np, np->host_flood);
+ np->host_flood = NULL;
+ netc_port_wr(np, NETC_PIPFCR, 0);
+
+ return 0;
+}
+
+static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev = np->dp->user;
+ u16 vlan_unaware_pvid;
+ bool mc, uc;
+
+ netc_port_set_mlo(np, MLO_DISABLE);
+ netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+
+ uc = ndev->flags & IFF_PROMISC;
+ mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
+
+ if (netc_port_add_host_flood_rule(np, uc, mc))
+ dev_warn(ds->dev,
+ "Failed to restore host flood rule on port %d\n",
+ port);
+
+ if (br_vlan_enabled(bridge.dev))
+ return;
+
+ vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+ /* There is no need to check the return value even if it fails.
+ * Because the PVID has been set to NETC_STANDALONE_PVID, the
+ * frames will not match this VLAN entry.
+ */
+ netc_port_del_vlan_entry(np, vlan_unaware_pvid);
}
static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -1996,8 +2255,11 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_mdb_add = netc_port_mdb_add,
.port_mdb_del = netc_port_mdb_del,
.port_set_host_flood = netc_port_set_host_flood,
+ .port_vlan_filtering = netc_port_vlan_filtering,
.port_vlan_add = netc_port_vlan_add,
.port_vlan_del = netc_port_vlan_del,
+ .port_bridge_join = netc_port_bridge_join,
+ .port_bridge_leave = netc_port_bridge_leave,
.get_pause_stats = netc_port_get_pause_stats,
.get_rmon_stats = netc_port_get_rmon_stats,
.get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
@@ -2045,6 +2307,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->ops = &netc_switch_ops;
ds->phylink_mac_ops = &netc_phylink_mac_ops;
ds->fdb_isolation = true;
+ ds->max_num_bridges = priv->info->num_ports - 1;
ds->priv = priv;
priv->ds = ds;
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 9ff334301fbc..982c8d3a3fbf 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -33,6 +33,7 @@
#define NETC_MAX_FRAME_LEN 9600
#define NETC_STANDALONE_PVID 0
+#define NETC_VLAN_UNAWARE_PVID(br_id) (4096 - (br_id))
/* Threshold format: MANT (bits 11:4) * 2^EXP (bits 3:0)
* Unit: Memory words (average of 20 bytes each)
@@ -79,6 +80,7 @@ struct netc_port {
u16 enable:1;
u16 uc:1;
u16 mc:1;
+ u16 pvid;
struct ipft_entry_data *host_flood;
};
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next 9/9] net: dsa: netc: implement dynamic FDB entry aging
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
` (7 preceding siblings ...)
2026-05-27 10:02 ` [PATCH net-next 8/9] net: dsa: netc: add bridge mode support wei.fang
@ 2026-05-27 10:02 ` wei.fang
2026-05-29 1:53 ` [PATCH net-next 0/9] net: dsa: netc: add bridge mode support Wei Fang
9 siblings, 0 replies; 11+ messages in thread
From: wei.fang @ 2026-05-27 10:02 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
From: Wei Fang <wei.fang@nxp•com>
The NETC switch does not age out dynamic FDB entries automatically.
Without software management, stale entries persist after topology
changes and cause incorrect forwarding.
Add a delayed work that periodically removes entries that have not been
refreshed within the specified cycles. The effective aging time is:
aging_time = fdbt_acteu_interval * fdbt_ageing_act_cnt
Default values are 3s interval and 100 cycles (300s total), matching
the IEEE 802.1Q default aging time. The work starts when the first
port joins a bridge (tracked via br_cnt) and is cancelled when the
last port leaves. All FDB operations are serialized under fdbt_lock.
Dynamic entries for a departing port are also flushed immediately in
port_bridge_leave() and netc_mac_link_down(), without waiting for the
next aging cycle.
Implement set_ageing_time and port_fast_age DSA operations to allow
the bridge layer to reconfigure aging parameters and trigger per-port
flushes on demand.
Signed-off-by: Wei Fang <wei.fang@nxp•com>
---
drivers/net/dsa/netc/netc_main.c | 80 ++++++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_switch.h | 8 +++
2 files changed, 88 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 1fe3b43e0459..cffa479e19b3 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -447,6 +447,26 @@ static void netc_free_ntmp_user(struct netc_switch *priv)
netc_free_ntmp_bitmaps(priv);
}
+static void netc_clean_fdbt_ageing_entries(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct netc_switch *priv;
+
+ priv = container_of(dwork, struct netc_switch, fdbt_clean);
+
+ /* Update the activity element in FDB table */
+ mutex_lock(&priv->fdbt_lock);
+ ntmp_fdbt_update_activity_element(&priv->ntmp);
+ /* Delete the aging entries after the activity element is updated */
+ ntmp_fdbt_delete_aging_entries(&priv->ntmp,
+ READ_ONCE(priv->fdbt_ageing_act_cnt));
+ mutex_unlock(&priv->fdbt_lock);
+
+ if (atomic_read(&priv->br_cnt))
+ schedule_delayed_work(&priv->fdbt_clean,
+ READ_ONCE(priv->fdbt_acteu_interval));
+}
+
static void netc_switch_dos_default_config(struct netc_switch *priv)
{
struct netc_switch_regs *regs = &priv->regs;
@@ -860,6 +880,11 @@ static int netc_setup(struct dsa_switch *ds)
INIT_HLIST_HEAD(&priv->fdb_list);
mutex_init(&priv->fdbt_lock);
+ priv->fdbt_acteu_interval = NETC_FDBT_CLEAN_INTERVAL;
+ priv->fdbt_ageing_act_cnt = NETC_FDBT_AGEING_ACT_CNT;
+ atomic_set(&priv->br_cnt, 0);
+ INIT_DELAYED_WORK(&priv->fdbt_clean,
+ netc_clean_fdbt_ageing_entries);
INIT_HLIST_HEAD(&priv->vlan_list);
mutex_init(&priv->vft_lock);
@@ -924,6 +949,7 @@ static void netc_teardown(struct dsa_switch *ds)
{
struct netc_switch *priv = ds->priv;
+ disable_delayed_work_sync(&priv->fdbt_clean);
netc_destroy_all_lists(priv);
netc_free_host_flood_rules(priv);
netc_free_ntmp_user(priv);
@@ -1918,6 +1944,7 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
struct netc_port *np = NETC_PORT(ds, port);
+ struct netc_switch *priv = ds->priv;
u16 vlan_unaware_pvid;
int err;
@@ -1944,20 +1971,42 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
np->host_flood = NULL;
netc_port_wr(np, NETC_PIPFCR, 0);
+ if (atomic_inc_return(&priv->br_cnt) == 1)
+ schedule_delayed_work(&priv->fdbt_clean,
+ READ_ONCE(priv->fdbt_acteu_interval));
+
return 0;
}
+static void netc_port_remove_dynamic_entries(struct netc_port *np)
+{
+ struct netc_switch *priv = np->switch_priv;
+
+ /* Return if the port is not available */
+ if (!np->dp)
+ return;
+
+ mutex_lock(&priv->fdbt_lock);
+ ntmp_fdbt_delete_port_dynamic_entries(&priv->ntmp, np->dp->index);
+ mutex_unlock(&priv->fdbt_lock);
+}
+
static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_bridge bridge)
{
struct netc_port *np = NETC_PORT(ds, port);
struct net_device *ndev = np->dp->user;
+ struct netc_switch *priv = ds->priv;
u16 vlan_unaware_pvid;
bool mc, uc;
netc_port_set_mlo(np, MLO_DISABLE);
netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+ if (atomic_dec_and_test(&priv->br_cnt))
+ cancel_delayed_work_sync(&priv->fdbt_clean);
+
+ netc_port_remove_dynamic_entries(np);
uc = ndev->flags & IFF_PROMISC;
mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
@@ -1977,6 +2026,34 @@ static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
netc_port_del_vlan_entry(np, vlan_unaware_pvid);
}
+static int netc_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+ struct netc_switch *priv = ds->priv;
+ u32 secs = msecs / 1000;
+ u32 act_cnt, interval;
+
+ if (!secs)
+ secs = 1;
+
+ for (interval = 1; interval <= secs; interval++) {
+ act_cnt = secs / interval;
+ if (act_cnt <= FDBT_ACT_CNT)
+ break;
+ }
+
+ WRITE_ONCE(priv->fdbt_acteu_interval, (unsigned long)interval * HZ);
+ WRITE_ONCE(priv->fdbt_ageing_act_cnt, act_cnt);
+
+ return 0;
+}
+
+static void netc_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ netc_port_remove_dynamic_entries(np);
+}
+
static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
@@ -2231,6 +2308,7 @@ static void netc_mac_link_down(struct phylink_config *config,
np = NETC_PORT(dp->ds, dp->index);
netc_port_mac_rx_graceful_stop(np);
netc_port_mac_tx_graceful_stop(np);
+ netc_port_remove_dynamic_entries(np);
}
static const struct phylink_mac_ops netc_phylink_mac_ops = {
@@ -2260,6 +2338,8 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_vlan_del = netc_port_vlan_del,
.port_bridge_join = netc_port_bridge_join,
.port_bridge_leave = netc_port_bridge_leave,
+ .set_ageing_time = netc_set_ageing_time,
+ .port_fast_age = netc_port_fast_age,
.get_pause_stats = netc_port_get_pause_stats,
.get_rmon_stats = netc_port_get_rmon_stats,
.get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 982c8d3a3fbf..8ad828f7d28c 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -50,6 +50,9 @@
/* PAUSE refresh threshold: send refresh when timer reaches this value */
#define NETC_PAUSE_THRESH 0x7FFF
+#define NETC_FDBT_CLEAN_INTERVAL (3 * HZ)
+#define NETC_FDBT_AGEING_ACT_CNT 100
+
struct netc_switch;
struct netc_switch_info {
@@ -124,6 +127,11 @@ struct netc_switch {
struct ntmp_user ntmp;
struct hlist_head fdb_list;
struct mutex fdbt_lock; /* FDB table lock */
+ struct delayed_work fdbt_clean;
+ /* (interval * act_cnt) is ageing time */
+ unsigned long fdbt_acteu_interval;
+ u8 fdbt_ageing_act_cnt; /* maximum is 127 */
+ atomic_t br_cnt;
struct hlist_head vlan_list;
struct mutex vft_lock; /* VLAN filter table lock */
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* RE: [PATCH net-next 0/9] net: dsa: netc: add bridge mode support
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
` (8 preceding siblings ...)
2026-05-27 10:02 ` [PATCH net-next 9/9] net: dsa: netc: implement dynamic FDB entry aging wei.fang
@ 2026-05-29 1:53 ` Wei Fang
9 siblings, 0 replies; 11+ messages in thread
From: Wei Fang @ 2026-05-29 1:53 UTC (permalink / raw)
To: Wei Fang (OSS), Claudiu Manoil, Vladimir Oltean, Clark Wang,
andrew+netdev@lunn•ch, davem@davemloft•net, edumazet@google•com,
kuba@kernel•org, pabeni@redhat•com, chleroy@kernel•org,
andrew@lunn•ch, olteanv@gmail•com
Cc: imx@lists•linux.dev, netdev@vger•kernel.org,
linux-kernel@vger•kernel.org, linuxppc-dev@lists•ozlabs.org,
linux-arm-kernel@lists•infradead.org
> This series adds bridge mode support to the NETC DSA switch driver,
> covering both VLAN-aware and VLAN-unaware operation.
>
> The NETC switch manages forwarding through a set of hardware tables
> accessed via NTMP: the FDB table (FDBT), VLAN filter table (VFT), egress
> treatment table (ETT), and egress count table (ECT). The series extends
> the NTMP layer with the operations required for bridging, then builds the
> DSA bridge callbacks on top.
>
> Since all switch ports share the VFT, so only one VLAN-aware bridge is
> supported.
>
> FDB aging is managed in software. A periodic delayed work sweeps the
> table using the hardware activity element mechanism, with a default aging
> time of 300 seconds matching the IEEE 802.1Q standard. Per-port entries
> are also flushed immediately on bridge leave and link-down events.
>
> Wei Fang (9):
> net: enetc: add interfaces to manage FDB entries
> net: enetc: add "Update" and "Delete" operations to VLAN filter table
> net: enetc: add interfaces to manage egress treatment table
> net: enetc: add "Update" operation to the egress count table
> net: dsa: netc: initialize the group bitmap of ETT and ECT
> net: enetc: add helpers to set/clear table bitmap
> net: dsa: netc: add VLAN filter table and egress treatment management
> net: dsa: netc: add bridge mode support
> net: dsa: netc: implement dynamic FDB entry aging
>
> drivers/net/dsa/netc/netc_main.c | 882
> +++++++++++++++++-
> drivers/net/dsa/netc/netc_switch.h | 34 +
> drivers/net/dsa/netc/netc_switch_hw.h | 6 +
> drivers/net/ethernet/freescale/enetc/ntmp.c | 431 +++++++++
> .../ethernet/freescale/enetc/ntmp_private.h | 14 +
> include/linux/fsl/ntmp.h | 60 ++
> 6 files changed, 1413 insertions(+), 14 deletions(-)
>
> --
> 2.34.1
Sashi reported some issues for this series:
[1] https://sashiko.dev/#/patchset/20260527100217.794987-1-wei.fang%40oss.nxp.com
[2] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260527100217.794987-1-wei.fang%40oss.nxp.com
Some of them are indeed potential issues, I will fix them in next version.
Any other comments are welcome, thanks.
--
pw-bot: cr
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-05-29 1:53 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
2026-05-27 10:02 ` [PATCH net-next 1/9] net: enetc: add interfaces to manage FDB entries wei.fang
2026-05-27 10:02 ` [PATCH net-next 2/9] net: enetc: add "Update" and "Delete" operations to VLAN filter table wei.fang
2026-05-27 10:02 ` [PATCH net-next 3/9] net: enetc: add interfaces to manage egress treatment table wei.fang
2026-05-27 10:02 ` [PATCH net-next 4/9] net: enetc: add "Update" operation to the egress count table wei.fang
2026-05-27 10:02 ` [PATCH net-next 5/9] net: dsa: netc: initialize the group bitmap of ETT and ECT wei.fang
2026-05-27 10:02 ` [PATCH net-next 6/9] net: enetc: add helpers to set/clear table bitmap wei.fang
2026-05-27 10:02 ` [PATCH net-next 7/9] net: dsa: netc: add VLAN filter table and egress treatment management wei.fang
2026-05-27 10:02 ` [PATCH net-next 8/9] net: dsa: netc: add bridge mode support wei.fang
2026-05-27 10:02 ` [PATCH net-next 9/9] net: dsa: netc: implement dynamic FDB entry aging wei.fang
2026-05-29 1:53 ` [PATCH net-next 0/9] net: dsa: netc: add bridge mode support Wei Fang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox