From: wei.fang@oss.nxp.com
To: xiaoning.wang@nxp.com, andrew@lunn.ch, olteanv@gmail.com,
andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
richardcochran@gmail.com, linusw@kernel.org
Cc: wei.fang@nxp.com, imx@lists.linux.dev, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, linuxppc-dev@lists.ozlabs.org,
linux-arm-kernel@lists.infradead.org
Subject: [PATCH v5 net-next 4/9] net: dsa: netc: tie setup resources and reset to DSA setup/teardown
Date: Mon, 12 Oct 2026 17:59:19 +0800 [thread overview]
Message-ID: <20261012095924.1129312-5-wei.fang@oss.nxp.com> (raw)
In-Reply-To: <20261012095924.1129312-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
The NETC switch reached a known hardware state via a one-time PCIe
FLR in probe(), and used devm_ allocations in netc_setup() for its
ports, EMDIO buses, reference clocks, command BD rings and buffer
pool table. Both are tied to the PCI device lifetime, not to the
DSA netc_setup()/netc_teardown() lifetime.
In a multi-switch DSA tree, binding or unbinding any switch tears
down and re-sets up the whole tree, so netc_teardown() and
netc_setup() can run again without a PCI re-probe. Two problems
then appear:
- No fresh FLR occurs, so the hardware tables keep their stale
entries while netc_setup() builds empty software state. Hardware
and software drift out of sync.
- The devm_ resources are not released by netc_teardown(), so each
re-setup leaks memory and re-registers the same EMDIO bus, which
fails.
Add netc_switch_reset() and call it at the start of netc_setup() so
every setup begins from a clean hardware state. Use
pci_reset_function_locked() during probe(), where the device lock is
already held, and pci_reset_function() otherwise; both reset the
function and restore the PCI config space, unlike the bare
pcie_flr() they replace.
Convert the setup-path devm_ allocations to explicit alloc and free,
and release them in netc_teardown() and on the error paths, so their
lifetime matches the DSA setup and teardown.
The NETC switch does not support multi-switch yet, so there is no
functional problem today. This only reworks the driver so that
multi-switch can be added later without hitting these issues.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 163 ++++++++++++++++++++++-------
drivers/net/dsa/netc/netc_switch.h | 1 +
2 files changed, 129 insertions(+), 35 deletions(-)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 77077352c1a5..c639ae67c9b7 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -135,7 +135,7 @@ static int netc_port_get_info_from_dt(struct netc_port *np,
struct device *dev)
{
if (of_find_property(node, "clock-names", NULL)) {
- np->ref_clk = devm_get_clk_from_child(dev, node, "ref");
+ np->ref_clk = of_clk_get_by_name(node, "ref");
if (IS_ERR(np->ref_clk)) {
dev_err(dev, "Port %d cannot get reference clock\n",
np->dp->index);
@@ -146,6 +146,24 @@ static int netc_port_get_info_from_dt(struct netc_port *np,
return 0;
}
+static void netc_port_free_emdio_bus(struct netc_port *np)
+{
+ struct device *dev = np->switch_priv->dev;
+ struct enetc_mdio_priv *mdio_priv;
+ struct enetc_hw *hw;
+
+ if (!np->emdio)
+ return;
+
+ mdio_priv = np->emdio->priv;
+ hw = mdio_priv->hw;
+
+ mdiobus_unregister(np->emdio);
+ mdiobus_free(np->emdio);
+ devm_kfree(dev, hw);
+ np->emdio = NULL;
+}
+
static int netc_port_create_emdio_bus(struct netc_port *np,
struct device_node *node)
{
@@ -161,9 +179,11 @@ static int netc_port_create_emdio_bus(struct netc_port *np,
return dev_err_probe(dev, PTR_ERR(hw),
"Failed to allocate enetc_hw\n");
- bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
- if (!bus)
- return -ENOMEM;
+ bus = mdiobus_alloc_size(sizeof(*mdio_priv));
+ if (!bus) {
+ err = -ENOMEM;
+ goto free_enetc_hw;
+ }
bus->name = "NXP NETC switch external MDIO Bus";
bus->read = enetc_mdio_read_c22;
@@ -177,14 +197,22 @@ static int netc_port_create_emdio_bus(struct netc_port *np,
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-p%d-emdio",
dev_name(dev), np->dp->index);
- err = devm_of_mdiobus_register(dev, bus, node);
- if (err)
- return dev_err_probe(dev, err,
- "Cannot register EMDIO bus\n");
+ err = of_mdiobus_register(bus, node);
+ if (err) {
+ dev_err(dev, "Cannot register EMDIO bus\n");
+ goto free_mdiobus;
+ }
np->emdio = bus;
return 0;
+
+free_mdiobus:
+ mdiobus_free(bus);
+free_enetc_hw:
+ devm_kfree(dev, hw);
+
+ return err;
}
static int netc_port_create_mdio_bus(struct netc_port *np,
@@ -238,6 +266,33 @@ static void netc_get_switch_capabilities(struct netc_switch *priv)
priv->num_bp = FIELD_GET(BPCAPR_NUM_BP, val);
}
+static void netc_free_all_ports(struct netc_switch *priv)
+{
+ if (!priv->ports)
+ return;
+
+ for (int i = 0; i < priv->info->num_ports; i++) {
+ struct netc_port *np = priv->ports[i];
+
+ if (!np)
+ continue;
+
+ netc_port_free_emdio_bus(np);
+
+ if (!IS_ERR_OR_NULL(np->ref_clk)) {
+ clk_put(np->ref_clk);
+ np->ref_clk = NULL;
+ }
+
+ kfree(np);
+ priv->ports[i] = NULL;
+ }
+
+ kfree(priv->ports);
+ priv->ports = NULL;
+ priv->port_bitmap = 0;
+}
+
static int netc_init_all_ports(struct netc_switch *priv)
{
struct device *dev = priv->dev;
@@ -246,9 +301,7 @@ static int netc_init_all_ports(struct netc_switch *priv)
int ett_offset = 0;
int err;
- priv->ports = devm_kcalloc(dev, priv->info->num_ports,
- sizeof(struct netc_port *),
- GFP_KERNEL);
+ priv->ports = kzalloc_objs(*priv->ports, priv->info->num_ports);
if (!priv->ports)
return -ENOMEM;
@@ -260,9 +313,11 @@ static int netc_init_all_ports(struct netc_switch *priv)
* difficult for us to cover.
*/
for (int i = 0; i < priv->info->num_ports; i++) {
- np = devm_kzalloc(dev, sizeof(*np), GFP_KERNEL);
- if (!np)
- return -ENOMEM;
+ np = kzalloc_obj(*np);
+ if (!np) {
+ err = -ENOMEM;
+ goto free_all_ports;
+ }
np->switch_priv = priv;
np->iobase = priv->regs.port + PORT_IOBASE(i);
@@ -278,18 +333,23 @@ static int netc_init_all_ports(struct netc_switch *priv)
err = netc_port_get_info_from_dt(np, dp->dn, dev);
if (err)
- return err;
+ goto free_all_ports;
if (dsa_port_is_user(dp)) {
err = netc_port_create_mdio_bus(np, dp->dn);
if (err) {
dev_err(dev, "Failed to create MDIO bus\n");
- return err;
+ goto free_all_ports;
}
}
}
return 0;
+
+free_all_ports:
+ netc_free_all_ports(priv);
+
+ return err;
}
static void netc_init_ntmp_tbl_versions(struct netc_switch *priv)
@@ -308,9 +368,7 @@ static int netc_init_all_cbdrs(struct netc_switch *priv)
ntmp->cbdr_num = NETC_CBDR_NUM;
ntmp->dev = priv->dev;
- ntmp->ring = devm_kcalloc(ntmp->dev, ntmp->cbdr_num,
- sizeof(struct netc_cbdr),
- GFP_KERNEL);
+ ntmp->ring = kzalloc_objs(*ntmp->ring, ntmp->cbdr_num);
if (!ntmp->ring)
return -ENOMEM;
@@ -336,6 +394,9 @@ static int netc_init_all_cbdrs(struct netc_switch *priv)
for (i--; i >= 0; i--)
ntmp_free_cbdr(&ntmp->ring[i]);
+ kfree(ntmp->ring);
+ ntmp->ring = NULL;
+
return err;
}
@@ -345,6 +406,9 @@ static void netc_remove_all_cbdrs(struct netc_switch *priv)
for (int i = 0; i < NETC_CBDR_NUM; i++)
ntmp_free_cbdr(&ntmp->ring[i]);
+
+ kfree(ntmp->ring);
+ ntmp->ring = NULL;
}
static u32 netc_num_available_ports(struct netc_switch *priv)
@@ -842,31 +906,49 @@ static void netc_ipv_to_buffer_pool_mapping(struct netc_switch *priv)
}
}
+static void netc_switch_free_bpt_list(struct netc_switch *priv)
+{
+ kfree(priv->bpt_list);
+ priv->bpt_list = NULL;
+}
+
static int netc_switch_bpt_default_config(struct netc_switch *priv)
{
+ int err;
+
if (priv->num_bp < priv->info->num_ports)
return -EINVAL;
- priv->bpt_list = devm_kcalloc(priv->dev, priv->num_bp,
- sizeof(struct bpt_cfge_data),
- GFP_KERNEL);
+ priv->bpt_list = kzalloc_objs(*priv->bpt_list, priv->num_bp);
if (!priv->bpt_list)
return -ENOMEM;
/* Initialize the maximum threshold of each buffer pool entry */
for (int i = 0; i < priv->num_bp; i++) {
struct bpt_cfge_data *cfge = &priv->bpt_list[i];
- int err;
cfge->max_thresh = cpu_to_le16(NETC_BP_THRESH);
err = ntmp_bpt_update_entry(&priv->ntmp, i, cfge);
if (err)
- return err;
+ goto free_bpt_list;
}
netc_ipv_to_buffer_pool_mapping(priv);
return 0;
+
+free_bpt_list:
+ netc_switch_free_bpt_list(priv);
+
+ return err;
+}
+
+static int netc_switch_reset(struct netc_switch *priv)
+{
+ if (priv->in_probe)
+ return pci_reset_function_locked(priv->pdev);
+
+ return pci_reset_function(priv->pdev);
}
static int netc_setup(struct dsa_switch *ds)
@@ -875,6 +957,10 @@ static int netc_setup(struct dsa_switch *ds)
struct dsa_port *dp;
int err;
+ err = netc_switch_reset(priv);
+ if (err)
+ return err;
+
err = netc_init_switch_id(priv);
if (err)
return err;
@@ -887,7 +973,7 @@ static int netc_setup(struct dsa_switch *ds)
err = netc_init_ntmp_user(priv);
if (err)
- return err;
+ goto free_all_ports;
INIT_HLIST_HEAD(&priv->fdb_list);
mutex_init(&priv->fdbt_lock);
@@ -910,22 +996,25 @@ static int netc_setup(struct dsa_switch *ds)
err = netc_add_standalone_vlan_entry(priv);
if (err)
- goto free_lock_and_ntmp_user;
+ goto free_bpt_list;
err = netc_add_standalone_fdb_bcast_entry(priv);
if (err)
- goto free_lock_and_ntmp_user;
+ goto free_bpt_list;
return 0;
+free_bpt_list:
+ netc_switch_free_bpt_list(priv);
free_lock_and_ntmp_user:
- /* No need to clear the hardware state, netc_setup() is only called
- * when the driver is bound, and FLR will be performed to reset the
- * hardware state.
+ /* No need to clear the hardware state. netc_setup() resets the
+ * hardware via FLR, so the next setup starts from a clean state.
*/
mutex_destroy(&priv->fdbt_lock);
mutex_destroy(&priv->vft_lock);
netc_free_ntmp_user(priv);
+free_all_ports:
+ netc_free_all_ports(priv);
return err;
}
@@ -945,10 +1034,10 @@ static void netc_free_host_flood_rules(struct netc_switch *priv)
dsa_switch_for_each_user_port(dp, priv->ds) {
struct netc_port *np = priv->ports[dp->index];
- /* No need to clear the hardware IPFT entry. Because PCIe
- * FLR will be performed when the switch is re-registered,
- * it will reset hardware state. So only need to free the
- * memory to avoid memory leak.
+ /* No need to clear the hardware IPFT entry. netc_setup()
+ * resets the hardware via FLR, so the next setup starts
+ * from a clean state. Only free the memory here to avoid
+ * a leak.
*/
kfree(np->host_flood);
np->host_flood = NULL;
@@ -963,6 +1052,8 @@ static void netc_teardown(struct dsa_switch *ds)
netc_destroy_all_lists(priv);
netc_free_host_flood_rules(priv);
netc_free_ntmp_user(priv);
+ netc_switch_free_bpt_list(priv);
+ netc_free_all_ports(priv);
}
static bool netc_port_is_emdio_consumer(struct device_node *node)
@@ -1045,7 +1136,6 @@ static int netc_switch_pci_init(struct pci_dev *pdev)
void __iomem *base;
int err;
- pcie_flr(pdev);
err = pcim_enable_device(pdev);
if (err)
return dev_err_probe(dev, err, "Failed to enable device\n");
@@ -2475,12 +2565,15 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->ageing_time_max = U32_MAX;
ds->priv = priv;
priv->ds = ds;
+ priv->in_probe = true;
err = dsa_register_switch(ds);
if (err)
return dev_err_probe(dev, err,
"Failed to register DSA switch\n");
+ priv->in_probe = false;
+
return 0;
}
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 305f2a92e2f9..4a8c5f9cde38 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -118,6 +118,7 @@ struct netc_switch {
struct device *dev;
struct dsa_switch *ds;
u16 revision;
+ bool in_probe;
const struct netc_switch_info *info;
struct netc_switch_regs regs;
--
2.34.1
next prev parent reply other threads:[~2026-10-12 9:55 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 9:59 [PATCH v5 net-next 0/9] net: dsa: netc: add PTP support for NETC switch wei.fang
2026-10-12 9:59 ` [PATCH v5 net-next 1/9] ptp: netc: use ioread64_lo_hi/iowrite64_lo_hi for 64-bit register access wei.fang
2026-10-12 9:59 ` [PATCH v5 net-next 2/9] ptp: netc: remove unnecessary pcie_flr() call in probe wei.fang
2026-10-12 9:59 ` [PATCH v5 net-next 3/9] ptp: netc: export netc_timer_get_current_time() for cross-driver use wei.fang
2026-10-12 9:59 ` wei.fang [this message]
2026-10-12 9:59 ` [PATCH v5 net-next 5/9] net: dsa: netc: use entry ID instead of pointer to track host flood rule wei.fang
2026-10-12 9:59 ` [PATCH v5 net-next 6/9] net: dsa: netc: check return value of ntmp_ipft_delete_entry() wei.fang
2026-10-12 9:59 ` [PATCH v5 net-next 7/9] net: dsa: netc: enable ingress port filtering lookup by default wei.fang
2026-10-12 9:59 ` [PATCH v5 net-next 8/9] net: dsa: netc: add PTP two-step timestamping support wei.fang
2026-10-12 9:59 ` [PATCH v5 net-next 9/9] net: dsa: netc: add PTP one-step " wei.fang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261012095924.1129312-5-wei.fang@oss.nxp.com \
--to=wei.fang@oss.nxp.com \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=imx@lists.linux.dev \
--cc=kuba@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=netdev@vger.kernel.org \
--cc=olteanv@gmail.com \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=wei.fang@nxp.com \
--cc=xiaoning.wang@nxp.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®