* [PATCH v7 net-next 10/15] net: dsa: netc: introduce NXP NETC switch driver for i.MX94
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
For i.MX94 series, the NETC IP provides full 802.1Q Ethernet switch
functionality, advanced QoS with 8 traffic classes, and a full range of
TSN standards capabilities. The switch has 3 user ports and 1 CPU port,
the CPU port is connected to an internal ENETC. Since the switch and the
internal ENETC are fully integrated within the NETC IP, no back-to-back
MAC connection is required. Instead, a light-weight "pseudo MAC" is used
between the switch and the ENETC. This translates to lower power (less
logic and memory) and lower delay (as there is no serialization delay
across this link).
Introduce the initial NETC switch driver with basic probe and remove
functionality. More features will be added in subsequent patches.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
MAINTAINERS | 11 +
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/netc/Kconfig | 15 +
drivers/net/dsa/netc/Makefile | 3 +
drivers/net/dsa/netc/netc_main.c | 600 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 49 +++
drivers/net/dsa/netc/netc_switch.h | 92 ++++
drivers/net/dsa/netc/netc_switch_hw.h | 133 ++++++
9 files changed, 906 insertions(+)
create mode 100644 drivers/net/dsa/netc/Kconfig
create mode 100644 drivers/net/dsa/netc/Makefile
create mode 100644 drivers/net/dsa/netc/netc_main.c
create mode 100644 drivers/net/dsa/netc/netc_platform.c
create mode 100644 drivers/net/dsa/netc/netc_switch.h
create mode 100644 drivers/net/dsa/netc/netc_switch_hw.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 5bbbbde6b907..78d0a6038086 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19290,6 +19290,17 @@ F: Documentation/devicetree/bindings/clock/*imx*
F: drivers/clk/imx/
F: include/dt-bindings/clock/*imx*
+NXP NETC ETHERNET SWITCH DRIVER
+M: Wei Fang <wei.fang@nxp.com>
+R: Clark Wang <xiaoning.wang@nxp.com>
+L: imx@lists.linux.dev
+L: netdev@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
+F: drivers/net/dsa/netc/
+F: include/linux/dsa/tag_netc.h
+F: net/dsa/tag_netc.c
+
NXP NETC TIMER PTP CLOCK DRIVER
M: Wei Fang <wei.fang@nxp.com>
M: Clark Wang <xiaoning.wang@nxp.com>
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 39fb8ead16b5..4ab567c5bbaf 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -76,6 +76,8 @@ source "drivers/net/dsa/mv88e6xxx/Kconfig"
source "drivers/net/dsa/mxl862xx/Kconfig"
+source "drivers/net/dsa/netc/Kconfig"
+
source "drivers/net/dsa/ocelot/Kconfig"
source "drivers/net/dsa/qca/Kconfig"
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index f5a463b87ec2..d2975badffc0 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -21,6 +21,7 @@ obj-y += lantiq/
obj-y += microchip/
obj-y += mv88e6xxx/
obj-y += mxl862xx/
+obj-y += netc/
obj-y += ocelot/
obj-y += qca/
obj-y += realtek/
diff --git a/drivers/net/dsa/netc/Kconfig b/drivers/net/dsa/netc/Kconfig
new file mode 100644
index 000000000000..0f246ac9e018
--- /dev/null
+++ b/drivers/net/dsa/netc/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NET_DSA_NETC_SWITCH
+ tristate "NXP NETC Ethernet switch support"
+ depends on ARM64 || COMPILE_TEST
+ depends on NET_DSA && PCI
+ select NET_DSA_TAG_NETC
+ select FSL_ENETC_MDIO
+ select NXP_NTMP
+ select NXP_NETC_LIB
+ help
+ This driver supports the NXP NETC Ethernet switch, which is embedded
+ as a PCIe function of the NXP NETC IP. But note that this driver is
+ is only available for NETC v4.3 and later versions.
+
+ If compiled as module (M), the module name is nxp-netc-switch.
diff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile
new file mode 100644
index 000000000000..4a5767562574
--- /dev/null
+++ b/drivers/net/dsa/netc/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o
+nxp-netc-switch-objs := netc_main.o netc_platform.o
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
new file mode 100644
index 000000000000..8e3a3230226c
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/fsl/enetc_mdio.h>
+#include <linux/if_vlan.h>
+#include <linux/of_mdio.h>
+
+#include "netc_switch.h"
+
+static enum dsa_tag_protocol
+netc_get_tag_protocol(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol mprot)
+{
+ return DSA_TAG_PROTO_NETC;
+}
+
+static void netc_port_rmw(struct netc_port *np, u32 reg,
+ u32 mask, u32 val)
+{
+ u32 old, new;
+
+ WARN_ON((mask | val) != mask);
+
+ old = netc_port_rd(np, reg);
+ new = (old & ~mask) | val;
+ if (new == old)
+ return;
+
+ netc_port_wr(np, reg, new);
+}
+
+static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
+{
+ if (is_netc_pseudo_port(np))
+ return;
+
+ netc_port_wr(np, reg, val);
+ if (np->caps.pmac)
+ netc_port_wr(np, reg + NETC_PMAC_OFFSET, val);
+}
+
+static void netc_port_get_capability(struct netc_port *np)
+{
+ u32 val;
+
+ val = netc_port_rd(np, NETC_PMCAPR);
+ if (val & PMCAPR_HD)
+ np->caps.half_duplex = true;
+
+ if (FIELD_GET(PMCAPR_FP, val) == FP_SUPPORT)
+ np->caps.pmac = true;
+
+ val = netc_port_rd(np, NETC_PCAPR);
+ if (val & PCAPR_LINK_TYPE)
+ np->caps.pseudo_link = true;
+}
+
+static int netc_port_create_emdio_bus(struct netc_port *np,
+ struct device_node *node)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct enetc_mdio_priv *mdio_priv;
+ struct device *dev = priv->dev;
+ struct enetc_hw *hw;
+ struct mii_bus *bus;
+ int err;
+
+ hw = enetc_hw_alloc(dev, np->iobase);
+ if (IS_ERR(hw))
+ 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->name = "NXP NETC switch external MDIO Bus";
+ bus->read = enetc_mdio_read_c22;
+ bus->write = enetc_mdio_write_c22;
+ bus->read_c45 = enetc_mdio_read_c45;
+ bus->write_c45 = enetc_mdio_write_c45;
+ bus->parent = dev;
+ mdio_priv = bus->priv;
+ mdio_priv->hw = hw;
+ mdio_priv->mdio_base = NETC_EMDIO_BASE;
+ 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");
+
+ np->emdio = bus;
+
+ return 0;
+}
+
+static int netc_port_create_mdio_bus(struct netc_port *np,
+ struct device_node *node)
+{
+ struct device_node *mdio_node;
+ int err;
+
+ mdio_node = of_get_child_by_name(node, "mdio");
+ if (mdio_node) {
+ err = netc_port_create_emdio_bus(np, mdio_node);
+ of_node_put(mdio_node);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int netc_init_switch_id(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ struct dsa_switch *ds = priv->ds;
+
+ /* The value of 0 is reserved for the VEPA switch and cannot
+ * be used. So 'dsa,member' is a required property for NETC
+ * switch, the member is used to specify the switch ID, which
+ * cannot be zero. This way, the hardware switch ID and the
+ * software switch ID are consistent.
+ */
+ if (ds->index > FIELD_MAX(SWCR_SWID) || !ds->index) {
+ dev_err(priv->dev, "Switch index %d out of range\n",
+ ds->index);
+ return -ERANGE;
+ }
+
+ netc_base_wr(regs, NETC_SWCR, ds->index);
+
+ return 0;
+}
+
+static int netc_init_all_ports(struct netc_switch *priv)
+{
+ struct device *dev = priv->dev;
+ struct netc_port *np;
+ struct dsa_port *dp;
+ int err;
+
+ priv->ports = devm_kcalloc(dev, priv->info->num_ports,
+ sizeof(struct netc_port *),
+ GFP_KERNEL);
+ if (!priv->ports)
+ return -ENOMEM;
+
+ /* Some DSA interfaces may set the port even it is disabled, such
+ * as .port_disable(), .port_stp_state_set() and so on. To avoid
+ * crash caused by accessing NULL port pointer, each port is
+ * allocated its own memory. Otherwise, we need to check whether
+ * the port pointer is NULL in these interfaces. The latter is
+ * 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->switch_priv = priv;
+ np->iobase = priv->regs.port + PORT_IOBASE(i);
+ netc_port_get_capability(np);
+ priv->ports[i] = np;
+ }
+
+ dsa_switch_for_each_available_port(dp, priv->ds) {
+ np = priv->ports[dp->index];
+ np->dp = dp;
+
+ 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;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void netc_init_ntmp_tbl_versions(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ /* All tables default to version 0 */
+ memset(&ntmp->tbl, 0, sizeof(ntmp->tbl));
+}
+
+static int netc_init_all_cbdrs(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ int i, err;
+
+ 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);
+ if (!ntmp->ring)
+ return -ENOMEM;
+
+ for (i = 0; i < ntmp->cbdr_num; i++) {
+ struct netc_cbdr *cbdr = &ntmp->ring[i];
+ struct netc_cbdr_regs cbdr_regs;
+
+ cbdr_regs.pir = regs->base + NETC_CBDRPIR(i);
+ cbdr_regs.cir = regs->base + NETC_CBDRCIR(i);
+ cbdr_regs.mr = regs->base + NETC_CBDRMR(i);
+ cbdr_regs.bar0 = regs->base + NETC_CBDRBAR0(i);
+ cbdr_regs.bar1 = regs->base + NETC_CBDRBAR1(i);
+ cbdr_regs.lenr = regs->base + NETC_CBDRLENR(i);
+
+ err = ntmp_init_cbdr(cbdr, ntmp->dev, &cbdr_regs);
+ if (err)
+ goto free_cbdrs;
+ }
+
+ return 0;
+
+free_cbdrs:
+ for (i--; i >= 0; i--)
+ ntmp_free_cbdr(&ntmp->ring[i]);
+
+ return err;
+}
+
+static void netc_remove_all_cbdrs(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ for (int i = 0; i < NETC_CBDR_NUM; i++)
+ ntmp_free_cbdr(&ntmp->ring[i]);
+}
+
+static int netc_init_ntmp_user(struct netc_switch *priv)
+{
+ netc_init_ntmp_tbl_versions(priv);
+
+ return netc_init_all_cbdrs(priv);
+}
+
+static void netc_free_ntmp_user(struct netc_switch *priv)
+{
+ netc_remove_all_cbdrs(priv);
+}
+
+static void netc_switch_dos_default_config(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = DOSL2CR_SAMEADDR | DOSL2CR_MSAMCC;
+ netc_base_wr(regs, NETC_DOSL2CR, val);
+
+ val = DOSL3CR_SAMEADDR | DOSL3CR_IPSAMCC;
+ netc_base_wr(regs, NETC_DOSL3CR, val);
+}
+
+static void netc_switch_vfht_default_config(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = netc_base_rd(regs, NETC_VFHTDECR2);
+
+ /* If no match is found in the VLAN Filter table, then VFHTDECR2[MLO]
+ * will take effect. VFHTDECR2[MLO] is set to "Software MAC learning
+ * secure" by default. Notice BPCR[MLO] will override VFHTDECR2[MLO]
+ * if its value is not zero.
+ */
+ val = u32_replace_bits(val, MLO_SW_SEC, VFHTDECR2_MLO);
+ val = u32_replace_bits(val, MFO_NO_MATCH_DISCARD, VFHTDECR2_MFO);
+ netc_base_wr(regs, NETC_VFHTDECR2, val);
+}
+
+static void netc_port_set_max_frame_size(struct netc_port *np,
+ u32 max_frame_size)
+{
+ netc_mac_port_wr(np, NETC_PM_MAXFRM(0),
+ max_frame_size & PM_MAXFRAM);
+}
+
+static void netc_switch_fixed_config(struct netc_switch *priv)
+{
+ netc_switch_dos_default_config(priv);
+ netc_switch_vfht_default_config(priv);
+}
+
+static void netc_port_set_tc_max_sdu(struct netc_port *np,
+ int tc, u32 max_sdu)
+{
+ u32 val = FIELD_PREP(PTCTMSDUR_MAXSDU, max_sdu) |
+ FIELD_PREP(PTCTMSDUR_SDU_TYPE, SDU_TYPE_MPDU);
+
+ netc_port_wr(np, NETC_PTCTMSDUR(tc), val);
+}
+
+static void netc_port_set_all_tc_msdu(struct netc_port *np)
+{
+ for (int tc = 0; tc < NETC_TC_NUM; tc++)
+ netc_port_set_tc_max_sdu(np, tc, NETC_MAX_FRAME_LEN);
+}
+
+static void netc_port_set_mlo(struct netc_port *np, enum netc_mlo mlo)
+{
+ netc_port_rmw(np, NETC_BPCR, BPCR_MLO, FIELD_PREP(BPCR_MLO, mlo));
+}
+
+static void netc_port_fixed_config(struct netc_port *np)
+{
+ /* Default IPV and DR setting */
+ netc_port_rmw(np, NETC_PQOSMR, PQOSMR_VS | PQOSMR_VE,
+ PQOSMR_VS | PQOSMR_VE);
+
+ /* Enable L2 and L3 DOS */
+ netc_port_rmw(np, NETC_PCR, PCR_L2DOSE | PCR_L3DOSE,
+ PCR_L2DOSE | PCR_L3DOSE);
+}
+
+static void netc_port_default_config(struct netc_port *np)
+{
+ netc_port_fixed_config(np);
+
+ /* Default VLAN unaware */
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM, BPDVR_RXVAM);
+
+ if (dsa_port_is_cpu(np->dp))
+ /* For CPU port, source port pruning is disabled */
+ netc_port_rmw(np, NETC_BPCR, BPCR_SRCPRND, BPCR_SRCPRND);
+ else
+ netc_port_set_mlo(np, MLO_DISABLE);
+
+ netc_port_set_max_frame_size(np, NETC_MAX_FRAME_LEN);
+ netc_port_set_all_tc_msdu(np);
+}
+
+static int netc_setup(struct dsa_switch *ds)
+{
+ struct netc_switch *priv = ds->priv;
+ struct dsa_port *dp;
+ int err;
+
+ err = netc_init_switch_id(priv);
+ if (err)
+ return err;
+
+ err = netc_init_all_ports(priv);
+ if (err)
+ return err;
+
+ err = netc_init_ntmp_user(priv);
+ if (err)
+ return err;
+
+ netc_switch_fixed_config(priv);
+
+ /* default setting for ports */
+ dsa_switch_for_each_available_port(dp, ds)
+ netc_port_default_config(priv->ports[dp->index]);
+
+ return 0;
+}
+
+static void netc_teardown(struct dsa_switch *ds)
+{
+ struct netc_switch *priv = ds->priv;
+
+ netc_free_ntmp_user(priv);
+}
+
+static bool netc_port_is_emdio_consumer(struct device_node *node)
+{
+ struct device_node *mdio_node;
+
+ /* If the port node has phy-handle property and it does
+ * not contain a mdio child node, then the port is the
+ * EMDIO consumer.
+ */
+ mdio_node = of_get_child_by_name(node, "mdio");
+ if (!mdio_node)
+ return true;
+
+ of_node_put(mdio_node);
+
+ return false;
+}
+
+/* Currently, phylink_of_phy_connect() is called by dsa_user_create(),
+ * so if the switch uses the external MDIO controller (like the EMDIO
+ * function) to manage the external PHYs. The MDIO bus may not be
+ * created when phylink_of_phy_connect() is called, so it will return
+ * an error and cause the switch driver to fail to probe.
+ * This workaround can be removed when DSA phylink_of_phy_connect()
+ * calls are moved from probe() to ndo_open().
+ */
+static int netc_switch_check_emdio_is_ready(struct device *dev)
+{
+ struct device_node *ports, *phy_node;
+ struct phy_device *phydev;
+ int err = 0;
+
+ ports = of_get_child_by_name(dev->of_node, "ethernet-ports");
+ if (!ports) {
+ dev_err(dev, "Cannot find the ethernet-ports node\n");
+ return -EINVAL;
+ }
+
+ for_each_available_child_of_node_scoped(ports, child) {
+ /* If the node does not have phy-handle property, then the
+ * port does not connect to a PHY, so the port is not the
+ * EMDIO consumer.
+ */
+ phy_node = of_parse_phandle(child, "phy-handle", 0);
+ if (!phy_node)
+ continue;
+
+ /* Note that from the hardware perspective, the switch ports
+ * do not support sharing the MDIO bus defined under one port.
+ * Each port can only access its own external PHY through its
+ * port MDIO bus.
+ */
+ if (!netc_port_is_emdio_consumer(child)) {
+ of_node_put(phy_node);
+ continue;
+ }
+
+ phydev = of_phy_find_device(phy_node);
+ of_node_put(phy_node);
+ if (!phydev) {
+ err = -EPROBE_DEFER;
+ goto out;
+ }
+
+ put_device(&phydev->mdio.dev);
+ }
+
+out:
+ of_node_put(ports);
+
+ return err;
+}
+
+static int netc_switch_pci_init(struct pci_dev *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct netc_switch_regs *regs;
+ struct netc_switch *priv;
+ 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");
+
+ err = pcim_request_all_regions(pdev, KBUILD_MODNAME);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to request regions\n");
+
+ /* The command BD rings and NTMP tables need DMA. No need to check
+ * the return value, because it never returns fail when the mask is
+ * DMA_BIT_MASK(64), see dma-api-howto.rst.
+ */
+ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+
+ if (pci_resource_len(pdev, NETC_REGS_BAR) < NETC_REGS_SIZE) {
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid register space size\n");
+ }
+
+ base = pcim_iomap(pdev, NETC_REGS_BAR, 0);
+ if (!base)
+ return dev_err_probe(dev, -ENXIO, "pcim_iomap() failed\n");
+
+ pci_set_master(pdev);
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->dev = dev;
+
+ regs = &priv->regs;
+ regs->base = base;
+ regs->port = regs->base + NETC_REGS_PORT_BASE;
+ regs->global = regs->base + NETC_REGS_GLOBAL_BASE;
+ pci_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static void netc_switch_get_ip_revision(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val = netc_glb_rd(regs, NETC_IPBRR0);
+
+ priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
+}
+
+static const struct dsa_switch_ops netc_switch_ops = {
+ .get_tag_protocol = netc_get_tag_protocol,
+ .setup = netc_setup,
+ .teardown = netc_teardown,
+};
+
+static int netc_switch_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct device_node *node = dev_of_node(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct netc_switch *priv;
+ struct dsa_switch *ds;
+ int err;
+
+ if (!node)
+ return dev_err_probe(dev, -ENODEV,
+ "No DT bindings, skipping\n");
+
+ err = netc_switch_check_emdio_is_ready(dev);
+ if (err)
+ return err;
+
+ err = netc_switch_pci_init(pdev);
+ if (err)
+ return err;
+
+ priv = pci_get_drvdata(pdev);
+ netc_switch_get_ip_revision(priv);
+
+ err = netc_switch_platform_probe(priv);
+ if (err)
+ return err;
+
+ ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
+ if (!ds)
+ return -ENOMEM;
+
+ ds->dev = dev;
+ ds->num_ports = priv->info->num_ports;
+ ds->num_tx_queues = NETC_TC_NUM;
+ ds->ops = &netc_switch_ops;
+ ds->priv = priv;
+ priv->ds = ds;
+
+ err = dsa_register_switch(ds);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Failed to register DSA switch\n");
+
+ return 0;
+}
+
+static void netc_switch_remove(struct pci_dev *pdev)
+{
+ struct netc_switch *priv = pci_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_unregister_switch(priv->ds);
+}
+
+static void netc_switch_shutdown(struct pci_dev *pdev)
+{
+ struct netc_switch *priv = pci_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_switch_shutdown(priv->ds);
+ pci_set_drvdata(pdev, NULL);
+}
+
+static const struct pci_device_id netc_switch_ids[] = {
+ { PCI_DEVICE(NETC_SWITCH_VENDOR_ID, NETC_SWITCH_DEVICE_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, netc_switch_ids);
+
+static struct pci_driver netc_switch_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = netc_switch_ids,
+ .probe = netc_switch_probe,
+ .remove = netc_switch_remove,
+ .shutdown = netc_switch_shutdown,
+};
+module_pci_driver(netc_switch_driver);
+
+MODULE_DESCRIPTION("NXP NETC Switch driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c
new file mode 100644
index 000000000000..abd599ea9c8d
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include "netc_switch.h"
+
+struct netc_switch_platform {
+ u16 revision;
+ const struct netc_switch_info *info;
+};
+
+static const struct netc_switch_info imx94_info = {
+ .num_ports = 4,
+};
+
+static const struct netc_switch_platform netc_platforms[] = {
+ { .revision = NETC_SWITCH_REV_4_3, .info = &imx94_info, },
+ { }
+};
+
+static const struct netc_switch_info *
+netc_switch_get_info(struct netc_switch *priv)
+{
+ int i;
+
+ /* Matching based on IP revision */
+ for (i = 0; i < ARRAY_SIZE(netc_platforms); i++) {
+ if (priv->revision == netc_platforms[i].revision)
+ return netc_platforms[i].info;
+ }
+
+ return NULL;
+}
+
+int netc_switch_platform_probe(struct netc_switch *priv)
+{
+ const struct netc_switch_info *info = netc_switch_get_info(priv);
+
+ if (!info) {
+ dev_err(priv->dev, "Cannot find switch platform info\n");
+ return -EINVAL;
+ }
+
+ priv->info = info;
+
+ return 0;
+}
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
new file mode 100644
index 000000000000..a6d36dcebc6d
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef _NETC_SWITCH_H
+#define _NETC_SWITCH_H
+
+#include <linux/dsa/tag_netc.h>
+#include <linux/fsl/netc_global.h>
+#include <linux/fsl/ntmp.h>
+#include <linux/of_device.h>
+#include <linux/of_net.h>
+#include <linux/pci.h>
+
+#include "netc_switch_hw.h"
+
+#define NETC_REGS_BAR 0
+#define NETC_REGS_SIZE 0x80000
+#define NETC_MSIX_TBL_BAR 2
+#define NETC_REGS_PORT_BASE 0x4000
+/* register block size per port */
+#define NETC_REGS_PORT_SIZE 0x4000
+#define PORT_IOBASE(p) (NETC_REGS_PORT_SIZE * (p))
+#define NETC_REGS_GLOBAL_BASE 0x70000
+
+#define NETC_SWITCH_REV_4_3 0x0403
+
+#define NETC_TC_NUM 8
+#define NETC_CBDR_NUM 2
+
+#define NETC_MAX_FRAME_LEN 9600
+
+struct netc_switch;
+
+struct netc_switch_info {
+ u32 num_ports;
+};
+
+struct netc_port_caps {
+ u32 half_duplex:1; /* indicates whether the port support half-duplex */
+ u32 pmac:1; /* indicates whether the port has preemption MAC */
+ u32 pseudo_link:1;
+};
+
+struct netc_port {
+ void __iomem *iobase;
+ struct netc_switch *switch_priv;
+ struct netc_port_caps caps;
+ struct dsa_port *dp;
+ struct mii_bus *emdio;
+};
+
+struct netc_switch_regs {
+ void __iomem *base;
+ void __iomem *port;
+ void __iomem *global;
+};
+
+struct netc_switch {
+ struct pci_dev *pdev;
+ struct device *dev;
+ struct dsa_switch *ds;
+ u16 revision;
+
+ const struct netc_switch_info *info;
+ struct netc_switch_regs regs;
+ struct netc_port **ports;
+
+ struct ntmp_user ntmp;
+};
+
+/* Write/Read Switch base registers */
+#define netc_base_rd(r, o) netc_read((r)->base + (o))
+#define netc_base_wr(r, o, v) netc_write((r)->base + (o), v)
+
+/* Write/Read registers of Switch Port (including pseudo MAC port) */
+#define netc_port_rd(p, o) netc_read((p)->iobase + (o))
+#define netc_port_wr(p, o, v) netc_write((p)->iobase + (o), v)
+
+/* Write/Read Switch global registers */
+#define netc_glb_rd(r, o) netc_read((r)->global + (o))
+#define netc_glb_wr(r, o, v) netc_write((r)->global + (o), v)
+
+static inline bool is_netc_pseudo_port(struct netc_port *np)
+{
+ return np->caps.pseudo_link;
+}
+
+int netc_switch_platform_probe(struct netc_switch *priv);
+
+#endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
new file mode 100644
index 000000000000..0419f7f9207e
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -0,0 +1,133 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef _NETC_SWITCH_HW_H
+#define _NETC_SWITCH_HW_H
+
+#include <linux/bitops.h>
+
+#define NETC_SWITCH_VENDOR_ID 0x1131
+#define NETC_SWITCH_DEVICE_ID 0xeef2
+
+/* Definition of Switch base registers */
+#define NETC_CBDRMR(a) (0x0800 + (a) * 0x30)
+#define NETC_CBDRBAR0(a) (0x0810 + (a) * 0x30)
+#define NETC_CBDRBAR1(a) (0x0814 + (a) * 0x30)
+#define NETC_CBDRPIR(a) (0x0818 + (a) * 0x30)
+#define NETC_CBDRCIR(a) (0x081c + (a) * 0x30)
+#define NETC_CBDRLENR(a) (0x0820 + (a) * 0x30)
+
+#define NETC_SWCR 0x1018
+#define SWCR_SWID GENMASK(2, 0)
+
+#define NETC_DOSL2CR 0x1220
+#define DOSL2CR_SAMEADDR BIT(0)
+#define DOSL2CR_MSAMCC BIT(1)
+
+#define NETC_DOSL3CR 0x1224
+#define DOSL3CR_SAMEADDR BIT(0)
+#define DOSL3CR_IPSAMCC BIT(1)
+
+#define NETC_VFHTDECR1 0x2014
+#define NETC_VFHTDECR2 0x2018
+#define VFHTDECR2_ET_PORT(a) BIT((a))
+#define VFHTDECR2_MLO GENMASK(26, 24)
+#define VFHTDECR2_MFO GENMASK(28, 27)
+
+/* Definition of Switch port registers */
+#define NETC_PCAPR 0x0000
+#define PCAPR_LINK_TYPE BIT(4)
+#define PCAPR_NUM_TC GENMASK(15, 12)
+#define PCAPR_NUM_Q GENMASK(19, 16)
+#define PCAPR_NUM_CG GENMASK(27, 24)
+#define PCAPR_TGS BIT(28)
+#define PCAPR_CBS BIT(29)
+
+#define NETC_PMCAPR 0x0004
+#define PMCAPR_HD BIT(8)
+#define PMCAPR_FP GENMASK(10, 9)
+#define FP_SUPPORT 2
+
+#define NETC_PCR 0x0010
+#define PCR_HDR_FMT BIT(0)
+#define PCR_NS_TAG_PORT BIT(3)
+#define PCR_L2DOSE BIT(4)
+#define PCR_L3DOSE BIT(5)
+#define PCR_TIMER_CS BIT(8)
+#define PCR_PSPEED GENMASK(29, 16)
+#define PSPEED_SET_VAL(s) FIELD_PREP(PCR_PSPEED, ((s) / 10 - 1))
+
+#define NETC_PQOSMR 0x0054
+#define PQOSMR_VS BIT(0)
+#define PQOSMR_VE BIT(1)
+#define PQOSMR_DDR GENMASK(3, 2)
+#define PQOSMR_DIPV GENMASK(6, 4)
+#define PQOSMR_VQMP GENMASK(19, 16)
+#define PQOSMR_QVMP GENMASK(23, 20)
+
+#define NETC_PTCTMSDUR(a) (0x208 + (a) * 0x20)
+#define PTCTMSDUR_MAXSDU GENMASK(15, 0)
+#define PTCTMSDUR_SDU_TYPE GENMASK(17, 16)
+#define SDU_TYPE_PPDU 0
+#define SDU_TYPE_MPDU 1
+#define SDU_TYPE_MSDU 2
+
+#define NETC_BPCR 0x500
+#define BPCR_DYN_LIMIT GENMASK(15, 0)
+#define BPCR_MLO GENMASK(22, 20)
+#define BPCR_UUCASTE BIT(24)
+#define BPCR_UMCASTE BIT(25)
+#define BPCR_MCASTE BIT(26)
+#define BPCR_BCASTE BIT(27)
+#define BPCR_STAMVD BIT(28)
+#define BPCR_SRCPRND BIT(29)
+
+/* MAC learning options, see BPCR[MLO], VFHTDECR2[MLO] and
+ * VLAN Filter Table CFGE_DATA[MLO]
+ */
+enum netc_mlo {
+ MLO_NOT_OVERRIDE = 0,
+ MLO_DISABLE,
+ MLO_HW,
+ MLO_SW_SEC,
+ MLO_SW_UNSEC,
+ MLO_DISABLE_SMAC,
+};
+
+/* MAC forwarding options, see VFHTDECR2[MFO] and VLAN
+ * Filter Table CFGE_DATA[MFO]
+ */
+enum netc_mfo {
+ MFO_NO_FDB_LOOKUP = 1,
+ MFO_NO_MATCH_FLOOD,
+ MFO_NO_MATCH_DISCARD,
+};
+
+#define NETC_BPDVR 0x510
+#define BPDVR_VID GENMASK(11, 0)
+#define BPDVR_DEI BIT(12)
+#define BPDVR_PCP GENMASK(15, 13)
+#define BPDVR_TPID BIT(16)
+#define BPDVR_RXTAGA GENMASK(23, 20)
+#define BPDVR_RXVAM BIT(24)
+#define BPDVR_TXTAGA GENMASK(26, 25)
+
+/* Definition of Switch ethernet MAC port registers */
+#define NETC_PMAC_OFFSET 0x400
+#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400)
+#define PM_CMD_CFG_TX_EN BIT(0)
+#define PM_CMD_CFG_RX_EN BIT(1)
+
+#define NETC_PM_MAXFRM(a) (0x1014 + (a) * 0x400)
+#define PM_MAXFRAM GENMASK(15, 0)
+
+#define NETC_PEMDIOCR 0x1c00
+#define NETC_EMDIO_BASE NETC_PEMDIOCR
+
+/* Definition of global registers (read only) */
+#define NETC_IPBRR0 0x0bf8
+#define IPBRR0_IP_REV GENMASK(15, 0)
+
+#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 09/15] net: dsa: add NETC switch tag support
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
The NXP NETC switch tag is a proprietary header added to frames after the
source MAC address. The switch tag has 3 types, and each type has 1 ~ 4
subtypes, the details are as follows.
Forward NXP switch tag (Type=0): Represents forwarded frames.
- SubType = 0 - Normal frame processing.
To_Port NXP switch tag (Type=1): Represents frames that are to be sent
to a specific switch port.
- SubType = 0. No request to perform timestamping.
- SubType = 1. Request to perform one-step timestamping.
- SubType = 2. Request to perform two-step timestamping.
- SubType = 3. Request to perform both one-step timestamping and
two-step timestamping.
To_Host NXP switch tag (Type=2): Represents frames redirected or copied
to the switch management port.
- SubType = 0. Received frames redirected or copied to the switch
management port.
- SubType = 1. Received frames redirected or copied to the switch
management port with captured timestamp at the switch port where
the frame was received.
- SubType = 2. Transmit timestamp response (two-step timestamping).
In addition, the length of different type switch tag is different, the
minimum length is 6 bytes, the maximum length is 14 bytes. Currently,
Forward tag, SubType 0 of To_Port tag and Subtype 0 of To_Host tag are
supported. More tags will be supported in the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
include/linux/dsa/tag_netc.h | 14 +++
include/net/dsa.h | 2 +
include/uapi/linux/if_ether.h | 1 +
net/dsa/Kconfig | 10 ++
net/dsa/Makefile | 1 +
net/dsa/tag_netc.c | 214 ++++++++++++++++++++++++++++++++++
6 files changed, 242 insertions(+)
create mode 100644 include/linux/dsa/tag_netc.h
create mode 100644 net/dsa/tag_netc.c
diff --git a/include/linux/dsa/tag_netc.h b/include/linux/dsa/tag_netc.h
new file mode 100644
index 000000000000..fe964722e5b0
--- /dev/null
+++ b/include/linux/dsa/tag_netc.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef __NET_DSA_TAG_NETC_H
+#define __NET_DSA_TAG_NETC_H
+
+#include <linux/skbuff.h>
+#include <net/dsa.h>
+
+#define NETC_TAG_MAX_LEN 14
+
+#endif
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 4cc67469cf2e..8c16ef23cc10 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -58,6 +58,7 @@ struct tc_action;
#define DSA_TAG_PROTO_YT921X_VALUE 30
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
+#define DSA_TAG_PROTO_NETC_VALUE 33
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -93,6 +94,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE,
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
+ DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
};
struct dsa_switch;
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index df9d44a11540..fb5efc8e06cc 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -123,6 +123,7 @@
#define ETH_P_DSA_A5PSW 0xE001 /* A5PSW Tag Value [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_IFE 0xED3E /* ForCES inter-FE LFB type */
#define ETH_P_AF_IUCV 0xFBFB /* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_NXP_NETC 0xFD3A /* NXP NETC DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_802_3_MIN 0x0600 /* If the value in the ethernet type is more than this value
* then the frame is Ethernet II. Else it is 802.3 */
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 5ed8c704636d..d5e725b90d78 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -125,6 +125,16 @@ config NET_DSA_TAG_KSZ
Say Y if you want to enable support for tagging frames for the
Microchip 8795/937x/9477/9893 families of switches.
+config NET_DSA_TAG_NETC
+ tristate "Tag driver for NXP NETC switches"
+ help
+ Say Y or M if you want to enable support for the NXP Switch Tag (NST),
+ as implemented by NXP NETC switches having version 4.3 or later. The
+ switch tag is a proprietary header added to frames after the source
+ MAC address, it has 3 types and each type has different subtypes, so
+ its length depends on the type and subtype of the tag, the maximum
+ length is 14 bytes.
+
config NET_DSA_TAG_OCELOT
tristate "Tag driver for Ocelot family of switches, using NPI port"
select PACKING
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index bf7247759a64..b8c2667cd14a 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o
+obj-$(CONFIG_NET_DSA_TAG_NETC) += tag_netc.o
obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o
obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o
obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o
diff --git a/net/dsa/tag_netc.c b/net/dsa/tag_netc.c
new file mode 100644
index 000000000000..07684e0ff064
--- /dev/null
+++ b/net/dsa/tag_netc.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/dsa/tag_netc.h>
+
+#include "tag.h"
+
+#define NETC_NAME "nxp_netc"
+
+/* Forward NXP switch tag */
+#define NETC_TAG_FORWARD 0
+
+/* To_Port NXP switch tag */
+#define NETC_TAG_TO_PORT 1
+/* SubType0: No request to perform timestamping */
+#define NETC_TAG_TP_SUBTYPE0 0
+
+/* To_Host NXP switch tag */
+#define NETC_TAG_TO_HOST 2
+/* SubType0: frames redirected or copied to CPU port */
+#define NETC_TAG_TH_SUBTYPE0 0
+/* SubType1: frames redirected or copied to CPU port with timestamp */
+#define NETC_TAG_TH_SUBTYPE1 1
+/* SubType2: Transmit timestamp response (two-step timestamping) */
+#define NETC_TAG_TH_SUBTYPE2 2
+
+/* NETC switch tag lengths */
+#define NETC_TAG_FORWARD_LEN 6
+#define NETC_TAG_TP_SUBTYPE0_LEN 6
+#define NETC_TAG_TH_SUBTYPE0_LEN 6
+#define NETC_TAG_TH_SUBTYPE1_LEN 14
+#define NETC_TAG_TH_SUBTYPE2_LEN 14
+#define NETC_TAG_CMN_LEN 5
+
+#define NETC_TAG_SUBTYPE GENMASK(3, 0)
+#define NETC_TAG_TYPE GENMASK(7, 4)
+#define NETC_TAG_QV BIT(0)
+#define NETC_TAG_IPV GENMASK(4, 2)
+#define NETC_TAG_SWITCH GENMASK(2, 0)
+#define NETC_TAG_PORT GENMASK(7, 3)
+
+struct netc_tag_cmn {
+ __be16 tpid;
+ u8 type;
+ u8 qos;
+ u8 switch_port;
+} __packed;
+
+static void netc_fill_common_tag(struct netc_tag_cmn *tag, u8 type,
+ u8 subtype, u8 sw_id, u8 port, u8 ipv)
+{
+ tag->tpid = htons(ETH_P_NXP_NETC);
+ tag->type = FIELD_PREP(NETC_TAG_TYPE, type) |
+ FIELD_PREP(NETC_TAG_SUBTYPE, subtype);
+ tag->qos = NETC_TAG_QV | FIELD_PREP(NETC_TAG_IPV, ipv);
+ tag->switch_port = FIELD_PREP(NETC_TAG_SWITCH, sw_id) |
+ FIELD_PREP(NETC_TAG_PORT, port);
+}
+
+static void *netc_fill_common_tp_tag(struct sk_buff *skb,
+ struct net_device *ndev,
+ u8 subtype, int tag_len)
+{
+ struct dsa_port *dp = dsa_user_to_port(ndev);
+ u16 queue = skb_get_queue_mapping(skb);
+ s8 ipv = netdev_txq_to_tc(ndev, queue);
+ void *tag;
+
+ if (unlikely(ipv < 0))
+ ipv = 0;
+
+ skb_push(skb, tag_len);
+ dsa_alloc_etype_header(skb, tag_len);
+
+ tag = dsa_etype_header_pos_tx(skb);
+ memset(tag + NETC_TAG_CMN_LEN, 0, tag_len - NETC_TAG_CMN_LEN);
+ /* As 'dsa,member' is a required property for NETC switch, the member
+ * is used to specify the switch ID (thus the hardware switch ID and
+ * the software switch ID are consistent), its range is 1 ~ 7. The
+ * NETC switch driver will check this value, and if it is invalid,
+ * the switch driver will fail the probe.
+ * In addition, according to the nxp,netc-switch.yaml doc, the port
+ * index will not be greater than 0xf.
+ */
+ netc_fill_common_tag(tag, NETC_TAG_TO_PORT, subtype,
+ dp->ds->index, dp->index, ipv);
+
+ return tag;
+}
+
+static void netc_fill_tp_tag_subtype0(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE0,
+ NETC_TAG_TP_SUBTYPE0_LEN);
+}
+
+/* Currently only support To_Port tag, subtype 0 */
+static struct sk_buff *netc_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ netc_fill_tp_tag_subtype0(skb, ndev);
+
+ return skb;
+}
+
+static int netc_get_rx_tag_len(int type, int subtype)
+{
+ /* Only NETC_TAG_TO_HOST and NETC_TAG_FORWARD are expected in RX,
+ * NETC_TAG_TO_PORT is a TX switch tag that does not exist in RX.
+ */
+ if (type == NETC_TAG_TO_HOST) {
+ if (subtype == NETC_TAG_TH_SUBTYPE1)
+ return NETC_TAG_TH_SUBTYPE1_LEN;
+ else if (subtype == NETC_TAG_TH_SUBTYPE2)
+ return NETC_TAG_TH_SUBTYPE2_LEN;
+ else
+ return NETC_TAG_TH_SUBTYPE0_LEN;
+ }
+
+ return NETC_TAG_FORWARD_LEN;
+}
+
+static struct sk_buff *netc_rcv(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct netc_tag_cmn *tag_cmn;
+ int tag_len, sw_id, port;
+ int type, subtype;
+
+ if (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN)))
+ return NULL;
+
+ tag_cmn = dsa_etype_header_pos_rx(skb);
+ if (ntohs(tag_cmn->tpid) != ETH_P_NXP_NETC) {
+ dev_warn_ratelimited(&ndev->dev, "Unknown TPID 0x%04x\n",
+ ntohs(tag_cmn->tpid));
+
+ return NULL;
+ }
+
+ if (tag_cmn->qos & NETC_TAG_QV)
+ skb->priority = FIELD_GET(NETC_TAG_IPV, tag_cmn->qos);
+
+ sw_id = FIELD_GET(NETC_TAG_SWITCH, tag_cmn->switch_port);
+ /* ENETC VEPA switch ID (0) is not supported yet */
+ if (!sw_id) {
+ dev_warn_ratelimited(&ndev->dev,
+ "VEPA switch ID is not supported yet\n");
+
+ return NULL;
+ }
+
+ port = FIELD_GET(NETC_TAG_PORT, tag_cmn->switch_port);
+ skb->dev = dsa_conduit_find_user(ndev, sw_id, port);
+ if (!skb->dev)
+ return NULL;
+
+ type = FIELD_GET(NETC_TAG_TYPE, tag_cmn->type);
+ subtype = FIELD_GET(NETC_TAG_SUBTYPE, tag_cmn->type);
+ if (type == NETC_TAG_FORWARD) {
+ dsa_default_offload_fwd_mark(skb);
+ } else if (type == NETC_TAG_TO_HOST) {
+ /* Currently only subtype0 supported */
+ if (subtype != NETC_TAG_TH_SUBTYPE0)
+ return NULL;
+ } else {
+ dev_warn_ratelimited(&ndev->dev,
+ "Unknown tag type %d\n", type);
+ return NULL;
+ }
+
+ /* Remove Switch tag from the frame */
+ tag_len = netc_get_rx_tag_len(type, subtype);
+ skb_pull_rcsum(skb, tag_len);
+ dsa_strip_etype_header(skb, tag_len);
+
+ return skb;
+}
+
+static void netc_flow_dissect(const struct sk_buff *skb, __be16 *proto,
+ int *offset)
+{
+ struct netc_tag_cmn *tag_cmn = (struct netc_tag_cmn *)(skb->data - 2);
+ int subtype = FIELD_GET(NETC_TAG_SUBTYPE, tag_cmn->type);
+ int type = FIELD_GET(NETC_TAG_TYPE, tag_cmn->type);
+ int tag_len = netc_get_rx_tag_len(type, subtype);
+
+ /* The RX minimum frame length of the NETC switch port is 64 bytes,
+ * and the frame is received by the ENETC driver. From the hardware
+ * perspective, the receive buffer of RX BD is at least 128 bytes,
+ * so the switch tag header is guaranteed to be in the linear region
+ * of the skb.
+ */
+ *offset = tag_len;
+ *proto = ((__be16 *)skb->data)[(tag_len / 2) - 1];
+}
+
+static const struct dsa_device_ops netc_netdev_ops = {
+ .name = NETC_NAME,
+ .proto = DSA_TAG_PROTO_NETC,
+ .xmit = netc_xmit,
+ .rcv = netc_rcv,
+ .needed_headroom = NETC_TAG_MAX_LEN,
+ .flow_dissect = netc_flow_dissect,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for NXP NETC switch family");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_NETC, NETC_NAME);
+module_dsa_tag_driver(netc_netdev_ops);
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 08/15] net: enetc: add multiple command BD rings support
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
All the tables of NETC switch are managed through the command BD ring,
but unlike ENETC, the switch has two command BD rings, if the current
ring is busy, the switch driver can switch to another ring to manage
the table. Currently, the NTMP driver does not support multiple rings.
Therefore, update ntmp_select_and_lock_cbdr() to select a appropriate
ring to execute the command for the switch.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 635032d24dc7..f71cad943424 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -146,11 +146,16 @@ static void ntmp_clean_cbdr(struct netc_cbdr *cbdr)
static void ntmp_select_and_lock_cbdr(struct ntmp_user *user,
struct netc_cbdr **cbdr)
{
- /* Currently only ENETC is supported, and it has only one command
- * BD ring.
- */
- *cbdr = &user->ring[0];
+ for (int i = 0; i < user->cbdr_num; i++) {
+ *cbdr = &user->ring[i];
+ if (mutex_trylock(&(*cbdr)->ring_lock))
+ return;
+ }
+ /* If all command BD rings are locked, we need to select one of
+ * them and wait for it.
+ */
+ *cbdr = &user->ring[raw_smp_processor_id() % user->cbdr_num];
mutex_lock(&(*cbdr)->ring_lock);
}
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 07/15] net: enetc: add support for "Add" and "Delete" operations to IPFT
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
The ingress port filter table (IPFT )contains a set of filters each
capable of classifying incoming traffic using a mix of L2, L3, and L4
parsed and arbitrary field data. As a result of a filter match, several
actions can be specified such as on whether to deny or allow a frame,
overriding internal QoS attributes associated with the frame and setting
parameters for the subsequent frame processing functions, such as stream
identification, policing, ingress mirroring. Each entry corresponds to a
filter. The ingress port filter entries are added using a precedence
value. If a frame matches multiple entries, the entry with the higher
precedence is used. Currently, this patch only adds "Add" and "Delete"
operations to the ingress port filter table. These two interfaces will
be used by both ENETC driver and NETC switch driver.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 80 ++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 36 +++++++
include/linux/fsl/ntmp.h | 93 +++++++++++++++++++
3 files changed, 209 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index ad89be85b185..635032d24dc7 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -21,6 +21,7 @@
/* Define NTMP Table ID */
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
+#define NTMP_IPFT_ID 13
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
#define NTMP_BPT_ID 41
@@ -271,6 +272,8 @@ static const char *ntmp_table_name(int tbl_id)
return "MAC Address Filter Table";
case NTMP_RSST_ID:
return "RSS Table";
+ case NTMP_IPFT_ID:
+ return "Ingress Port Filter Table";
case NTMP_FDBT_ID:
return "FDB Table";
case NTMP_VFT_ID:
@@ -513,6 +516,83 @@ int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count)
}
EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry);
+/**
+ * ntmp_ipft_add_entry - add an entry into the ingress port filter table
+ * @user: target ntmp_user struct
+ * @entry: the entry data, entry->cfge (configuration element data) and
+ * entry->keye (key element data) are used as input. Since the entry ID
+ * is assigned by the hardware, so entry->entry_id is a returned value
+ * for the driver to use, the driver can update/delete/query the entry
+ * based on the entry_id.
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ipft_add_entry(struct ntmp_user *user,
+ struct ipft_entry_data *entry)
+{
+ struct ipft_resp_query *resp;
+ struct ipft_req_ua *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*resp);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Note that NTMP_GEN_UA_STSEU is used to reset the statistics of
+ * the entry. The STSE_DATA is not present in the request data for
+ * 'Add' operation.
+ */
+ ntmp_fill_crd(&req->crd, user->tbl.ipft_ver, NTMP_QA_ENTRY_ID,
+ NTMP_GEN_UA_CFGEU | NTMP_GEN_UA_STSEU);
+ req->ak.keye = entry->keye;
+ req->cfge = entry->cfge;
+
+ len = NTMP_LEN(sizeof(*req), swcbd.size);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_IPFT_ID,
+ NTMP_CMD_AQ, NTMP_AM_TERNARY_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err) {
+ dev_err(user->dev, "Failed to add %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_IPFT_ID), ERR_PTR(err));
+
+ goto unlock_cbdr;
+ }
+
+ resp = (struct ipft_resp_query *)req;
+ entry->entry_id = le32_to_cpu(resp->entry_id);
+
+unlock_cbdr:
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ipft_add_entry);
+
+/**
+ * ntmp_ipft_delete_entry - delete a specified ingress port filter table entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified ID of the ingress port filter table entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ u32 req_len = sizeof(struct ipft_req_qd);
+
+ return ntmp_delete_entry_by_id(user, NTMP_IPFT_ID,
+ user->tbl.ipft_ver,
+ entry_id, req_len,
+ NTMP_STATUS_RESP_LEN);
+}
+EXPORT_SYMBOL_GPL(ntmp_ipft_delete_entry);
+
/**
* ntmp_fdbt_add_entry - add an entry into the FDB table
* @user: target ntmp_user struct
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 64df49e9a3ef..0a9b87286105 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -99,6 +99,42 @@ struct rsst_req_update {
u8 groups[];
};
+/* Ingress Port Filter Table Response Data Buffer Format of Query action */
+struct ipft_resp_query {
+ __le32 status;
+ __le32 entry_id;
+ struct ipft_keye_data keye;
+ __le64 match_count; /* STSE_DATA */
+ struct ipft_cfge_data cfge;
+} __packed;
+
+struct ipft_ak_eid {
+ __le32 entry_id;
+ __le32 resv[52];
+};
+
+union ipft_access_key {
+ struct ipft_ak_eid eid;
+ struct ipft_keye_data keye;
+};
+
+/* Ingress Port Filter Table Request Data Buffer Format of Update and
+ * Add actions
+ */
+struct ipft_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union ipft_access_key ak;
+ struct ipft_cfge_data cfge;
+};
+
+/* Ingress Port Filter Table Request Data Buffer Format of Query and
+ * Delete actions
+ */
+struct ipft_req_qd {
+ struct ntmp_req_by_eid rbe;
+ __le32 resv[52];
+};
+
/* Access Key Format of FDB Table */
struct fdbt_ak_eid {
__le32 entry_id;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index d74714a402f6..f68551045b60 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -7,6 +7,7 @@
#include <linux/if_ether.h>
#define NTMP_NULL_ENTRY_ID 0xffffffffU
+#define IPFT_MAX_PLD_LEN 24
struct maft_keye_data {
u8 mac_addr[ETH_ALEN];
@@ -34,6 +35,7 @@ struct netc_tbl_vers {
u8 fdbt_ver;
u8 vft_ver;
u8 bpt_ver;
+ u8 ipft_ver;
};
struct netc_swcbd {
@@ -73,6 +75,94 @@ struct maft_entry_data {
struct maft_cfge_data cfge;
};
+struct ipft_pld_byte {
+ u8 data;
+ u8 mask;
+};
+
+struct ipft_keye_data {
+ __le16 precedence;
+ __le16 resv0[3];
+ __le16 frm_attr_flags;
+#define IPFT_FAF_OVLAN BIT(2)
+#define IPFT_FAF_IVLAN BIT(3)
+#define IPFT_FAF_IP_HDR BIT(7)
+#define IPFT_FAF_IP_VER6 BIT(8)
+#define IPFT_FAF_L4_CODE GENMASK(11, 10)
+#define IPFT_FAF_TCP_HDR 1
+#define IPFT_FAF_UDP_HDR 2
+#define IPFT_FAF_SCTP_HDR 3
+#define IPFT_FAF_WOL_MAGIC BIT(12)
+ __le16 frm_attr_flags_mask;
+ __le16 dscp;
+#define IPFT_DSCP GENMASK(5, 0)
+#define IPFT_DSCP_MASK GENMASK(11, 6)
+#define IPFT_DSCP_MASK_ALL 0x3f
+ __le16 src_port; /* This field is reserved for ENETC */
+#define IPFT_SRC_PORT GENMASK(4, 0)
+#define IPFT_SRC_PORT_MASK GENMASK(9, 5)
+#define IPFT_SRC_PORT_MASK_ALL 0x1f
+ __be16 outer_vlan_tci;
+ __be16 outer_vlan_tci_mask;
+ u8 dmac[ETH_ALEN];
+ u8 dmac_mask[ETH_ALEN];
+ u8 smac[ETH_ALEN];
+ u8 smac_mask[ETH_ALEN];
+ __be16 inner_vlan_tci;
+ __be16 inner_vlan_tci_mask;
+ __be16 ethertype;
+ __be16 ethertype_mask;
+ u8 ip_protocol;
+ u8 ip_protocol_mask;
+ __le16 resv1[7];
+ __be32 ip_src[4];
+ __le32 resv2[2];
+ __be32 ip_src_mask[4];
+ __be16 l4_src_port;
+ __be16 l4_src_port_mask;
+ __le32 resv3;
+ __be32 ip_dst[4];
+ __le32 resv4[2];
+ __be32 ip_dst_mask[4];
+ __be16 l4_dst_port;
+ __be16 l4_dst_port_mask;
+ __le32 resv5;
+ struct ipft_pld_byte byte[IPFT_MAX_PLD_LEN];
+};
+
+struct ipft_cfge_data {
+ __le32 cfg;
+#define IPFT_IPV GENMASK(3, 0)
+#define IPFT_OIPV BIT(4)
+#define IPFT_DR GENMASK(6, 5)
+#define IPFT_ODR BIT(7)
+#define IPFT_FLTFA GENMASK(10, 8)
+#define IPFT_FLTFA_DISCARD 0
+#define IPFT_FLTFA_PERMIT 1
+/* Redirect is only for switch */
+#define IPFT_FLTFA_REDIRECT 2
+#define IPFT_IMIRE BIT(11)
+#define IPFT_WOLTE BIT(12)
+#define IPFT_FLTA GENMASK(14, 13)
+#define IPFT_FLTA_RP 1
+#define IPFT_FLTA_IS 2
+#define IPFT_FLTA_SI_BITMAP 3
+#define IPFT_RPR GENMASK(16, 15)
+#define IPFT_CTD BIT(17)
+#define IPFT_HR GENMASK(21, 18)
+#define IPFT_TIMECAPE BIT(22)
+#define IPFT_RRT BIT(23)
+#define IPFT_BL2F BIT(24)
+#define IPFT_EVMEID GENMASK(31, 28)
+ __le32 flta_tgt;
+};
+
+struct ipft_entry_data {
+ u32 entry_id; /* hardware assigns entry ID */
+ struct ipft_keye_data keye;
+ struct ipft_cfge_data cfge;
+};
+
struct fdbt_keye_data {
u8 mac_addr[ETH_ALEN]; /* big-endian */
__le16 resv0;
@@ -162,6 +252,9 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table,
int count);
int ntmp_rsst_query_entry(struct ntmp_user *user,
u32 *table, int count);
+int ntmp_ipft_add_entry(struct ntmp_user *user,
+ struct ipft_entry_data *entry);
+int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
const struct fdbt_keye_data *keye,
const struct fdbt_cfge_data *cfge);
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 06/15] net: enetc: add support for the "Update" operation to buffer pool table
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
The buffer pool table contains buffer pool configuration and operational
information. Each entry corresponds to a buffer pool. The Entry ID value
represents the buffer pool ID to access.
The buffer pool table is a static bounded index table, buffer pools are
always present and enabled. It only supports Update and Query operations,
This patch only adds ntmp_bpt_update_entry() helper to support updating
the specified entry of the buffer pool table. Query action to the table
will be added in the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 43 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 6 +++
include/linux/fsl/ntmp.h | 26 +++++++++++
3 files changed, 75 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index db74a9107975..ad89be85b185 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -23,11 +23,15 @@
#define NTMP_RSST_ID 3
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
+#define NTMP_BPT_ID 41
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
#define NTMP_GEN_UA_STSEU BIT(1)
+/* Specific Update Actions for some tables */
+#define BPT_UA_BPSEU BIT(1)
+
/* Query Action: 0: Full query. 1: Query entry ID, the fields after entry
* ID are not returned.
*/
@@ -271,6 +275,8 @@ static const char *ntmp_table_name(int tbl_id)
return "FDB Table";
case NTMP_VFT_ID:
return "VLAN Filter Table";
+ case NTMP_BPT_ID:
+ return "Buffer Pool Table";
default:
return "Unknown Table";
}
@@ -749,5 +755,42 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
}
EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct bpt_cfge_data *cfge)
+{
+ struct bpt_req_update *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;
+
+ /* Note that BPT_UA_BPSEU is used to update the BPSE_DATA of the entry,
+ * which is maintained by the hardware. The BPSE_DATA is not present in
+ * the request data for 'Update' operation.
+ */
+ ntmp_fill_crd_eid(&req->rbe, user->tbl.bpt_ver, 0,
+ NTMP_GEN_UA_CFGEU | BPT_UA_BPSEU, entry_id);
+ req->cfge = *cfge;
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0),
+ NTMP_BPT_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_BPT_ID), entry_id, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_bpt_update_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 575ee783be47..64df49e9a3ef 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -175,4 +175,10 @@ struct vft_req_ua {
struct vft_cfge_data cfge;
};
+/* Buffer Pool Table Request Data Buffer Format of Update action */
+struct bpt_req_update {
+ struct ntmp_req_by_eid rbe;
+ struct bpt_cfge_data cfge;
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 3672e0dc7726..d74714a402f6 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -33,6 +33,7 @@ struct netc_tbl_vers {
u8 rsst_ver;
u8 fdbt_ver;
u8 vft_ver;
+ u8 bpt_ver;
};
struct netc_swcbd {
@@ -123,6 +124,29 @@ struct vft_cfge_data {
__le32 et_eid;
};
+struct bpt_bpse_data {
+ __le32 amount_used;
+ __le32 amount_used_hwm;
+ u8 bpd_fc_state;
+#define BPT_FC_STATE BIT(0)
+#define BPT_BPD BIT(1)
+} __packed;
+
+struct bpt_cfge_data {
+ u8 fccfg_sbpen;
+#define BPT_SBP_EN BIT(0)
+#define BPT_FC_CFG GENMASK(2, 1)
+#define BPT_FC_CFG_EN_BPFC 1
+ u8 pfc_vector;
+ __le16 max_thresh;
+ __le16 fc_on_thresh;
+ __le16 fc_off_thresh;
+ __le16 sbp_thresh;
+ __le16 resv;
+ __le32 sbp_eid;
+ __le32 fc_ports;
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -149,6 +173,8 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
struct fdbt_entry_data *entry);
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,
+ const struct bpt_cfge_data *cfge);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 05/15] net: enetc: add support for the "Add" operation to VLAN filter table
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
The VLAN filter table contains configuration and control information for
each VLAN configured on the switch. Each VLAN entry includes the VLAN
port membership, which FID to use in the FDB lookup, which spanning tree
group to use, the egress frame modification actions to apply to a frame
exiting form this VLAN, and various configuration and control parameters
for this VLAN.
The VLAN filter table can only be managed by the command BD ring using
table management protocol version 2.0. The table supports Add, Delete,
Update and Query operations. And the table supports 3 access methods:
Entry ID, Exact Match Key Element and Search. But currently we only add
the ntmp_vft_add_entry() helper to support the upcoming switch driver to
add an entry to the VLAN filter table. Other interfaces will be added in
the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 50 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 19 +++++++
include/linux/fsl/ntmp.h | 24 +++++++++
3 files changed, 93 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 6074eeafd5a2..db74a9107975 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -22,6 +22,7 @@
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
#define NTMP_FDBT_ID 15
+#define NTMP_VFT_ID 18
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
@@ -268,6 +269,8 @@ static const char *ntmp_table_name(int tbl_id)
return "RSS Table";
case NTMP_FDBT_ID:
return "FDB Table";
+ case NTMP_VFT_ID:
+ return "VLAN Filter Table";
default:
return "Unknown Table";
}
@@ -699,5 +702,52 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
}
EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+/**
+ * ntmp_vft_add_entry - add an entry into the VLAN filter table
+ * @user: target ntmp_user struct
+ * @vid: VLAN ID
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_vft_add_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_ADD, 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 add %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_add_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index b0b5805ac4f6..575ee783be47 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -156,4 +156,23 @@ struct fdbt_resp_query {
u8 resv[3];
};
+/* Access Key Format of VLAN Filter Table */
+struct vft_ak_exact {
+ __le16 vid; /* bit0~11: VLAN ID, other bits are reserved */
+ __le16 resv;
+};
+
+union vft_access_key {
+ __le32 entry_id; /* entry_id match */
+ struct vft_ak_exact exact;
+ __le32 resume_entry_id; /* search */
+};
+
+/* VLAN Filter Table Request Data Buffer Format of Update and Add actions */
+struct vft_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union vft_access_key ak;
+ struct vft_cfge_data cfge;
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 4cfff835954e..3672e0dc7726 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -32,6 +32,7 @@ struct netc_tbl_vers {
u8 maft_ver;
u8 rsst_ver;
u8 fdbt_ver;
+ u8 vft_ver;
};
struct netc_swcbd {
@@ -101,6 +102,27 @@ struct fdbt_entry_data {
#define FDBT_ACT_FLAG BIT(7)
};
+struct vft_cfge_data {
+ __le32 bitmap_stg;
+#define VFT_PORT_MEMBERSHIP GENMASK(23, 0)
+#define VFT_STG_ID_MASK GENMASK(27, 24)
+#define VFT_STG_ID(g) FIELD_PREP(VFT_STG_ID_MASK, (g))
+ __le16 fid;
+#define VFT_FID GENMASK(11, 0)
+ __le16 cfg;
+#define VFT_MLO GENMASK(2, 0)
+#define VFT_MFO GENMASK(4, 3)
+#define VFT_IPMFE BIT(6)
+#define VFT_IPMFLE BIT(7)
+#define VFT_PGA BIT(8)
+#define VFT_SFDA BIT(10)
+#define VFT_OSFDA BIT(11)
+#define VFT_FDBAFSS BIT(12)
+ __le32 eta_port_bitmap;
+#define VFT_ETA_PORT_BITMAP GENMASK(23, 0)
+ __le32 et_eid;
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -125,6 +147,8 @@ int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
u32 *resume_entry_id,
struct fdbt_entry_data *entry);
+int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 04/15] net: enetc: add basic operations to the FDB table
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
The FDB table is used for MAC learning lookups and MAC forwarding lookups.
Each table entry includes information such as a FID and MAC address that
may be unicast or multicast and a forwarding destination field containing
a port bitmap identifying the associated port(s) with the MAC address.
FDB table entries can be static or dynamic. Static entries are added from
software whereby dynamic entries are added either by software or by the
hardware as MAC addresses are learned in the datapath.
The FDB table can only be managed by the command BD ring using table
management protocol version 2.0. Table management command operations Add,
Delete, Update and Query are supported. And the FDB table supports three
access methods: Entry ID, Exact Match Key Element and Search. This patch
adds the following basic supports to the FDB table.
ntmp_fdbt_update_entry() - update the configuration element data of a
specified FDB entry
ntmp_fdbt_delete_entry() - delete a specified FDB entry
ntmp_fdbt_add_entry() - add an entry into the FDB table
ntmp_fdbt_search_port_entry() - Search the FDB entry on the specified
port based on RESUME_ENTRY_ID.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 205 +++++++++++++++++-
.../ethernet/freescale/enetc/ntmp_private.h | 61 +++++-
include/linux/fsl/ntmp.h | 44 +++-
3 files changed, 307 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index c94a928622fd..6074eeafd5a2 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* NETC NTMP (NETC Table Management Protocol) 2.0 Library
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#include <linux/dma-mapping.h>
@@ -21,11 +21,17 @@
/* Define NTMP Table ID */
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
+#define NTMP_FDBT_ID 15
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
#define NTMP_GEN_UA_STSEU BIT(1)
+/* Query Action: 0: Full query. 1: Query entry ID, the fields after entry
+ * ID are not returned.
+ */
+#define NTMP_QA_ENTRY_ID 1
+
#define NTMP_ENTRY_ID_SIZE 4
#define RSST_ENTRY_NUM 64
#define RSST_STSE_DATA_SIZE(n) ((n) * 8)
@@ -260,6 +266,8 @@ static const char *ntmp_table_name(int tbl_id)
return "MAC Address Filter Table";
case NTMP_RSST_ID:
return "RSS Table";
+ case NTMP_FDBT_ID:
+ return "FDB Table";
default:
return "Unknown Table";
}
@@ -496,5 +504,200 @@ int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count)
}
EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry);
+/**
+ * ntmp_fdbt_add_entry - add an entry into the FDB table
+ * @user: target ntmp_user struct
+ * @entry_id: returned value, the entry ID of the new added entry
+ * @keye: key element data
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
+ const struct fdbt_keye_data *keye,
+ const struct fdbt_cfge_data *cfge)
+{
+ struct fdbt_resp_query *resp;
+ 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, NTMP_QA_ENTRY_ID,
+ NTMP_GEN_UA_CFGEU);
+ req->ak.exact.keye = *keye;
+ req->cfge = *cfge;
+
+ len = NTMP_LEN(swcbd.size, sizeof(*resp));
+ /* The entry ID is allotted by hardware, so we need to perform
+ * a query action after the add action to get the entry ID from
+ * hardware.
+ */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_AQ, 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 add %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+ goto unlock_cbdr;
+ }
+
+ if (entry_id) {
+ resp = (struct fdbt_resp_query *)req;
+ *entry_id = le32_to_cpu(resp->entry_id);
+ }
+
+unlock_cbdr:
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_add_entry);
+
+/**
+ * ntmp_fdbt_update_entry - update the configuration element data of the
+ * specified FDB entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified entry ID of the FDB table
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct fdbt_cfge_data *cfge)
+{
+ 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, NTMP_GEN_UA_CFGEU);
+ req->ak.eid.entry_id = cpu_to_le32(entry_id);
+ req->cfge = *cfge;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_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, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_update_entry);
+
+/**
+ * ntmp_fdbt_delete_entry - delete the specified FDB entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified ID of the FDB entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ u32 req_len = sizeof(struct fdbt_req_qd);
+
+ return ntmp_delete_entry_by_id(user, NTMP_FDBT_ID,
+ user->tbl.fdbt_ver,
+ entry_id, req_len,
+ NTMP_STATUS_RESP_LEN);
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_entry);
+
+/**
+ * ntmp_fdbt_search_port_entry - Search the FDB entry on the specified
+ * port based on RESUME_ENTRY_ID
+ * @user: target ntmp_user struct
+ * @port: the specified switch port ID
+ * @resume_entry_id: it is both an input and an output. As an input, it
+ * represents the FDB entry ID to be searched. If it is a NULL entry ID,
+ * it indicates that the first FDB entry for that port is being searched.
+ * As an output, it represents the next FDB entry ID to be searched.
+ * @entry: returned value, the response data of the searched FDB entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
+ u32 *resume_entry_id,
+ struct fdbt_entry_data *entry)
+{
+ struct fdbt_resp_query *resp;
+ 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.search.resume_eid = cpu_to_le32(*resume_entry_id);
+ req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port));
+ /* Match CFGE_DATA[PORT_BITMAP] field */
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_PORT_BITMAP;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, sizeof(*resp));
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_QUERY, 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 search %s entry on port %d, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err));
+ goto unlock_cbdr;
+ }
+
+ if (!cbd.resp_hdr.num_matched) {
+ entry->entry_id = NTMP_NULL_ENTRY_ID;
+ *resume_entry_id = NTMP_NULL_ENTRY_ID;
+ goto unlock_cbdr;
+ }
+
+ resp = (struct fdbt_resp_query *)req;
+ *resume_entry_id = le32_to_cpu(resp->status);
+ entry->entry_id = le32_to_cpu(resp->entry_id);
+ entry->keye = resp->keye;
+ entry->cfge = resp->cfge;
+ entry->acte = resp->acte;
+
+unlock_cbdr:
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index f8dff3ba2c28..b0b5805ac4f6 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
/*
* NTMP table request and response data buffer formats
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#ifndef __NTMP_PRIVATE_H
@@ -11,6 +11,7 @@
#include <linux/fsl/ntmp.h>
#define NTMP_EID_REQ_LEN 8
+#define NTMP_STATUS_RESP_LEN 4
#define NETC_CBDR_BD_NUM 256
#define NETC_CBDRCIR_INDEX GENMASK(9, 0)
#define NETC_CBDRCIR_SBE BIT(31)
@@ -30,6 +31,7 @@ union netc_cbd {
#define NTMP_CMD_QUERY BIT(2)
#define NTMP_CMD_ADD BIT(3)
#define NTMP_CMD_QU (NTMP_CMD_QUERY | NTMP_CMD_UPDATE)
+#define NTMP_CMD_AQ (NTMP_CMD_ADD | NTMP_CMD_QUERY)
u8 access_method;
#define NTMP_ACCESS_METHOD GENMASK(7, 4)
#define NTMP_AM_ENTRY_ID 0
@@ -97,4 +99,61 @@ struct rsst_req_update {
u8 groups[];
};
+/* Access Key Format of FDB Table */
+struct fdbt_ak_eid {
+ __le32 entry_id;
+ __le32 resv[7];
+};
+
+struct fdbt_ak_exact {
+ struct fdbt_keye_data keye;
+ __le32 resv[5];
+};
+
+struct fdbt_ak_search {
+ __le32 resume_eid;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+ u8 keye_mc;
+#define FDBT_KEYE_MAC GENMASK(1, 0)
+ u8 cfge_mc;
+#define FDBT_CFGE_MC GENMASK(2, 0)
+#define FDBT_CFGE_MC_ANY 0
+#define FDBT_CFGE_MC_DYNAMIC 1
+#define FDBT_CFGE_MC_PORT_BITMAP 2
+#define FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP 3
+ u8 acte_mc;
+#define FDBT_ACTE_MC BIT(0)
+};
+
+union fdbt_access_key {
+ struct fdbt_ak_eid eid;
+ struct fdbt_ak_exact exact;
+ struct fdbt_ak_search search;
+};
+
+/* FDB Table Request Data Buffer Format of Update and Add actions */
+struct fdbt_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union fdbt_access_key ak;
+ struct fdbt_cfge_data cfge;
+};
+
+/* FDB Table Request Data Buffer Format of Query and Delete actions */
+struct fdbt_req_qd {
+ struct ntmp_cmn_req_data crd;
+ union fdbt_access_key ak;
+};
+
+/* FDB Table Response Data Buffer Format of Query action */
+struct fdbt_resp_query {
+ __le32 status;
+ __le32 entry_id;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+ u8 resv[3];
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 83a449b4d6ec..4cfff835954e 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -1,11 +1,13 @@
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
-/* Copyright 2025 NXP */
+/* Copyright 2025-2026 NXP */
#ifndef __NETC_NTMP_H
#define __NETC_NTMP_H
#include <linux/bitops.h>
#include <linux/if_ether.h>
+#define NTMP_NULL_ENTRY_ID 0xffffffffU
+
struct maft_keye_data {
u8 mac_addr[ETH_ALEN];
__le16 resv;
@@ -29,6 +31,7 @@ struct netc_cbdr_regs {
struct netc_tbl_vers {
u8 maft_ver;
u8 rsst_ver;
+ u8 fdbt_ver;
};
struct netc_swcbd {
@@ -68,6 +71,36 @@ struct maft_entry_data {
struct maft_cfge_data cfge;
};
+struct fdbt_keye_data {
+ u8 mac_addr[ETH_ALEN]; /* big-endian */
+ __le16 resv0;
+ __le16 fid;
+#define FDBT_FID GENMASK(11, 0)
+ __le16 resv1;
+};
+
+struct fdbt_cfge_data {
+ __le32 port_bitmap;
+#define FDBT_PORT_BITMAP GENMASK(23, 0)
+ __le32 cfg;
+#define FDBT_OETEID GENMASK(1, 0)
+#define FDBT_EPORT GENMASK(6, 2)
+#define FDBT_IMIRE BIT(7)
+#define FDBT_CTD GENMASK(10, 9)
+#define FDBT_DYNAMIC BIT(11)
+#define FDBT_TIMECAPE BIT(12)
+ __le32 et_eid;
+};
+
+struct fdbt_entry_data {
+ u32 entry_id;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+#define FDBT_ACT_CNT GENMASK(6, 0)
+#define FDBT_ACT_FLAG BIT(7)
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -83,6 +116,15 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table,
int count);
int ntmp_rsst_query_entry(struct ntmp_user *user,
u32 *table, int count);
+int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
+ const struct fdbt_keye_data *keye,
+ const struct fdbt_cfge_data *cfge);
+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_search_port_entry(struct ntmp_user *user, int port,
+ u32 *resume_entry_id,
+ struct fdbt_entry_data *entry);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 03/15] net: enetc: add pre-boot initialization for i.MX94 switch
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
Before probing the NETC switch driver, some pre-initialization needs to
be set in NETCMIX and IERB to ensure that the switch can work properly.
For example, i.MX94 NETC switch has three external ports and each port
is bound to a link. And each link needs to be configured so that it can
work properly, such as I/O variant and MII protocol.
In addition, the switch port 2 (MAC 2) and ENETC 0 (MAC 3) share the same
parallel interface, they cannot be used at the same time due to the SoC
constraint. And the MAC selection is controlled by the mac2_mac3_sel bit
of EXT_PIN_CONTROL register. Currently, the interface is set for ENETC 0
by default unless the switch port 2 is enabled in the DT node.
Like ENETC, each external port of the NETC switch can manage its external
PHY through its port MDIO registers. And the port can only access its own
external PHY by setting the PHY address to the LaBCR[MDIO_PHYAD_PRTAD].
If the accessed PHY address is not equal to LaBCR[MDIO_PHYAD_PRTAD], then
the MDIO access initiated by port MDIO will be invalid.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
.../ethernet/freescale/enetc/netc_blk_ctrl.c | 185 +++++++++++++++---
1 file changed, 163 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
index 92a0f824dae7..c7eb0234c785 100644
--- a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
+++ b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
@@ -261,40 +261,108 @@ static int imx94_link_config(struct netc_blk_ctrl *priv,
}
static int imx94_enetc_link_config(struct netc_blk_ctrl *priv,
- struct device_node *np)
+ struct device_node *np,
+ bool *enetc0_en)
{
int link_id = imx94_enetc_get_link_id(np);
if (link_id < 0)
return link_id;
+ if (link_id == IMX94_ENETC0_LINK && of_device_is_available(np))
+ *enetc0_en = true;
+
return imx94_link_config(priv, np, link_id);
}
+static int imx94_switch_link_config(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ bool *swp2_en)
+{
+ struct device_node *ports;
+ u32 port_id;
+ int err = 0;
+
+ ports = of_get_child_by_name(np, "ethernet-ports");
+ if (!ports)
+ return -ENODEV;
+
+ /* The switch may be owned by a guest OS, in this case, the switch
+ * node in the host OS will be disabled, but we still hope that the
+ * host OS could do some configurations for the switch, as the
+ * netc_blk_ctrl is owned by host OS. So of_device_is_available()
+ * is not needed here.
+ */
+ for_each_available_child_of_node_scoped(ports, child) {
+ if (of_property_read_u32(child, "reg", &port_id) < 0) {
+ err = -ENODEV;
+ goto end;
+ }
+
+ switch (port_id) {
+ case 0 ... 2: /* External ports */
+ err = imx94_link_config(priv, child, port_id);
+ if (err)
+ goto end;
+
+ if (port_id == 2)
+ *swp2_en = true;
+
+ break;
+ case 3: /* CPU port */
+ break;
+ default:
+ err = -EINVAL;
+ goto end;
+ }
+ }
+
+end:
+ of_node_put(ports);
+
+ return err;
+}
+
static int imx94_netcmix_init(struct platform_device *pdev)
{
struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
struct device_node *np = pdev->dev.of_node;
+ bool enetc0_en = false, swp2_en = false;
u32 val;
int err;
for_each_child_of_node_scoped(np, child) {
for_each_child_of_node_scoped(child, gchild) {
- if (!of_device_is_compatible(gchild, "pci1131,e101"))
- continue;
-
- err = imx94_enetc_link_config(priv, gchild);
- if (err)
- return err;
+ if (of_device_is_compatible(gchild, "pci1131,e101")) {
+ err = imx94_enetc_link_config(priv, gchild,
+ &enetc0_en);
+ if (err)
+ return err;
+ } else if (of_device_is_compatible(gchild,
+ "pci1131,eef2")) {
+ err = imx94_switch_link_config(priv, gchild,
+ &swp2_en);
+ if (err)
+ return err;
+ }
}
}
- /* ENETC 0 and switch port 2 share the same parallel interface.
- * Currently, the switch is not supported, so this interface is
- * used by ENETC 0 by default.
+ if (enetc0_en && swp2_en) {
+ dev_err(&pdev->dev,
+ "Cannot enable swp2 and enetc0 at the same time\n");
+ return -EINVAL;
+ }
+
+ /* ENETC 0 and switch port 2 share the same parallel interface, they
+ * cannot be enabled at the same time. The interface is set for the
+ * ENETC 0 by default unless the switch port 2 is enabled in the DTS.
*/
val = netc_reg_read(priv->netcmix, IMX94_EXT_PIN_CONTROL);
- val |= MAC2_MAC3_SEL;
+ if (!swp2_en)
+ val |= MAC2_MAC3_SEL;
+ else
+ val &= ~MAC2_MAC3_SEL;
netc_reg_write(priv->netcmix, IMX94_EXT_PIN_CONTROL, val);
return 0;
@@ -610,6 +678,78 @@ static int imx94_enetc_mdio_phyaddr_config(struct netc_blk_ctrl *priv,
return 0;
}
+static int imx94_ierb_enetc_init(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 phy_mask)
+{
+ int err;
+
+ err = imx94_enetc_update_tid(priv, np);
+ if (err)
+ return err;
+
+ return imx94_enetc_mdio_phyaddr_config(priv, np, phy_mask);
+}
+
+static int imx94_switch_mdio_phyaddr_config(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 port_id, u32 phy_mask)
+{
+ int addr;
+
+ /* The switch has 3 external ports at most */
+ if (port_id > 2)
+ return 0;
+
+ addr = netc_get_phy_addr(np);
+ if (addr < 0) {
+ if (addr == -ENODEV)
+ return 0;
+
+ return addr;
+ }
+
+ if (phy_mask & BIT(addr)) {
+ dev_err(&priv->pdev->dev,
+ "Found same PHY address in EMDIO and switch node\n");
+ return -EINVAL;
+ }
+
+ netc_reg_write(priv->ierb, IERB_LBCR(port_id),
+ LBCR_MDIO_PHYAD_PRTAD(addr));
+
+ return 0;
+}
+
+static int imx94_ierb_switch_init(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 phy_mask)
+{
+ struct device_node *ports;
+ u32 port_id;
+ int err = 0;
+
+ ports = of_get_child_by_name(np, "ethernet-ports");
+ if (!ports)
+ return -ENODEV;
+
+ for_each_available_child_of_node_scoped(ports, child) {
+ err = of_property_read_u32(child, "reg", &port_id);
+ if (err)
+ goto end;
+
+ err = imx94_switch_mdio_phyaddr_config(priv, child,
+ port_id, phy_mask);
+ if (err)
+ goto end;
+ }
+
+end:
+ of_node_put(ports);
+
+ return err;
+}
+
static int imx94_ierb_init(struct platform_device *pdev)
{
struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
@@ -625,17 +765,18 @@ static int imx94_ierb_init(struct platform_device *pdev)
for_each_child_of_node_scoped(np, child) {
for_each_child_of_node_scoped(child, gchild) {
- if (!of_device_is_compatible(gchild, "pci1131,e101"))
- continue;
-
- err = imx94_enetc_update_tid(priv, gchild);
- if (err)
- return err;
-
- err = imx94_enetc_mdio_phyaddr_config(priv, gchild,
- phy_mask);
- if (err)
- return err;
+ if (of_device_is_compatible(gchild, "pci1131,e101")) {
+ err = imx94_ierb_enetc_init(priv, gchild,
+ phy_mask);
+ if (err)
+ return err;
+ } else if (of_device_is_compatible(gchild,
+ "pci1131,eef2")) {
+ err = imx94_ierb_switch_init(priv, gchild,
+ phy_mask);
+ if (err)
+ return err;
+ }
}
}
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 02/15] dt-bindings: net: dsa: add NETC switch
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
Add bindings for NETC switch. This switch is a PCIe function of NETC IP,
it supports advanced QoS with 8 traffic classes and 4 drop resilience
levels, and a full range of TSN standards capabilities. The switch CPU
port connects to an internal ENETC port, which is also a PCIe function
of NETC IP. So these two ports use a light-weight "pseudo MAC" instead
of a back-to-back MAC, because the "pseudo MAC" provides the delineation
between switch and ENETC, this translates to lower power (less logic and
memory) and lower delay (as there is no serialization delay across this
link).
Signed-off-by: Wei Fang <wei.fang@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../bindings/net/dsa/nxp,netc-switch.yaml | 131 ++++++++++++++++++
1 file changed, 131 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
diff --git a/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
new file mode 100644
index 000000000000..1b35e4cbd049
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/nxp,netc-switch.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NETC Switch family
+
+description: >
+ The NETC presents itself as a multi-function PCIe Root Complex Integrated
+ Endpoint (RCiEP) and provides full 802.1Q Ethernet switch functionality,
+ advanced QoS with 8 traffic classes and 4 drop resilience levels, and a
+ full range of TSN standards capabilities.
+
+ The CPU port of the switch connects to an internal ENETC. The switch and
+ the internal ENETC are fully integrated into the NETC IP, a back-to-back
+ MAC is not required. Instead, a light-weight "pseudo MAC" provides the
+ delineation between the switch and ENETC. This translates to lower power
+ (less logic and memory) and lower delay (as there is no serialization
+ delay across this link).
+
+maintainers:
+ - Wei Fang <wei.fang@nxp.com>
+
+properties:
+ compatible:
+ enum:
+ - pci1131,eef2
+
+ reg:
+ maxItems: 1
+
+ dsa,member:
+ description: >
+ The property indicates DSA cluster and switch index. For NETC switch,
+ the valid range of the switch index is 1 ~ 7, the index is reflected
+ in the switch tag as an indication of the switch ID where the frame
+ originated. The value 0 is reserved for ENETC VEPA switch, whose ID
+ is hardwired to zero.
+ items:
+ - true
+ - minimum: 1
+ maximum: 7
+
+ ethernet-ports:
+ type: object
+ patternProperties:
+ "^ethernet-port@[0-9a-f]$":
+ type: object
+ $ref: dsa-port.yaml#
+
+ properties:
+ clocks:
+ items:
+ - description: MAC transmit/receive reference clock.
+
+ clock-names:
+ items:
+ - const: ref
+
+ mdio:
+ $ref: /schemas/net/mdio.yaml#
+ unevaluatedProperties: false
+ description:
+ Optional child node for switch port, otherwise use NETC EMDIO.
+
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - dsa,member
+ - ethernet-ports
+
+allOf:
+ - $ref: /schemas/pci/pci-device.yaml
+ - $ref: dsa.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ pcie {
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ ethernet-switch@0,2 {
+ compatible = "pci1131,eef2";
+ reg = <0x200 0 0 0 0>;
+ dsa,member = <0 1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_switch>;
+
+ ethernet-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-port@0 {
+ reg = <0>;
+ phy-handle = <ðphy0>;
+ phy-mode = "mii";
+ };
+
+ ethernet-port@1 {
+ reg = <1>;
+ phy-handle = <ðphy1>;
+ phy-mode = "mii";
+ };
+
+ ethernet-port@2 {
+ reg = <2>;
+ clocks = <&scmi_clk 103>;
+ clock-names = "ref";
+ phy-handle = <ðphy2>;
+ phy-mode = "rgmii-id";
+ };
+
+ ethernet-port@3 {
+ reg = <3>;
+ ethernet = <&enetc3>;
+ phy-mode = "internal";
+
+ fixed-link {
+ speed = <2500>;
+ full-duplex;
+ pause;
+ };
+ };
+ };
+ };
+ };
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 01/15] dt-bindings: net: dsa: update the description of 'dsa,member' property
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
The current description indicates that the 'dsa,member' property cannot
be set for a switch that is not part of any cluster. Vladimir thinks
that this is a case where the actual technical limitation was poorly
transposed into words when this restriction was first documented, in
commit 8c5ad1d6179d ("net: dsa: Document new binding").
The true technical limitation is that many DSA tagging protocols are
topology-unaware, and always call dsa_conduit_find_user() with a
switch_id of 0. Specifying a custom "dsa,member" property with a
non-zero switch_id would break them.
Therefore, for topology-aware switches, it is fine to specify this
property for them, even if they are not part of any cluster. Our NETC
switch is a good example which is topology-aware, the switch_id is
carried in the switch tag, but the switch_id 0 is reserved for VEPA
switch and cannot be used, so we need to use this property to assign
a non-zero switch_id for it.
Suggested-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: Wei Fang <wei.fang@nxp.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
Documentation/devicetree/bindings/net/dsa/dsa.yaml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/dsa/dsa.yaml b/Documentation/devicetree/bindings/net/dsa/dsa.yaml
index 2abd036578d1..801e1411e5c2 100644
--- a/Documentation/devicetree/bindings/net/dsa/dsa.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/dsa.yaml
@@ -28,7 +28,11 @@ properties:
A two element list indicates which DSA cluster, and position within the
cluster a switch takes. <0 0> is cluster 0, switch 0. <0 1> is cluster 0,
switch 1. <1 0> is cluster 1, switch 0. A switch not part of any cluster
- (single device hanging off a CPU port) must not specify this property
+ (single device hanging off a CPU port) does not usually need to specify
+ this property, and then it becomes cluster 0, switch 0. For a topology
+ aware switch, its switch index can be specified through this property,
+ even if it is not part of any cluster. Also, topology-unaware switches
+ must always be defined as index 0 of their cluster.
$ref: /schemas/types.yaml#/definitions/uint32-array
additionalProperties: true
--
2.34.1
^ permalink raw reply related
* [PATCH v7 net-next 00/15] Add preliminary NETC switch support for i.MX94
From: Wei Fang @ 2026-05-13 3:04 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, maxime.chevallier,
andrew, olteanv
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
i.MX94 NETC (v4.3) integrates 802.1Q Ethernet switch functionality, the
switch provides advanced QoS with 8 traffic classes and a full range of
TSN standards capabilities. It has 3 user ports and 1 CPU port, and the
CPU port is connected to an internal ENETC through the pseduo link, so
instead of a back-to-back MAC, the lightweight "pseudo MAC" is used at
both ends of the pseudo link to transfer Ethernet frames. The pseudo
link provides a zero-copy interface (no serialization delay) and lower
power (less logic and memory).
Like most Ethernet switches, the NETC switch also supports a proprietary
switch tag, is used to carry in-band metadata information about frames.
This in-band metadata information can include the source port from which
the frame was received, what was the reason why this frame got forwarded
to the entity, and for the entity to indicate the precise destination
port of a frame. The NETC switch tag is added to frames after the source
MAC address. There are three types of switch tags, and each type has 1
to 4 subtypes, more details are as follows.
Forward switch tag (Type = 0): Represents forwarded frames.
- SubType = 0 - Normal frame processing.
To_Port switch tag (Type = 1): Represents frames that are to be sent to
a specific switch port.
- SubType = 0. No request to perform timestamping.
- SubType = 1. Request to perform one-step timestamping.
- SubType = 2. Request to perform two-step timestamping.
- SubType = 3. Request to perform both one-step timestamping and
two-step timestamping.
To_Host switch tag (Type = 2): Represents frames redirected or copied to
the switch management port.
- SubType = 0. Received frames redirected or copied to the switch
management port.
- SubType = 1. Received frames redirected or copied to the switch
management port with captured timestamp at the switch port where
the frame was received.
- SubType = 2. Transmit timestamp response (two-step timestamping).
Currently, this patch set supports Forward tag, SubType 0 of To_Port tag
and SubType 0 of To_Host tag. More tags will be supported in the future.
In addition, the switch supports NETC Table Management Protocol (NTMP),
some switch functionality is controlled using control messages sent to
the hardware using BD ring interface with 32B descriptors similar to the
packet Transmit BD ring used on ENETC. This interface is referred to as
the command BD ring. This is used to configure functionality where the
underlying resources may be shared between different entities or being
too large to configure using direct registers.
For this patch set, we have supported the following tables through the
command BD ring interface.
FDB Table: It contains forwarding and/or filtering information about MAC
addresses. The FDB table is used for MAC learning lookups and MAC
forwarding lookups.
VLAN Filter Table: It contains configuration and control information for
each VLAN configured on the switch.
Buffer Pool Table: It contains buffer pool configuration and operational
information. Each entry corresponds to a buffer pool. Currently, we use
this table to implement flow control feature on each port.
Ingress Port Filter Table: It contains a set of filters each capable of
classifying incoming traffic using a mix of L2, L3, and L4 parsed and
arbitrary field data. We use this table to implement host flood support
to the switch port.
The switch also supports other tables, and we will add more advanced
features through them in the future.
---
v7:
1. Add items to dsa'member property
2. Refine netc_rcv() and netc_get_rx_tag_len()
3. Add "depends on ARM64 || COMPILE_TEST" to config NET_DSA_NETC_SWITCH
4. Add addr check in netc_port_set_fdb_entry()
5. Add more comments to avoid false positives from sashiko
v6 link: https://lore.kernel.org/imx/20260509102954.4116624-1-wei.fang@nxp.com/
v5 link: https://lore.kernel.org/imx/20260430024945.3413973-1-wei.fang@nxp.com/
v4 link: https://lore.kernel.org/imx/20260331113025.1566878-1-wei.fang@nxp.com/
v3 link: https://lore.kernel.org/imx/20260326062917.3552334-1-wei.fang@nxp.com/
v2 link: https://lore.kernel.org/imx/20260323060752.1157031-1-wei.fang@nxp.com/
v1 link: https://lore.kernel.org/imx/20260316094152.1558671-1-wei.fang@nxp.com/
---
Wei Fang (15):
dt-bindings: net: dsa: update the description of 'dsa,member' property
dt-bindings: net: dsa: add NETC switch
net: enetc: add pre-boot initialization for i.MX94 switch
net: enetc: add basic operations to the FDB table
net: enetc: add support for the "Add" operation to VLAN filter table
net: enetc: add support for the "Update" operation to buffer pool
table
net: enetc: add support for "Add" and "Delete" operations to IPFT
net: enetc: add multiple command BD rings support
net: dsa: add NETC switch tag support
net: dsa: netc: introduce NXP NETC switch driver for i.MX94
net: dsa: netc: add phylink MAC operations
net: dsa: netc: add FDB, STP, MTU, port setup and host flooding
support
net: dsa: netc: initialize buffer pool table and implement
flow-control
net: dsa: netc: add support for the standardized counters
net: dsa: netc: add support for ethtool private statistics
.../devicetree/bindings/net/dsa/dsa.yaml | 6 +-
.../bindings/net/dsa/nxp,netc-switch.yaml | 131 ++
MAINTAINERS | 11 +
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/netc/Kconfig | 15 +
drivers/net/dsa/netc/Makefile | 3 +
drivers/net/dsa/netc/netc_ethtool.c | 297 +++
drivers/net/dsa/netc/netc_main.c | 1585 +++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 87 +
drivers/net/dsa/netc/netc_switch.h | 173 ++
drivers/net/dsa/netc/netc_switch_hw.h | 361 ++++
.../ethernet/freescale/enetc/netc_blk_ctrl.c | 185 +-
drivers/net/ethernet/freescale/enetc/ntmp.c | 391 +++-
.../ethernet/freescale/enetc/ntmp_private.h | 122 +-
include/linux/dsa/tag_netc.h | 14 +
include/linux/fsl/netc_global.h | 6 +
include/linux/fsl/ntmp.h | 187 +-
include/net/dsa.h | 2 +
include/uapi/linux/if_ether.h | 1 +
net/dsa/Kconfig | 10 +
net/dsa/Makefile | 1 +
net/dsa/tag_netc.c | 214 +++
23 files changed, 3775 insertions(+), 30 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
create mode 100644 drivers/net/dsa/netc/Kconfig
create mode 100644 drivers/net/dsa/netc/Makefile
create mode 100644 drivers/net/dsa/netc/netc_ethtool.c
create mode 100644 drivers/net/dsa/netc/netc_main.c
create mode 100644 drivers/net/dsa/netc/netc_platform.c
create mode 100644 drivers/net/dsa/netc/netc_switch.h
create mode 100644 drivers/net/dsa/netc/netc_switch_hw.h
create mode 100644 include/linux/dsa/tag_netc.h
create mode 100644 net/dsa/tag_netc.c
--
2.34.1
^ permalink raw reply
* Re: [PATCH v2] drivers/base/memory: make memory block get/put explicit
From: Muchun Song @ 2026-05-13 1:41 UTC (permalink / raw)
To: Richard Cheng
Cc: Muchun Song, Andrew Morton, David Hildenbrand, Greg Kroah-Hartman,
linux-mm, driver-core, Oscar Salvador, Lorenzo Stoakes,
Liam R . Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Danilo Krummrich,
Rafael J . Wysocki, linux-kernel, linux-cxl, linuxppc-dev,
linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy, Heiko Carstens, Vasily Gorbik,
Alexander Gordeev, Christian Borntraeger, Sven Schnelle,
Sumanth Korikkar, Kees Cook, Douglas Anderson, Donet Tom
In-Reply-To: <agMVtO5QPGYVb48D@MWDK4CY14F>
> On May 12, 2026, at 20:03, Richard Cheng <icheng@nvidia.com> wrote:
>
> On Tue, May 12, 2026 at 03:26:35PM +0800, Muchun Song wrote:
>> Rename the memory block lookup helper to make the acquired reference
>> explicit, add memory_block_put() to wrap put_device(), remove
>> find_memory_block(), and use memory_block_get() as the single block-id
>> based lookup interface.
>>
>> This makes it clearer to callers that a successful lookup holds a
>> reference that must be dropped, reducing the chance of forgetting the
>> matching put and leaking the memory block device reference.
>>
>> Link: https://lore.kernel.org/linux-mm/7887915D-E598-42B3-9AFE-BFFBACE8DE2D@linux.dev/#t
>> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
>> Acked-by: Oscar Salvador <osalvador@suse.de>
>> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
>> Acked-by: Michal Hocko <mhocko@suse.com>
>> Tested-by: Donet Tom <donettom@linux.ibm.com>
>> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
>> ---
>> Changes in v2:
>> - mention the removal of find_memory_block() in the commit message
>> - drop the redundant extern from the memory_block_get() declaration
>> ---
>> .../platforms/pseries/hotplug-memory.c | 14 ++-----
>> drivers/base/memory.c | 38 +++++++------------
>> drivers/base/node.c | 4 +-
>> drivers/s390/char/sclp_mem.c | 17 ++++-----
>> include/linux/memory.h | 7 +++-
>> mm/memory_hotplug.c | 5 +--
>> 6 files changed, 35 insertions(+), 50 deletions(-)
>>
>> diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c
>> index 75f85a5da981..94f3b57054b6 100644
>> --- a/arch/powerpc/platforms/pseries/hotplug-memory.c
>> +++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
>> @@ -164,13 +164,7 @@ static int update_lmb_associativity_index(struct drmem_lmb *lmb)
>>
>> static struct memory_block *lmb_to_memblock(struct drmem_lmb *lmb)
>> {
>> - unsigned long section_nr;
>> - struct memory_block *mem_block;
>> -
>> - section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr));
>> -
>> - mem_block = find_memory_block(section_nr);
>> - return mem_block;
>> + return memory_block_get(phys_to_block_id(lmb->base_addr));
>> }
>>
>> static int get_lmb_range(u32 drc_index, int n_lmbs,
>> @@ -220,7 +214,7 @@ static int dlpar_change_lmb_state(struct drmem_lmb *lmb, bool online)
>> else
>> rc = 0;
>>
>> - put_device(&mem_block->dev);
>> + memory_block_put(mem_block);
>>
>> return rc;
>> }
>> @@ -319,12 +313,12 @@ static int dlpar_remove_lmb(struct drmem_lmb *lmb)
>>
>> rc = dlpar_offline_lmb(lmb);
>> if (rc) {
>> - put_device(&mem_block->dev);
>> + memory_block_put(mem_block);
>> return rc;
>> }
>>
>> __remove_memory(lmb->base_addr, memory_block_size);
>> - put_device(&mem_block->dev);
>> + memory_block_put(mem_block);
>>
>> /* Update memory regions for memory remove */
>> memblock_remove(lmb->base_addr, memory_block_size);
>> diff --git a/drivers/base/memory.c b/drivers/base/memory.c
>> index 11d57cfa8d72..5b5d41089e81 100644
>> --- a/drivers/base/memory.c
>> +++ b/drivers/base/memory.c
>> @@ -649,7 +649,7 @@ int __weak arch_get_memory_phys_device(unsigned long start_pfn)
>> *
>> * Called under device_hotplug_lock.
>> */
>> -struct memory_block *find_memory_block_by_id(unsigned long block_id)
>> +struct memory_block *memory_block_get(unsigned long block_id)
>> {
>> struct memory_block *mem;
>>
>> @@ -659,16 +659,6 @@ struct memory_block *find_memory_block_by_id(unsigned long block_id)
>> return mem;
>> }
>>
>> -/*
>> - * Called under device_hotplug_lock.
>> - */
>> -struct memory_block *find_memory_block(unsigned long section_nr)
>> -{
>> - unsigned long block_id = memory_block_id(section_nr);
>> -
>> - return find_memory_block_by_id(block_id);
>> -}
>> -
>> static struct attribute *memory_memblk_attrs[] = {
>> &dev_attr_phys_index.attr,
>> &dev_attr_state.attr,
>> @@ -701,7 +691,7 @@ static int __add_memory_block(struct memory_block *memory)
>>
>> ret = device_register(&memory->dev);
>> if (ret) {
>> - put_device(&memory->dev);
>> + memory_block_put(memory);
>> return ret;
>> }
>> ret = xa_err(xa_store(&memory_blocks, memory->dev.id, memory,
>> @@ -795,9 +785,9 @@ static int add_memory_block(unsigned long block_id, int nid, unsigned long state
>> struct memory_block *mem;
>> int ret = 0;
>>
>> - mem = find_memory_block_by_id(block_id);
>> + mem = memory_block_get(block_id);
>> if (mem) {
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> return -EEXIST;
>> }
>> mem = kzalloc_obj(*mem);
>> @@ -845,8 +835,8 @@ static void remove_memory_block(struct memory_block *memory)
>> memory->group = NULL;
>> }
>>
>> - /* drop the ref. we got via find_memory_block() */
>> - put_device(&memory->dev);
>> + /* drop the ref. we got via memory_block_get() */
>> + memory_block_put(memory);
>> device_unregister(&memory->dev);
>> }
>>
>> @@ -880,7 +870,7 @@ int create_memory_block_devices(unsigned long start, unsigned long size,
>> end_block_id = block_id;
>> for (block_id = start_block_id; block_id != end_block_id;
>> block_id++) {
>> - mem = find_memory_block_by_id(block_id);
>> + mem = memory_block_get(block_id);
>> if (WARN_ON_ONCE(!mem))
>> continue;
>> remove_memory_block(mem);
>> @@ -908,7 +898,7 @@ void remove_memory_block_devices(unsigned long start, unsigned long size)
>> return;
>>
>> for (block_id = start_block_id; block_id != end_block_id; block_id++) {
>> - mem = find_memory_block_by_id(block_id);
>> + mem = memory_block_get(block_id);
>> if (WARN_ON_ONCE(!mem))
>> continue;
>> num_poisoned_pages_sub(-1UL, memblk_nr_poison(mem));
>> @@ -1015,12 +1005,12 @@ int walk_memory_blocks(unsigned long start, unsigned long size,
>> return 0;
>>
>> for (block_id = start_block_id; block_id <= end_block_id; block_id++) {
>> - mem = find_memory_block_by_id(block_id);
>> + mem = memory_block_get(block_id);
>> if (!mem)
>> continue;
>>
>> ret = func(mem, arg);
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> if (ret)
>> break;
>> }
>> @@ -1228,22 +1218,22 @@ int walk_dynamic_memory_groups(int nid, walk_memory_groups_func_t func,
>> void memblk_nr_poison_inc(unsigned long pfn)
>> {
>> const unsigned long block_id = pfn_to_block_id(pfn);
>> - struct memory_block *mem = find_memory_block_by_id(block_id);
>> + struct memory_block *mem = memory_block_get(block_id);
>>
>> if (mem) {
>> atomic_long_inc(&mem->nr_hwpoison);
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> }
>> }
>>
>> void memblk_nr_poison_sub(unsigned long pfn, long i)
>> {
>> const unsigned long block_id = pfn_to_block_id(pfn);
>> - struct memory_block *mem = find_memory_block_by_id(block_id);
>> + struct memory_block *mem = memory_block_get(block_id);
>>
>> if (mem) {
>> atomic_long_sub(i, &mem->nr_hwpoison);
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> }
>> }
>>
>> diff --git a/drivers/base/node.c b/drivers/base/node.c
>> index 126f66aa2c3e..b3333ca92090 100644
>> --- a/drivers/base/node.c
>> +++ b/drivers/base/node.c
>> @@ -847,13 +847,13 @@ static void register_memory_blocks_under_nodes(void)
>> for (block_id = start_block_id; block_id <= end_block_id; block_id++) {
>> struct memory_block *mem;
>>
>> - mem = find_memory_block_by_id(block_id);
>> + mem = memory_block_get(block_id);
>> if (!mem)
>> continue;
>>
>> memory_block_add_nid_early(mem, nid);
>> do_register_memory_block_under_node(nid, mem);
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> }
>>
>> }
>> diff --git a/drivers/s390/char/sclp_mem.c b/drivers/s390/char/sclp_mem.c
>> index 78c054e26d17..6df1926d4c62 100644
>> --- a/drivers/s390/char/sclp_mem.c
>> +++ b/drivers/s390/char/sclp_mem.c
>> @@ -204,7 +204,7 @@ static ssize_t sclp_config_mem_store(struct kobject *kobj, struct kobj_attribute
>> addr = sclp_mem->id * block_size;
>> /*
>> * Hold device_hotplug_lock when adding/removing memory blocks.
>> - * Additionally, also protect calls to find_memory_block() and
>> + * Additionally, also protect calls to memory_block_get() and
>> * sclp_attach_storage().
>> */
>> rc = lock_device_hotplug_sysfs();
>> @@ -231,20 +231,19 @@ static ssize_t sclp_config_mem_store(struct kobject *kobj, struct kobj_attribute
>> sclp_mem_change_state(addr, block_size, 0);
>> goto out_unlock;
>> }
>> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(addr)));
>> - put_device(&mem->dev);
>> + mem = memory_block_get(phys_to_block_id(addr));
>> + memory_block_put(mem);
>> WRITE_ONCE(sclp_mem->config, 1);
>> } else {
>> if (!sclp_mem->config)
>> goto out_unlock;
>> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(addr)));
>> + mem = memory_block_get(phys_to_block_id(addr));
>> if (mem->state != MEM_OFFLINE) {
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> rc = -EBUSY;
>> goto out_unlock;
>> }
>> - /* drop the ref just got via find_memory_block() */
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> sclp_mem_change_state(addr, block_size, 0);
>> __remove_memory(addr, block_size);
>> #ifdef CONFIG_KASAN
>> @@ -294,11 +293,11 @@ static ssize_t sclp_memmap_on_memory_store(struct kobject *kobj, struct kobj_att
>> return rc;
>> block_size = memory_block_size_bytes();
>> sclp_mem = container_of(kobj, struct sclp_mem, kobj);
>> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(sclp_mem->id * block_size)));
>> + mem = memory_block_get(phys_to_block_id(sclp_mem->id * block_size));
>> if (!mem) {
>> WRITE_ONCE(sclp_mem->memmap_on_memory, value);
>> } else {
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>> rc = -EBUSY;
>> }
>> unlock_device_hotplug();
>> diff --git a/include/linux/memory.h b/include/linux/memory.h
>> index 5bb5599c6b2b..463dc02f6cff 100644
>> --- a/include/linux/memory.h
>> +++ b/include/linux/memory.h
>> @@ -158,7 +158,11 @@ int create_memory_block_devices(unsigned long start, unsigned long size,
>> void remove_memory_block_devices(unsigned long start, unsigned long size);
>> extern void memory_dev_init(void);
>> extern int memory_notify(enum memory_block_state state, void *v);
>> -extern struct memory_block *find_memory_block(unsigned long section_nr);
>> +struct memory_block *memory_block_get(unsigned long block_id);
>> +static inline void memory_block_put(struct memory_block *mem)
>> +{
>> + put_device(&mem->dev);
>> +}
>
> Hi Muchun,
Hi,
>
> Thanks for the work, I have a small suggestion if that fits your thought.
>
> I think we should at least add a comment above memory_block_put() to remind the caller to check
> for the availabitliy of "mem" before calling this function.
> We perform the check in memory_block_get() inside the function body, I see different usage pattern
> across the caller when they're dealing with "mem == NULL" and avoid to call memory_block_put(),
> I can understand we should leverage the check to caller, not inside memory_block_put().
> But just in case the next caller might forgot to do the check or think the behavior might be symmetric
> bettween memory_block_get() and memory_block_put(), a comment above the function would be nice.
Thanks for the suggestion!
Regarding the additional comment, I feel they might not be strictly necessary.
If a user passes a NULL pointer, the issue would be exposed immediately
before memory_block_put() is even called. It's unlikely a user would obtain
mem and not perform any read/write operations; any such attempt would trigger
a NULL pointer dereference right away.
As for adding comments to memory_block_get(), I’m wondering if it’s truly
essential. The function is quite straightforward—anyone looking at the
definition would see the implementation alongside the comments. It’s very
clear from the code that mem must be non-NULL to be "gotten."
Overall, I’m concerned the extra comments might not add much value. What do
you think?
Thanks,
Muchun
>
> Best regards,
> Richard Cheng.
>
>> typedef int (*walk_memory_blocks_func_t)(struct memory_block *, void *);
>> extern int walk_memory_blocks(unsigned long start, unsigned long size,
>> void *arg, walk_memory_blocks_func_t func);
>> @@ -171,7 +175,6 @@ struct memory_group *memory_group_find_by_id(int mgid);
>> typedef int (*walk_memory_groups_func_t)(struct memory_group *, void *);
>> int walk_dynamic_memory_groups(int nid, walk_memory_groups_func_t func,
>> struct memory_group *excluded, void *arg);
>> -struct memory_block *find_memory_block_by_id(unsigned long block_id);
>> #define hotplug_memory_notifier(fn, pri) ({ \
>> static __meminitdata struct notifier_block fn##_mem_nb =\
>> { .notifier_call = fn, .priority = pri };\
>> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
>> index 462d8dcd636d..890c6453e887 100644
>> --- a/mm/memory_hotplug.c
>> +++ b/mm/memory_hotplug.c
>> @@ -1417,14 +1417,13 @@ static void remove_memory_blocks_and_altmaps(u64 start, u64 size)
>> struct vmem_altmap *altmap = NULL;
>> struct memory_block *mem;
>>
>> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(cur_start)));
>> + mem = memory_block_get(phys_to_block_id(cur_start));
>> if (WARN_ON_ONCE(!mem))
>> continue;
>>
>> altmap = mem->altmap;
>> mem->altmap = NULL;
>> - /* drop the ref. we got via find_memory_block() */
>> - put_device(&mem->dev);
>> + memory_block_put(mem);
>>
>> remove_memory_block_devices(cur_start, memblock_size);
>>
>>
>> base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
>> --
>> 2.54.0
^ permalink raw reply
* Re: [PATCH V4 2/2] tools/perf: Use scnprintf in buffer offset calculations
From: Ian Rogers @ 2026-05-13 1:02 UTC (permalink / raw)
To: Athira Rajeev
Cc: acme, jolsa, adrian.hunter, mpetlan, tmricht, maddy, namhyung,
linux-perf-users, linuxppc-dev, hbathini, Tejas.Manhas1,
Tanushree.Shah, shivani
In-Reply-To: <20260504154205.21394-2-atrajeev@linux.ibm.com>
On Mon, May 4, 2026 at 8:42 AM Athira Rajeev <atrajeev@linux.ibm.com> wrote:
>
> Replace snprintf with scnprintf in buffer offset calculations to
> ensure the 'used' count will not exceed the "len".
>
> The current logic in perf_pmu__for_each_event uses an unconditional
> + 1 increment to buf_used to account for null terminators. This can
> cause a a stack buffer overflow in the subsequent scnprintf call.
> When the local stack buffer buf (1024 bytes) is full, buf_used can
> reach 1025. This causes the subsequent remaining space calculation
> sizeof(buf) - buf_used to underflow.
>
> Use sub_non_neg() to see if space actually existed, and only
> increment the offset if remaning space is present.
>
> Changes includes:
> - Use sub_non_neg to check if space exists
> - Replacing snprintf with scnprintf to ensure the return value
> reflects the actual bytes written into the buffer.
> - Only increment buf_used by 1 if space exists
> - If a parameterized event uses a built-in perf keyword for its
> parameter name (eg, config=?), the lexer parses it as a predefined
> term token, which sets term->config to NULL. Add check to use
> parse_events__term_type_str() if term->config is NULL.
>
> Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> Changelog:
> v2 -> v3:
> - Split the scnprintf related changes in separate patch
> - Handle the overflow issues and unconditional increment
> wrapped around sub_non_neg addressing review comment from Sashiko
>
> tools/perf/util/pmu.c | 46 ++++++++++++++++++++++++++++++++-----------
> 1 file changed, 35 insertions(+), 11 deletions(-)
>
> diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
> index 0b8d58543f17..4b9ade1a4cf9 100644
> --- a/tools/perf/util/pmu.c
> +++ b/tools/perf/util/pmu.c
> @@ -2129,15 +2129,19 @@ static char *format_alias(char *buf, int len, const struct perf_pmu *pmu,
> pr_err("Failure to parse '%s' terms '%s': %d\n",
> alias->name, alias->terms, ret);
> parse_events_terms__exit(&terms);
> - snprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name);
> + scnprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name);
> return buf;
> }
> - used = snprintf(buf, len, "%.*s/%s", (int)pmu_name_len, pmu->name, alias->name);
> + used = scnprintf(buf, len, "%.*s/%s", (int)pmu_name_len, pmu->name, alias->name);
>
> list_for_each_entry(term, &terms.terms, list) {
> + const char *name = term->config;
> +
> + if (!name)
> + name = parse_events__term_type_str(term->type_term);
> if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR)
> - used += snprintf(buf + used, sub_non_neg(len, used),
> - ",%s=%s", term->config,
> + used += scnprintf(buf + used, sub_non_neg(len, used),
> + ",%s=%s", name,
> term->val.str);
> }
> parse_events_terms__exit(&terms);
> @@ -2201,6 +2205,7 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
> int ret = 0;
> struct hashmap_entry *entry;
> size_t bkt;
> + size_t size_rem, len;
>
> if (perf_pmu__is_tracepoint(pmu))
> return tp_pmu__for_each_event(pmu, state, cb);
> @@ -2234,17 +2239,36 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
> }
> buf_used = strlen(buf) + 1;
> }
> +
> info.scale_unit = NULL;
> if (strlen(event->unit) || event->scale != 1.0) {
> - info.scale_unit = buf + buf_used;
> - buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used,
> - "%G%s", event->scale, event->unit) + 1;
> + /* Check the remaining space */
> + size_rem = sub_non_neg(sizeof(buf), buf_used);
> +
> + if (size_rem > 0) {
> + info.scale_unit = buf + buf_used;
> + len = scnprintf(buf + buf_used, size_rem, "%G%s",
> + event->scale, event->unit);
> + /*
> + * Increment buf_used by 1 only if
> + * it fits remaining space
> + */
> + buf_used += min(len + 1, size_rem);
> + }
> }
> info.desc = event->desc;
> info.long_desc = event->long_desc;
> - info.encoding_desc = buf + buf_used;
> - buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used,
> - "%.*s/%s/", (int)pmu_name_len, info.pmu_name, event->terms) + 1;
> + info.encoding_desc = NULL;
> +
> + /* Check the remaining space */
> + size_rem = sub_non_neg(sizeof(buf), buf_used);
> + if (size_rem > 0) {
> + info.encoding_desc = buf + buf_used;
> + len = scnprintf(buf + buf_used, size_rem, "%.*s/%s/",
> + (int)pmu_name_len, info.pmu_name, event->terms);
> + buf_used += min(len + 1, size_rem);
> + }
> +
> info.str = event->terms;
> info.topic = event->topic;
> info.deprecated = perf_pmu_alias__check_deprecated(pmu, event);
> @@ -2254,7 +2278,7 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
> }
> if (pmu->selectable) {
> info.name = buf;
> - snprintf(buf, sizeof(buf), "%s//", pmu->name);
> + scnprintf(buf, sizeof(buf), "%s//", pmu->name);
> info.alias = NULL;
> info.scale_unit = NULL;
> info.desc = NULL;
> --
> 2.47.3
>
^ permalink raw reply
* Re: [PATCH V4 1/2] tools/perf: Fix the check for parameterized field in event term
From: Ian Rogers @ 2026-05-13 0:57 UTC (permalink / raw)
To: Venkat
Cc: Athira Rajeev, acme, jolsa, adrian.hunter, mpetlan, tmricht,
maddy, namhyung, linux-perf-users, linuxppc-dev, hbathini,
Tejas.Manhas1, Tanushree.Shah, shivani
In-Reply-To: <0FE86BD2-554C-402C-80A2-C8F86FC2F685@linux.ibm.com>
On Tue, May 12, 2026 at 1:53 AM Venkat <venkat88@linux.ibm.com> wrote:
>
>
>
> > On 4 May 2026, at 9:12 PM, Athira Rajeev <atrajeev@linux.ibm.com> wrote:
> >
> > The format_alias() function in util/pmu.c has a check to
> > detect whether the event has parameterized field ( =? ).
> > The string alias->terms contains the event and if the event
> > has user configurable parameter, there will be presence of
> > sub string "=?" in the alias->terms.
> >
> > Snippet of code:
> >
> > /* Paramemterized events have the parameters shown. */
> > if (strstr(alias->terms, "=?")) {
> > /* No parameters. */
> > snprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name);
> >
> > if "strstr" contains the substring, it returns a pointer
> > and hence enters the above check which is not the expected
> > check. And hence "perf list" doesn't have the parameterized
> > fields in the result.
> >
> > Fix this check to use:
> >
> > if (!strstr(alias->terms, "=?")) {
> >
> > With this change, perf list shows the events correctly with
> > the strings showing parameters.
> >
> > Before the fix:
> >
> > # ./perf list|grep -w PM_PAU_CYC
> > hv_24x7/PM_PAU_CYC/ [Kernel PMU event]
> >
> > With this fix:
> >
> > # ./perf list|grep -w PM_PAU_CYC
> > hv_24x7/PM_PAU_CYC,chip=?/ [Kernel PMU event]
> >
> > Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
>
> Tested-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> > ---
> > Changelog:
> > v3 -> v4:
> > Updated commit message to show real example
> > addressing review comment from Namhyung.
> >
> > v2 -> v3:
> > Split the strstr correction in a single patch
> >
> > tools/perf/util/pmu.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
> > index 23337d2fa281..0b8d58543f17 100644
> > --- a/tools/perf/util/pmu.c
> > +++ b/tools/perf/util/pmu.c
> > @@ -2117,7 +2117,7 @@ static char *format_alias(char *buf, int len, const struct perf_pmu *pmu,
> > skip_duplicate_pmus);
> >
> > /* Paramemterized events have the parameters shown. */
> > - if (strstr(alias->terms, "=?")) {
> > + if (!strstr(alias->terms, "=?")) {
> > /* No parameters. */
> > snprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name);
> > return buf;
> > --
> > 2.47.3
> >
>
^ permalink raw reply
* [powerpc:fixes-test] BUILD SUCCESS dbc30a57bd8e026995e9fa8e8c31cffd18542c01
From: kernel test robot @ 2026-05-12 18:35 UTC (permalink / raw)
To: Madhavan Srinivasan; +Cc: linuxppc-dev
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git fixes-test
branch HEAD: dbc30a57bd8e026995e9fa8e8c31cffd18542c01 powerpc/hv-gpci: fix preempt count leak in sysfs show paths
elapsed time: 725m
configs tested: 255
configs skipped: 165
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc defconfig gcc-15.2.0
arc haps_hs_defconfig gcc-15.2.0
arc randconfig-001 gcc-8.5.0
arc randconfig-001-20260512 gcc-11.5.0
arc randconfig-001-20260512 gcc-8.5.0
arc randconfig-002 gcc-8.5.0
arc randconfig-002-20260512 gcc-11.5.0
arc randconfig-002-20260512 gcc-8.5.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm defconfig gcc-15.2.0
arm footbridge_defconfig clang-17
arm randconfig-001 gcc-8.5.0
arm randconfig-001-20260512 gcc-11.5.0
arm randconfig-001-20260512 gcc-8.5.0
arm randconfig-002 gcc-8.5.0
arm randconfig-002-20260512 gcc-11.5.0
arm randconfig-002-20260512 gcc-8.5.0
arm randconfig-003 gcc-8.5.0
arm randconfig-003-20260512 gcc-11.5.0
arm randconfig-003-20260512 gcc-8.5.0
arm randconfig-004 gcc-8.5.0
arm randconfig-004-20260512 gcc-11.5.0
arm randconfig-004-20260512 gcc-8.5.0
arm spear3xx_defconfig clang-17
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001 clang-23
arm64 randconfig-001-20260512 clang-23
arm64 randconfig-001-20260512 gcc-14.3.0
arm64 randconfig-002 clang-23
arm64 randconfig-002-20260512 clang-23
arm64 randconfig-002-20260512 gcc-14.3.0
arm64 randconfig-003 clang-23
arm64 randconfig-003-20260512 clang-23
arm64 randconfig-003-20260512 gcc-14.3.0
arm64 randconfig-004 clang-23
arm64 randconfig-004-20260512 clang-23
arm64 randconfig-004-20260512 gcc-14.3.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001 clang-23
csky randconfig-001-20260512 clang-23
csky randconfig-001-20260512 gcc-14.3.0
csky randconfig-002 clang-23
csky randconfig-002-20260512 clang-23
csky randconfig-002-20260512 gcc-14.3.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001 gcc-11.5.0
hexagon randconfig-001-20260512 gcc-10.5.0
hexagon randconfig-002 gcc-11.5.0
hexagon randconfig-002-20260512 gcc-10.5.0
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260512 gcc-14
i386 buildonly-randconfig-002-20260512 gcc-14
i386 buildonly-randconfig-003-20260512 gcc-14
i386 buildonly-randconfig-004-20260512 gcc-14
i386 buildonly-randconfig-005-20260512 gcc-14
i386 buildonly-randconfig-006-20260512 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-001 gcc-14
i386 randconfig-001-20260512 gcc-14
i386 randconfig-002 gcc-14
i386 randconfig-002-20260512 gcc-14
i386 randconfig-003 gcc-14
i386 randconfig-003-20260512 gcc-14
i386 randconfig-004 gcc-14
i386 randconfig-004-20260512 gcc-14
i386 randconfig-005 gcc-14
i386 randconfig-005-20260512 gcc-14
i386 randconfig-006 gcc-14
i386 randconfig-006-20260512 gcc-14
i386 randconfig-007 gcc-14
i386 randconfig-007-20260512 gcc-14
i386 randconfig-011 clang-20
i386 randconfig-011-20260512 clang-20
i386 randconfig-012 clang-20
i386 randconfig-012-20260512 clang-20
i386 randconfig-013 clang-20
i386 randconfig-013-20260512 clang-20
i386 randconfig-014 clang-20
i386 randconfig-014-20260512 clang-20
i386 randconfig-015 clang-20
i386 randconfig-015-20260512 clang-20
i386 randconfig-016 clang-20
i386 randconfig-016-20260512 clang-20
i386 randconfig-017 clang-20
i386 randconfig-017-20260512 clang-20
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001 gcc-11.5.0
loongarch randconfig-001-20260512 gcc-10.5.0
loongarch randconfig-002 gcc-11.5.0
loongarch randconfig-002-20260512 gcc-10.5.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k amiga_defconfig gcc-15.2.0
m68k atari_defconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips cu1000-neo_defconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001 gcc-11.5.0
nios2 randconfig-001-20260512 gcc-10.5.0
nios2 randconfig-002 gcc-11.5.0
nios2 randconfig-002-20260512 gcc-10.5.0
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260512 gcc-12.5.0
parisc randconfig-001-20260513 gcc-8.5.0
parisc randconfig-002-20260512 gcc-12.5.0
parisc randconfig-002-20260513 gcc-8.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc randconfig-001-20260512 gcc-12.5.0
powerpc randconfig-001-20260513 gcc-8.5.0
powerpc randconfig-002-20260512 gcc-12.5.0
powerpc randconfig-002-20260513 gcc-8.5.0
powerpc64 randconfig-001-20260512 gcc-12.5.0
powerpc64 randconfig-001-20260513 gcc-8.5.0
powerpc64 randconfig-002-20260512 gcc-12.5.0
powerpc64 randconfig-002-20260513 gcc-8.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001 gcc-15.2.0
riscv randconfig-001-20260512 gcc-15.2.0
riscv randconfig-001-20260513 gcc-15.2.0
riscv randconfig-002 gcc-15.2.0
riscv randconfig-002-20260512 gcc-15.2.0
riscv randconfig-002-20260513 gcc-15.2.0
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001 gcc-15.2.0
s390 randconfig-001-20260512 gcc-15.2.0
s390 randconfig-001-20260513 gcc-15.2.0
s390 randconfig-002 gcc-15.2.0
s390 randconfig-002-20260512 gcc-15.2.0
s390 randconfig-002-20260513 gcc-15.2.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh defconfig gcc-14
sh randconfig-001 gcc-15.2.0
sh randconfig-001-20260512 gcc-15.2.0
sh randconfig-001-20260513 gcc-15.2.0
sh randconfig-002 gcc-15.2.0
sh randconfig-002-20260512 gcc-15.2.0
sh randconfig-002-20260513 gcc-15.2.0
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001 gcc-13.4.0
sparc randconfig-001-20260512 gcc-13.4.0
sparc randconfig-002 gcc-13.4.0
sparc randconfig-002-20260512 gcc-13.4.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001 gcc-13.4.0
sparc64 randconfig-001-20260512 gcc-13.4.0
sparc64 randconfig-002 gcc-13.4.0
sparc64 randconfig-002-20260512 gcc-13.4.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001 gcc-13.4.0
um randconfig-001-20260512 gcc-13.4.0
um randconfig-002 gcc-13.4.0
um randconfig-002-20260512 gcc-13.4.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001 gcc-14
x86_64 buildonly-randconfig-001-20260512 gcc-14
x86_64 buildonly-randconfig-002 gcc-14
x86_64 buildonly-randconfig-002-20260512 gcc-14
x86_64 buildonly-randconfig-003 gcc-14
x86_64 buildonly-randconfig-003-20260512 gcc-14
x86_64 buildonly-randconfig-004 gcc-14
x86_64 buildonly-randconfig-004-20260512 gcc-14
x86_64 buildonly-randconfig-005 gcc-14
x86_64 buildonly-randconfig-005-20260512 gcc-14
x86_64 buildonly-randconfig-006 gcc-14
x86_64 buildonly-randconfig-006-20260512 gcc-14
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001 gcc-14
x86_64 randconfig-001-20260512 gcc-14
x86_64 randconfig-002 gcc-14
x86_64 randconfig-002-20260512 gcc-14
x86_64 randconfig-003 gcc-14
x86_64 randconfig-003-20260512 gcc-14
x86_64 randconfig-004 gcc-14
x86_64 randconfig-004-20260512 gcc-14
x86_64 randconfig-005 gcc-14
x86_64 randconfig-005-20260512 gcc-14
x86_64 randconfig-006 gcc-14
x86_64 randconfig-006-20260512 gcc-14
x86_64 randconfig-011-20260512 clang-20
x86_64 randconfig-012-20260512 clang-20
x86_64 randconfig-013-20260512 clang-20
x86_64 randconfig-014-20260512 clang-20
x86_64 randconfig-015-20260512 clang-20
x86_64 randconfig-016-20260512 clang-20
x86_64 randconfig-071-20260512 clang-20
x86_64 randconfig-072-20260512 clang-20
x86_64 randconfig-073-20260512 clang-20
x86_64 randconfig-074-20260512 clang-20
x86_64 randconfig-075-20260512 clang-20
x86_64 randconfig-076-20260512 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
xtensa randconfig-001 gcc-13.4.0
xtensa randconfig-001-20260512 gcc-13.4.0
xtensa randconfig-002 gcc-13.4.0
xtensa randconfig-002-20260512 gcc-13.4.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] powerpc/64s: Fix the vector number in comments for h_facility_unavailable
From: Amit Machhiwal @ 2026-05-12 18:03 UTC (permalink / raw)
To: Gautam Menghani
Cc: maddy, mpe, npiggin, chleroy, Gautam Menghani, linuxppc-dev,
linux-kernel
In-Reply-To: <20260511080412.50722-1-Gautam.Menghani@ibm.com>
On 2026/05/11 01:34 PM, Gautam Menghani wrote:
> From: Gautam Menghani <gautam@linux.ibm.com>
>
> The comments explaining the h_facility_unavailable interrupt have mentioned
> the vector number as 0xf60 instead of 0xf80. Fix this typo.
>
> Signed-off-by: Gautam Menghani <gautam@linux.ibm.com>
> ---
> arch/powerpc/kernel/exceptions-64s.S | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
Reviewed-by: Amit Machhiwal <amachhiw@linux.ibm.com>
>
> diff --git a/arch/powerpc/kernel/exceptions-64s.S b/arch/powerpc/kernel/exceptions-64s.S
> index b7229430ca94..2696fbbca3b6 100644
> --- a/arch/powerpc/kernel/exceptions-64s.S
> +++ b/arch/powerpc/kernel/exceptions-64s.S
> @@ -2498,7 +2498,7 @@ EXC_COMMON_BEGIN(facility_unavailable_common)
>
>
> /**
> - * Interrupt 0xf60 - Hypervisor Facility Unavailable Interrupt.
> + * Interrupt 0xf80 - Hypervisor Facility Unavailable Interrupt.
> * This is a synchronous interrupt in response to
> * executing an instruction without access to the facility that can only
> * be resolved in HV mode (e.g., HFSCR).
> --
> 2.53.0
>
>
^ permalink raw reply
* Re: [PATCH] powerpc/64s: Fix the vector number in comments for h_facility_unavailable
From: Harsh Prateek Bora @ 2026-05-12 17:48 UTC (permalink / raw)
To: Gautam Menghani, maddy, mpe, npiggin, chleroy
Cc: Gautam Menghani, linuxppc-dev, linux-kernel
In-Reply-To: <20260511080412.50722-1-Gautam.Menghani@ibm.com>
On 11/05/26 1:34 pm, Gautam Menghani wrote:
> From: Gautam Menghani <gautam@linux.ibm.com>
>
> The comments explaining the h_facility_unavailable interrupt have mentioned
> the vector number as 0xf60 instead of 0xf80. Fix this typo.
>
> Signed-off-by: Gautam Menghani <gautam@linux.ibm.com>
> ---
> arch/powerpc/kernel/exceptions-64s.S | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/powerpc/kernel/exceptions-64s.S b/arch/powerpc/kernel/exceptions-64s.S
> index b7229430ca94..2696fbbca3b6 100644
> --- a/arch/powerpc/kernel/exceptions-64s.S
> +++ b/arch/powerpc/kernel/exceptions-64s.S
> @@ -2498,7 +2498,7 @@ EXC_COMMON_BEGIN(facility_unavailable_common)
>
>
> /**
> - * Interrupt 0xf60 - Hypervisor Facility Unavailable Interrupt.
> + * Interrupt 0xf80 - Hypervisor Facility Unavailable Interrupt.
Reviewed-by: Harsh Prateek Bora <harshpb@linux.ibm.com>
> * This is a synchronous interrupt in response to
> * executing an instruction without access to the facility that can only
> * be resolved in HV mode (e.g., HFSCR).
^ permalink raw reply
* Re: [PATCH] powerpc/rtas_pci: No hotplug on permanently removed device on pSeries
From: Harsh Prateek Bora @ 2026-05-12 17:44 UTC (permalink / raw)
To: Shivaprasad G Bhat, maddy, linuxppc-dev
Cc: mpe, npiggin, chleroy, linux-kernel
In-Reply-To: <b7b05e38-675a-4304-a266-91b10ab74e50@linux.ibm.com>
On 12/05/26 10:47 pm, Harsh Prateek Bora wrote:
>
>
> On 27/04/26 8:25 am, Shivaprasad G Bhat wrote:
>> The eeh_driver disables and offlines the PE permanently when it
>> exceeds the freeze count beyond eeh_max_freeze within the last hour.
>> The PE is only offline, so the device tree entries, eeh device
>> references are all intact till the real unplug of the device from
>> the guest/host takes place.
>>
>> On pSeries, with a new hotplug of any PCI device, the drmgr initiates
>> a system-wide PCI rescan, which finds devices offlined by the eeh_driver
>> and there will be attempts to bring them online. This leads to
>> recurring EEHs either at the config read time itself or a bit
>> later depending on the type of the problem.
>>
>> For PowerNV, the commit d2b0f6f77ee5 ("powerpc/eeh: No hotplug on
>> permanently removed dev") introduced the EEH_DEV_REMOVED flag to
>> prevent such inadvertent rescans on hierarchical toplogies relavent in
>> Baremetal setups. For pSeries, such topologies don't really make sense
>> as the devices are either part of the same PE OR exposed as independent
>> devices on multiple virtual PHBs. However, the inadvertent rescans are
>> still a possibility with either hotplug of a new device or otherwise
>> with manual system-wide pci bus rescan attempts.
>>
>> So the patch checks for EEH_DEV_REMOVED before allowing config space
>> access just like PowerNV, making the PCI core omit the PE, and thus
Also, not sure if this commit description is correct as the patch only
changes behaviour of RTAS config-space accesses and not making changes
for omission semantics.
The existing code in arch/powerpc/kernel/pci_of_scan.c already
suppresses discovery of removed device:
#ifdef CONFIG_EEH
if (edev && (edev->mode & EEH_DEV_REMOVED))
return NULL;
#endif
>> preventing subsequent EEH recurances. The patch is tested on PowerVM
>> and KVM machines with single and multi-function devices, and on the
>> devices behind a switch. The unplug of the affected devices post EEH
>> removal is also working fine as expected.
>>
>> Signed-off-by: Shivaprasad G Bhat <sbhat@linux.ibm.com>
>> References: d2b0f6f77ee5 ("powerpc/eeh: No hotplug on permanently
>> removed dev")
>> ---
>> arch/powerpc/kernel/rtas_pci.c | 6 ++++++
>> 1 file changed, 6 insertions(+)
>>
>> diff --git a/arch/powerpc/kernel/rtas_pci.c b/arch/powerpc/kernel/
>> rtas_pci.c
>> index fccf96e897f6..ce24b18712ca 100644
>> --- a/arch/powerpc/kernel/rtas_pci.c
>> +++ b/arch/powerpc/kernel/rtas_pci.c
>> @@ -57,6 +57,9 @@ int rtas_pci_dn_read_config(struct pci_dn *pdn, int
>> where, int size, u32 *val)
>> if (pdn->edev && pdn->edev->pe &&
>> (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED))
>> return PCIBIOS_SET_FAILED;
>> +
>> + if (pdn->edev && pdn->edev->mode & EEH_DEV_REMOVED)
>
> Consider using paranthesis for (pdn->edev->mode & EEH_DEV_REMOVED) and
> moving to next line for readability (similar to prev one).
>
>> + return PCIBIOS_SET_FAILED;
>
> Why not return PCIBIOS_DEVICE_NOT_FOUND ? Returning SET_FAILED for a
> removed device could be misleading.
>
Also we should check for EEH_DEV_REMOVED before checking for
EEH_PE_CFG_BLOCKED to avoid returning early when the latter is still set
for a removed device.
> Above comments applies to below changes also.
>
>> #endif
>> addr = rtas_config_addr(pdn->busno, pdn->devfn, where);
>> @@ -108,6 +111,9 @@ int rtas_pci_dn_write_config(struct pci_dn *pdn,
>> int where, int size, u32 val)
>> if (pdn->edev && pdn->edev->pe &&
>> (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED))
>> return PCIBIOS_SET_FAILED;
>> +
>> + if (pdn->edev && pdn->edev->mode & EEH_DEV_REMOVED)
>> + return PCIBIOS_SET_FAILED;
>> #endif
>> addr = rtas_config_addr(pdn->busno, pdn->devfn, where);
>>
>>
>>
>
>
^ permalink raw reply
* Re: [PATCH] powerpc/rtas_pci: No hotplug on permanently removed device on pSeries
From: Harsh Prateek Bora @ 2026-05-12 17:17 UTC (permalink / raw)
To: Shivaprasad G Bhat, maddy, linuxppc-dev
Cc: mpe, npiggin, chleroy, linux-kernel
In-Reply-To: <177725851139.12391.5948009745181492600.stgit@linux.ibm.com>
On 27/04/26 8:25 am, Shivaprasad G Bhat wrote:
> The eeh_driver disables and offlines the PE permanently when it
> exceeds the freeze count beyond eeh_max_freeze within the last hour.
> The PE is only offline, so the device tree entries, eeh device
> references are all intact till the real unplug of the device from
> the guest/host takes place.
>
> On pSeries, with a new hotplug of any PCI device, the drmgr initiates
> a system-wide PCI rescan, which finds devices offlined by the eeh_driver
> and there will be attempts to bring them online. This leads to
> recurring EEHs either at the config read time itself or a bit
> later depending on the type of the problem.
>
> For PowerNV, the commit d2b0f6f77ee5 ("powerpc/eeh: No hotplug on
> permanently removed dev") introduced the EEH_DEV_REMOVED flag to
> prevent such inadvertent rescans on hierarchical toplogies relavent in
> Baremetal setups. For pSeries, such topologies don't really make sense
> as the devices are either part of the same PE OR exposed as independent
> devices on multiple virtual PHBs. However, the inadvertent rescans are
> still a possibility with either hotplug of a new device or otherwise
> with manual system-wide pci bus rescan attempts.
>
> So the patch checks for EEH_DEV_REMOVED before allowing config space
> access just like PowerNV, making the PCI core omit the PE, and thus
> preventing subsequent EEH recurances. The patch is tested on PowerVM
> and KVM machines with single and multi-function devices, and on the
> devices behind a switch. The unplug of the affected devices post EEH
> removal is also working fine as expected.
>
> Signed-off-by: Shivaprasad G Bhat <sbhat@linux.ibm.com>
> References: d2b0f6f77ee5 ("powerpc/eeh: No hotplug on permanently removed dev")
> ---
> arch/powerpc/kernel/rtas_pci.c | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/arch/powerpc/kernel/rtas_pci.c b/arch/powerpc/kernel/rtas_pci.c
> index fccf96e897f6..ce24b18712ca 100644
> --- a/arch/powerpc/kernel/rtas_pci.c
> +++ b/arch/powerpc/kernel/rtas_pci.c
> @@ -57,6 +57,9 @@ int rtas_pci_dn_read_config(struct pci_dn *pdn, int where, int size, u32 *val)
> if (pdn->edev && pdn->edev->pe &&
> (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED))
> return PCIBIOS_SET_FAILED;
> +
> + if (pdn->edev && pdn->edev->mode & EEH_DEV_REMOVED)
Consider using paranthesis for (pdn->edev->mode & EEH_DEV_REMOVED) and
moving to next line for readability (similar to prev one).
> + return PCIBIOS_SET_FAILED;
Why not return PCIBIOS_DEVICE_NOT_FOUND ? Returning SET_FAILED for a
removed device could be misleading.
Above comments applies to below changes also.
> #endif
>
> addr = rtas_config_addr(pdn->busno, pdn->devfn, where);
> @@ -108,6 +111,9 @@ int rtas_pci_dn_write_config(struct pci_dn *pdn, int where, int size, u32 val)
> if (pdn->edev && pdn->edev->pe &&
> (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED))
> return PCIBIOS_SET_FAILED;
> +
> + if (pdn->edev && pdn->edev->mode & EEH_DEV_REMOVED)
> + return PCIBIOS_SET_FAILED;
> #endif
>
> addr = rtas_config_addr(pdn->busno, pdn->devfn, where);
>
>
>
^ permalink raw reply
* Re: [PATCH v3] powerpc/audit: Convert powerpc to AUDIT_ARCH_COMPAT_GENERIC
From: Paul Moore @ 2026-05-12 15:58 UTC (permalink / raw)
To: Madhavan Srinivasan, Christophe Leroy (CS GROUP)
Cc: Michael Ellerman, Nicholas Piggin, Eric Paris,
Venkat Rao Bagalkote, linux-kernel, linuxppc-dev, audit,
Thomas Weissschuh, Cédric Le Goater
In-Reply-To: <177562236431.1381144.17164983023348957945.b4-ty@linux.ibm.com>
On Wed, Apr 8, 2026 at 12:28 AM Madhavan Srinivasan <maddy@linux.ibm.com> wrote:
>
> On Tue, 10 Mar 2026 16:08:07 +0100, Christophe Leroy (CS GROUP) wrote:
> > Commit e65e1fc2d24b ("[PATCH] syscall class hookup for all normal
> > targets") added generic support for AUDIT but that didn't include
> > support for bi-arch like powerpc.
> >
> > Commit 4b58841149dc ("audit: Add generic compat syscall support")
> > added generic support for bi-arch.
> >
> > [...]
>
> Applied to powerpc/next.
>
> [1/1] powerpc/audit: Convert powerpc to AUDIT_ARCH_COMPAT_GENERIC
> https://git.kernel.org/powerpc/c/f26ad12356a275ab303d5d3af4790ad94acc20d7
I never saw a follow-up demonstrating that the audit test suite runs
clean with this change.
Madhavan, did you test this before merging?
--
paul-moore.com
^ permalink raw reply
* Re: IBM Power S822LC: pci 0021:0d:00.0: xHCI HW did not halt within 32000 usec status = 0x0
From: Paul Menzel @ 2026-05-12 15:32 UTC (permalink / raw)
To: Michal Pecio
Cc: Mathias Nyman, Greg Kroah-Hartman, linux-usb, LKML, linuxppc-dev,
Benjamin Herrenschmidt
In-Reply-To: <20260512102233.290d3665.michal.pecio@gmail.com>
[Cc: +Benjamin]
Dear Michal,
Thank you for your reply.
Am 12.05.26 um 10:22 schrieb Michal Pecio:
> On Tue, 12 May 2026 08:17:08 +0200, Paul Menzel wrote:>
>>> I honestly don't know what to do with this. I think I would start with
>>> looking whether xhci_shutdown() in the old kernel manages to halt it
>>> successfully or if it also fails, and what's the USBSTS there.
>>>
>>> It seems that you can get such information by enabling dynamic debug
>>>
>>> echo 'module xhci_hcd +p' >/proc/dynamic_debug/control
>>>
>>> and capturing old kernel's log up to kexec() through a serial cable.
>>
>> Unfortunately, nothing is logged over the serial console (BMC SOL) after
>> running `sudo kexec -e` or `sudo systemctl reboot`. I just see:
>>
>> [69530.180531343,5] OPAL: Switch to big-endian OS
>> [69538.407292205,5] OPAL: Switch to little-endian OS
>>
>> Which is the OPAL firmware, so it might be involved? No idea, if it
>> touches the xHCI controller.
>
> So some FW involvement is potentially possible.
>
> BTW, another method of doing kexec is to setup a crash kernel and
> then trigger panic with /proc/sysrq-trigger.
>
> This probably won't run xhci_shutdown(). Not sure about OPAL FW.
> Is the outcome any different?
Is the motivation to try to not get the OPAL message to rule out any
involvement.
I have to check, how to set the crash kernel up.
>> But strangely no xHCI messages are there – also after booting with
>> Petitboot and initialized xHCI controller? No idea, if it points to,
>> that during kexec or shutdown nothing is power off?
>>
>> With `sudo systemctl reboot` only the line below are logged:
>>
>> [ 121.811384] libvirt-guests.sh[3366]: Running guests on default URI:
>> [ 121.811988] libvirt-guests.sh[3376]: no running guests.
>> [ … (systemd service stop notifications)]
>> [ 136.254846] systemd-shutdown[1]: Waiting for process: watch_ldconfig
>> [ 218.549684] reboot: Restarting system
>> [69760.484679183,5] OPAL: Reboot request...
>> 3.55778|Ignoring boot flags, incorrect version 0x0
>> 3.59881|ISTEP 6. 3
>
> Only "reboot: Restarting system" looks like it's kernel. Maybe you need
> to tweak loglevel before rebooting or kexecing? Try to get more kernel
> messages showing over serial during operation, then kexec.
I actually did set the log level by adding `debug` to the Linux kernel
command line, and with
$ echo 9 | sudo tee /proc/sysrq-trigger
9
and it was confirmed:
sysrq: Changing Loglevel
sysrq: Loglevel set to 9
Unfortunately, no more messages.
As a further data point, adding `ppc_pci_reset_phbs` to the command line
also gets xhci_hcd to initialize the TI xHCI host controller:
$ lspci -nn -s 0021:0d:00.0
0021:0d:00.0 USB controller [0c03]: Texas Instruments TUSB73x0
SuperSpeed USB 3.0 xHCI Host Controller [104c:8241] (rev 02)
[ 14.050249] Issue PHB reset ...
[…]
[ 19.339822] ehci_hcd: block sizes: qh 144 qtd 96 itd 192 sitd 96
[ 19.339919] ohci_hcd: block sizes: ed 112 td 96
[ 19.340538] xhci_hcd 0021:0d:00.0: xHCI Host Controller
No log `xHCI HW did not halt within 32000 usec status = 0x0` (or 0x10
with the other patch). In `arch/powerpc/platforms/powernv/pci-ioda.c`,
reading the comment in `pnv_pci_init_ioda_phb()` suggests, that PHB
should be reset also in the kexec case:
/*
* If we're running in kdump kernel, the previous kernel never
* shutdown PCI devices correctly. We already got IODA table
* cleaned out. So we have to issue PHB reset to stop all PCI
* transactions from previous kernel. The ppc_pci_reset_phbs
* kernel parameter will force this reset too. Additionally,
* if the IODA reset above failed then use a bigger hammer.
* This can happen if we get a PHB fatal error in very early
* boot.
*/
if (is_kdump_kernel() || pci_reset_phbs || rc) {
pr_info(" Issue PHB reset ...\n");
pnv_eeh_phb_reset(hose, EEH_RESET_FUNDAMENTAL);
pnv_eeh_phb_reset(hose, EEH_RESET_DEACTIVATE);
}
At least, I’d assume that kdump and kexec are similar, that both do not
shut down PCI devices? (Commit 361f2a2a1536 (powrpc/powernv: Reset PHB
in kdump kernel) from 2024 adds (some) the code above.)
Kind regards,
Paul
^ permalink raw reply
* Re: [PATCH v2] drivers/base/memory: make memory block get/put explicit
From: Mike Rapoport @ 2026-05-12 12:24 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, linux-mm,
driver-core, Oscar Salvador, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Danilo Krummrich, Rafael J . Wysocki, linux-kernel, linux-cxl,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy, Heiko Carstens, Vasily Gorbik,
Alexander Gordeev, Christian Borntraeger, Sven Schnelle,
Sumanth Korikkar, Kees Cook, Douglas Anderson, Donet Tom,
muchun.song
In-Reply-To: <20260512072635.3969576-1-songmuchun@bytedance.com>
On Tue, May 12, 2026 at 03:26:35PM +0800, Muchun Song wrote:
> Rename the memory block lookup helper to make the acquired reference
> explicit, add memory_block_put() to wrap put_device(), remove
> find_memory_block(), and use memory_block_get() as the single block-id
> based lookup interface.
>
> This makes it clearer to callers that a successful lookup holds a
> reference that must be dropped, reducing the chance of forgetting the
> matching put and leaking the memory block device reference.
>
> Link: https://lore.kernel.org/linux-mm/7887915D-E598-42B3-9AFE-BFFBACE8DE2D@linux.dev/#t
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Acked-by: Oscar Salvador <osalvador@suse.de>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Acked-by: Michal Hocko <mhocko@suse.com>
> Tested-by: Donet Tom <donettom@linux.ibm.com>
> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
> Changes in v2:
> - mention the removal of find_memory_block() in the commit message
> - drop the redundant extern from the memory_block_get() declaration
> ---
> .../platforms/pseries/hotplug-memory.c | 14 ++-----
> drivers/base/memory.c | 38 +++++++------------
> drivers/base/node.c | 4 +-
> drivers/s390/char/sclp_mem.c | 17 ++++-----
> include/linux/memory.h | 7 +++-
> mm/memory_hotplug.c | 5 +--
> 6 files changed, 35 insertions(+), 50 deletions(-)
--
Sincerely yours,
Mike.
^ permalink raw reply
* Re: [PATCH v2] drivers/base/memory: make memory block get/put explicit
From: Richard Cheng @ 2026-05-12 12:03 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, linux-mm,
driver-core, Oscar Salvador, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Danilo Krummrich, Rafael J . Wysocki, linux-kernel, linux-cxl,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy, Heiko Carstens, Vasily Gorbik,
Alexander Gordeev, Christian Borntraeger, Sven Schnelle,
Sumanth Korikkar, Kees Cook, Douglas Anderson, Donet Tom,
muchun.song
In-Reply-To: <20260512072635.3969576-1-songmuchun@bytedance.com>
On Tue, May 12, 2026 at 03:26:35PM +0800, Muchun Song wrote:
> Rename the memory block lookup helper to make the acquired reference
> explicit, add memory_block_put() to wrap put_device(), remove
> find_memory_block(), and use memory_block_get() as the single block-id
> based lookup interface.
>
> This makes it clearer to callers that a successful lookup holds a
> reference that must be dropped, reducing the chance of forgetting the
> matching put and leaking the memory block device reference.
>
> Link: https://lore.kernel.org/linux-mm/7887915D-E598-42B3-9AFE-BFFBACE8DE2D@linux.dev/#t
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Acked-by: Oscar Salvador <osalvador@suse.de>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Acked-by: Michal Hocko <mhocko@suse.com>
> Tested-by: Donet Tom <donettom@linux.ibm.com>
> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> Changes in v2:
> - mention the removal of find_memory_block() in the commit message
> - drop the redundant extern from the memory_block_get() declaration
> ---
> .../platforms/pseries/hotplug-memory.c | 14 ++-----
> drivers/base/memory.c | 38 +++++++------------
> drivers/base/node.c | 4 +-
> drivers/s390/char/sclp_mem.c | 17 ++++-----
> include/linux/memory.h | 7 +++-
> mm/memory_hotplug.c | 5 +--
> 6 files changed, 35 insertions(+), 50 deletions(-)
>
> diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c
> index 75f85a5da981..94f3b57054b6 100644
> --- a/arch/powerpc/platforms/pseries/hotplug-memory.c
> +++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
> @@ -164,13 +164,7 @@ static int update_lmb_associativity_index(struct drmem_lmb *lmb)
>
> static struct memory_block *lmb_to_memblock(struct drmem_lmb *lmb)
> {
> - unsigned long section_nr;
> - struct memory_block *mem_block;
> -
> - section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr));
> -
> - mem_block = find_memory_block(section_nr);
> - return mem_block;
> + return memory_block_get(phys_to_block_id(lmb->base_addr));
> }
>
> static int get_lmb_range(u32 drc_index, int n_lmbs,
> @@ -220,7 +214,7 @@ static int dlpar_change_lmb_state(struct drmem_lmb *lmb, bool online)
> else
> rc = 0;
>
> - put_device(&mem_block->dev);
> + memory_block_put(mem_block);
>
> return rc;
> }
> @@ -319,12 +313,12 @@ static int dlpar_remove_lmb(struct drmem_lmb *lmb)
>
> rc = dlpar_offline_lmb(lmb);
> if (rc) {
> - put_device(&mem_block->dev);
> + memory_block_put(mem_block);
> return rc;
> }
>
> __remove_memory(lmb->base_addr, memory_block_size);
> - put_device(&mem_block->dev);
> + memory_block_put(mem_block);
>
> /* Update memory regions for memory remove */
> memblock_remove(lmb->base_addr, memory_block_size);
> diff --git a/drivers/base/memory.c b/drivers/base/memory.c
> index 11d57cfa8d72..5b5d41089e81 100644
> --- a/drivers/base/memory.c
> +++ b/drivers/base/memory.c
> @@ -649,7 +649,7 @@ int __weak arch_get_memory_phys_device(unsigned long start_pfn)
> *
> * Called under device_hotplug_lock.
> */
> -struct memory_block *find_memory_block_by_id(unsigned long block_id)
> +struct memory_block *memory_block_get(unsigned long block_id)
> {
> struct memory_block *mem;
>
> @@ -659,16 +659,6 @@ struct memory_block *find_memory_block_by_id(unsigned long block_id)
> return mem;
> }
>
> -/*
> - * Called under device_hotplug_lock.
> - */
> -struct memory_block *find_memory_block(unsigned long section_nr)
> -{
> - unsigned long block_id = memory_block_id(section_nr);
> -
> - return find_memory_block_by_id(block_id);
> -}
> -
> static struct attribute *memory_memblk_attrs[] = {
> &dev_attr_phys_index.attr,
> &dev_attr_state.attr,
> @@ -701,7 +691,7 @@ static int __add_memory_block(struct memory_block *memory)
>
> ret = device_register(&memory->dev);
> if (ret) {
> - put_device(&memory->dev);
> + memory_block_put(memory);
> return ret;
> }
> ret = xa_err(xa_store(&memory_blocks, memory->dev.id, memory,
> @@ -795,9 +785,9 @@ static int add_memory_block(unsigned long block_id, int nid, unsigned long state
> struct memory_block *mem;
> int ret = 0;
>
> - mem = find_memory_block_by_id(block_id);
> + mem = memory_block_get(block_id);
> if (mem) {
> - put_device(&mem->dev);
> + memory_block_put(mem);
> return -EEXIST;
> }
> mem = kzalloc_obj(*mem);
> @@ -845,8 +835,8 @@ static void remove_memory_block(struct memory_block *memory)
> memory->group = NULL;
> }
>
> - /* drop the ref. we got via find_memory_block() */
> - put_device(&memory->dev);
> + /* drop the ref. we got via memory_block_get() */
> + memory_block_put(memory);
> device_unregister(&memory->dev);
> }
>
> @@ -880,7 +870,7 @@ int create_memory_block_devices(unsigned long start, unsigned long size,
> end_block_id = block_id;
> for (block_id = start_block_id; block_id != end_block_id;
> block_id++) {
> - mem = find_memory_block_by_id(block_id);
> + mem = memory_block_get(block_id);
> if (WARN_ON_ONCE(!mem))
> continue;
> remove_memory_block(mem);
> @@ -908,7 +898,7 @@ void remove_memory_block_devices(unsigned long start, unsigned long size)
> return;
>
> for (block_id = start_block_id; block_id != end_block_id; block_id++) {
> - mem = find_memory_block_by_id(block_id);
> + mem = memory_block_get(block_id);
> if (WARN_ON_ONCE(!mem))
> continue;
> num_poisoned_pages_sub(-1UL, memblk_nr_poison(mem));
> @@ -1015,12 +1005,12 @@ int walk_memory_blocks(unsigned long start, unsigned long size,
> return 0;
>
> for (block_id = start_block_id; block_id <= end_block_id; block_id++) {
> - mem = find_memory_block_by_id(block_id);
> + mem = memory_block_get(block_id);
> if (!mem)
> continue;
>
> ret = func(mem, arg);
> - put_device(&mem->dev);
> + memory_block_put(mem);
> if (ret)
> break;
> }
> @@ -1228,22 +1218,22 @@ int walk_dynamic_memory_groups(int nid, walk_memory_groups_func_t func,
> void memblk_nr_poison_inc(unsigned long pfn)
> {
> const unsigned long block_id = pfn_to_block_id(pfn);
> - struct memory_block *mem = find_memory_block_by_id(block_id);
> + struct memory_block *mem = memory_block_get(block_id);
>
> if (mem) {
> atomic_long_inc(&mem->nr_hwpoison);
> - put_device(&mem->dev);
> + memory_block_put(mem);
> }
> }
>
> void memblk_nr_poison_sub(unsigned long pfn, long i)
> {
> const unsigned long block_id = pfn_to_block_id(pfn);
> - struct memory_block *mem = find_memory_block_by_id(block_id);
> + struct memory_block *mem = memory_block_get(block_id);
>
> if (mem) {
> atomic_long_sub(i, &mem->nr_hwpoison);
> - put_device(&mem->dev);
> + memory_block_put(mem);
> }
> }
>
> diff --git a/drivers/base/node.c b/drivers/base/node.c
> index 126f66aa2c3e..b3333ca92090 100644
> --- a/drivers/base/node.c
> +++ b/drivers/base/node.c
> @@ -847,13 +847,13 @@ static void register_memory_blocks_under_nodes(void)
> for (block_id = start_block_id; block_id <= end_block_id; block_id++) {
> struct memory_block *mem;
>
> - mem = find_memory_block_by_id(block_id);
> + mem = memory_block_get(block_id);
> if (!mem)
> continue;
>
> memory_block_add_nid_early(mem, nid);
> do_register_memory_block_under_node(nid, mem);
> - put_device(&mem->dev);
> + memory_block_put(mem);
> }
>
> }
> diff --git a/drivers/s390/char/sclp_mem.c b/drivers/s390/char/sclp_mem.c
> index 78c054e26d17..6df1926d4c62 100644
> --- a/drivers/s390/char/sclp_mem.c
> +++ b/drivers/s390/char/sclp_mem.c
> @@ -204,7 +204,7 @@ static ssize_t sclp_config_mem_store(struct kobject *kobj, struct kobj_attribute
> addr = sclp_mem->id * block_size;
> /*
> * Hold device_hotplug_lock when adding/removing memory blocks.
> - * Additionally, also protect calls to find_memory_block() and
> + * Additionally, also protect calls to memory_block_get() and
> * sclp_attach_storage().
> */
> rc = lock_device_hotplug_sysfs();
> @@ -231,20 +231,19 @@ static ssize_t sclp_config_mem_store(struct kobject *kobj, struct kobj_attribute
> sclp_mem_change_state(addr, block_size, 0);
> goto out_unlock;
> }
> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(addr)));
> - put_device(&mem->dev);
> + mem = memory_block_get(phys_to_block_id(addr));
> + memory_block_put(mem);
> WRITE_ONCE(sclp_mem->config, 1);
> } else {
> if (!sclp_mem->config)
> goto out_unlock;
> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(addr)));
> + mem = memory_block_get(phys_to_block_id(addr));
> if (mem->state != MEM_OFFLINE) {
> - put_device(&mem->dev);
> + memory_block_put(mem);
> rc = -EBUSY;
> goto out_unlock;
> }
> - /* drop the ref just got via find_memory_block() */
> - put_device(&mem->dev);
> + memory_block_put(mem);
> sclp_mem_change_state(addr, block_size, 0);
> __remove_memory(addr, block_size);
> #ifdef CONFIG_KASAN
> @@ -294,11 +293,11 @@ static ssize_t sclp_memmap_on_memory_store(struct kobject *kobj, struct kobj_att
> return rc;
> block_size = memory_block_size_bytes();
> sclp_mem = container_of(kobj, struct sclp_mem, kobj);
> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(sclp_mem->id * block_size)));
> + mem = memory_block_get(phys_to_block_id(sclp_mem->id * block_size));
> if (!mem) {
> WRITE_ONCE(sclp_mem->memmap_on_memory, value);
> } else {
> - put_device(&mem->dev);
> + memory_block_put(mem);
> rc = -EBUSY;
> }
> unlock_device_hotplug();
> diff --git a/include/linux/memory.h b/include/linux/memory.h
> index 5bb5599c6b2b..463dc02f6cff 100644
> --- a/include/linux/memory.h
> +++ b/include/linux/memory.h
> @@ -158,7 +158,11 @@ int create_memory_block_devices(unsigned long start, unsigned long size,
> void remove_memory_block_devices(unsigned long start, unsigned long size);
> extern void memory_dev_init(void);
> extern int memory_notify(enum memory_block_state state, void *v);
> -extern struct memory_block *find_memory_block(unsigned long section_nr);
> +struct memory_block *memory_block_get(unsigned long block_id);
> +static inline void memory_block_put(struct memory_block *mem)
> +{
> + put_device(&mem->dev);
> +}
Hi Muchun,
Thanks for the work, I have a small suggestion if that fits your thought.
I think we should at least add a comment above memory_block_put() to remind the caller to check
for the availabitliy of "mem" before calling this function.
We perform the check in memory_block_get() inside the function body, I see different usage pattern
across the caller when they're dealing with "mem == NULL" and avoid to call memory_block_put(),
I can understand we should leverage the check to caller, not inside memory_block_put().
But just in case the next caller might forgot to do the check or think the behavior might be symmetric
bettween memory_block_get() and memory_block_put(), a comment above the function would be nice.
Best regards,
Richard Cheng.
> typedef int (*walk_memory_blocks_func_t)(struct memory_block *, void *);
> extern int walk_memory_blocks(unsigned long start, unsigned long size,
> void *arg, walk_memory_blocks_func_t func);
> @@ -171,7 +175,6 @@ struct memory_group *memory_group_find_by_id(int mgid);
> typedef int (*walk_memory_groups_func_t)(struct memory_group *, void *);
> int walk_dynamic_memory_groups(int nid, walk_memory_groups_func_t func,
> struct memory_group *excluded, void *arg);
> -struct memory_block *find_memory_block_by_id(unsigned long block_id);
> #define hotplug_memory_notifier(fn, pri) ({ \
> static __meminitdata struct notifier_block fn##_mem_nb =\
> { .notifier_call = fn, .priority = pri };\
> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
> index 462d8dcd636d..890c6453e887 100644
> --- a/mm/memory_hotplug.c
> +++ b/mm/memory_hotplug.c
> @@ -1417,14 +1417,13 @@ static void remove_memory_blocks_and_altmaps(u64 start, u64 size)
> struct vmem_altmap *altmap = NULL;
> struct memory_block *mem;
>
> - mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(cur_start)));
> + mem = memory_block_get(phys_to_block_id(cur_start));
> if (WARN_ON_ONCE(!mem))
> continue;
>
> altmap = mem->altmap;
> mem->altmap = NULL;
> - /* drop the ref. we got via find_memory_block() */
> - put_device(&mem->dev);
> + memory_block_put(mem);
>
> remove_memory_block_devices(cur_start, memblock_size);
>
>
> base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
> --
> 2.54.0
>
>
^ permalink raw reply
* Re: IBM Power S822LC: pci 0021:0d:00.0: xHCI HW did not halt within 32000 usec status = 0x0
From: Michal Pecio @ 2026-05-11 23:20 UTC (permalink / raw)
To: Paul Menzel
Cc: Mathias Nyman, Greg Kroah-Hartman, linux-usb, LKML, linuxppc-dev
In-Reply-To: <69fa1c3f-3ea9-42cb-a49a-7da39f72806e@molgen.mpg.de>
On Mon, 11 May 2026 23:57:33 +0200, Paul Menzel wrote:
> Am 06.05.26 um 19:30 schrieb Michal Pecio:
> > On Wed, 6 May 2026 18:06:20 +0200, Paul Menzel wrote:
> >> On the IBM Power S822LC (8335-GCA POWER8), rebooting into Linux 7.1-rc2+
> >> with kexec results in the warning below:
> >>
> >> [ 0.000000] Linux version 7.1.0-rc2+ (x@b) (gcc (Ubuntu 11.2.0-7ubuntu2) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.37) #3 SMP PREEMPT Wed May 6 08:50:5
> >> […]
> >> [ 0.000000] Hardware name: 8335-GCA POWER8 (raw) 0x4d0200 opal:skiboot-5.4.8-5787ad3 PowerNV
> >> […]
> >> [ 1.593760] NET: Registered PF_UNIX/PF_LOCAL protocol family
> >> [ 1.593859] pci 0021:0d:00.0: enabling device (0140 -> 0142)
> >> [ 1.627080] pci 0021:0d:00.0: xHCI HW did not halt within 32000 usec status = 0x0
> >> [ 1.627094] pci 0021:0d:00.0: quirk_usb_early_handoff+0x0/0x300 took 32465 usecs
> >> [ 1.627123] PCI: CLS 0 bytes, default 128
>
> > Does it work any better if kexecing other kernel versions?
>
> No, the problem goes as far back as 5.17-rc7. (I didn’t try anything
> before.)
>
> > What if you increase XHCI_MAX_HALT_USEC by 10* or 100* ?
>
> I have to test this.
I missed your dmesg attachment previously.
This may not help if another halt attempt 200ms later fails too.
Per spec (5.4.1.1), the HC is supposed to complete halt in 16ms.
> > Does the controller work normally after this warning?
> It does not look like it. In the log attached to my report, later on
> there is:
>
> [ 1.739374] xhci_hcd 0021:0d:00.0: xHCI Host Controller
> [ 1.739431] xhci_hcd 0021:0d:00.0: new USB bus registered,
> assigned bus number 1
> [ 1.794727] Freeing initrd memory: 52928K
> [ 1.801984] xhci_hcd 0021:0d:00.0: Host halt failed, -110
> [ 1.801988] xhci_hcd 0021:0d:00.0: can't setup: -110
> [ 1.802137] xhci_hcd 0021:0d:00.0: USB bus 1 deregistered
> [ 1.802154] xhci_hcd 0021:0d:00.0: init 0021:0d:00.0 fail, -110
> [ 1.802250] xhci_hcd 0021:0d:00.0: probe with driver xhci_hcd
> failed with error -110
Right, this chip seems stuck and the driver fails to reinitialize it.
> PS: Claude Sonnet 4.6 cooked up the attached patch, which does *not*
> help though, but does get it to the return code 0x10, which Claude
> replied to with:
>
> > ● The status change 0x0 → 0x10 is meaningful: 0x10 is PCD (Port Change Detect, bit 4),
> > HCHalted=0. The old-kernel reset (from our commit) did take effect …
Do you mean that running xhci_reset() before kexec() causes the new
kernel to see 0x10 instead of 0x0 in the status register? Is this
reproducible, not random or a one time fluke?
A little odd, one could expect reset to have the opposite effect.
Is there truly some machine firmware running during kexec() and using
the HC, as your LLM says?
I honestly don't know what to do with this. I think I would start with
looking whether xhci_shutdown() in the old kernel manages to halt it
successfully or if it also fails, and what's the USBSTS there.
It seems that you can get such information by enabling dynamic debug
echo 'module xhci_hcd +p' >/proc/dynamic_debug/control
and capcturing old kernel's log up to kexec() through a serial cable.
Regards,
Michal
^ permalink raw reply
* [PATCH v2] drivers/base/memory: make memory block get/put explicit
From: Muchun Song @ 2026-05-12 7:26 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Greg Kroah-Hartman, linux-mm,
driver-core
Cc: Oscar Salvador, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Danilo Krummrich, Rafael J . Wysocki, linux-kernel, linux-cxl,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy, Heiko Carstens, Vasily Gorbik,
Alexander Gordeev, Christian Borntraeger, Sven Schnelle,
Sumanth Korikkar, Kees Cook, Douglas Anderson, Muchun Song,
Donet Tom, muchun.song
Rename the memory block lookup helper to make the acquired reference
explicit, add memory_block_put() to wrap put_device(), remove
find_memory_block(), and use memory_block_get() as the single block-id
based lookup interface.
This makes it clearer to callers that a successful lookup holds a
reference that must be dropped, reducing the chance of forgetting the
matching put and leaking the memory block device reference.
Link: https://lore.kernel.org/linux-mm/7887915D-E598-42B3-9AFE-BFFBACE8DE2D@linux.dev/#t
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Oscar Salvador <osalvador@suse.de>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Acked-by: Michal Hocko <mhocko@suse.com>
Tested-by: Donet Tom <donettom@linux.ibm.com>
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
---
Changes in v2:
- mention the removal of find_memory_block() in the commit message
- drop the redundant extern from the memory_block_get() declaration
---
.../platforms/pseries/hotplug-memory.c | 14 ++-----
drivers/base/memory.c | 38 +++++++------------
drivers/base/node.c | 4 +-
drivers/s390/char/sclp_mem.c | 17 ++++-----
include/linux/memory.h | 7 +++-
mm/memory_hotplug.c | 5 +--
6 files changed, 35 insertions(+), 50 deletions(-)
diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c
index 75f85a5da981..94f3b57054b6 100644
--- a/arch/powerpc/platforms/pseries/hotplug-memory.c
+++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
@@ -164,13 +164,7 @@ static int update_lmb_associativity_index(struct drmem_lmb *lmb)
static struct memory_block *lmb_to_memblock(struct drmem_lmb *lmb)
{
- unsigned long section_nr;
- struct memory_block *mem_block;
-
- section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr));
-
- mem_block = find_memory_block(section_nr);
- return mem_block;
+ return memory_block_get(phys_to_block_id(lmb->base_addr));
}
static int get_lmb_range(u32 drc_index, int n_lmbs,
@@ -220,7 +214,7 @@ static int dlpar_change_lmb_state(struct drmem_lmb *lmb, bool online)
else
rc = 0;
- put_device(&mem_block->dev);
+ memory_block_put(mem_block);
return rc;
}
@@ -319,12 +313,12 @@ static int dlpar_remove_lmb(struct drmem_lmb *lmb)
rc = dlpar_offline_lmb(lmb);
if (rc) {
- put_device(&mem_block->dev);
+ memory_block_put(mem_block);
return rc;
}
__remove_memory(lmb->base_addr, memory_block_size);
- put_device(&mem_block->dev);
+ memory_block_put(mem_block);
/* Update memory regions for memory remove */
memblock_remove(lmb->base_addr, memory_block_size);
diff --git a/drivers/base/memory.c b/drivers/base/memory.c
index 11d57cfa8d72..5b5d41089e81 100644
--- a/drivers/base/memory.c
+++ b/drivers/base/memory.c
@@ -649,7 +649,7 @@ int __weak arch_get_memory_phys_device(unsigned long start_pfn)
*
* Called under device_hotplug_lock.
*/
-struct memory_block *find_memory_block_by_id(unsigned long block_id)
+struct memory_block *memory_block_get(unsigned long block_id)
{
struct memory_block *mem;
@@ -659,16 +659,6 @@ struct memory_block *find_memory_block_by_id(unsigned long block_id)
return mem;
}
-/*
- * Called under device_hotplug_lock.
- */
-struct memory_block *find_memory_block(unsigned long section_nr)
-{
- unsigned long block_id = memory_block_id(section_nr);
-
- return find_memory_block_by_id(block_id);
-}
-
static struct attribute *memory_memblk_attrs[] = {
&dev_attr_phys_index.attr,
&dev_attr_state.attr,
@@ -701,7 +691,7 @@ static int __add_memory_block(struct memory_block *memory)
ret = device_register(&memory->dev);
if (ret) {
- put_device(&memory->dev);
+ memory_block_put(memory);
return ret;
}
ret = xa_err(xa_store(&memory_blocks, memory->dev.id, memory,
@@ -795,9 +785,9 @@ static int add_memory_block(unsigned long block_id, int nid, unsigned long state
struct memory_block *mem;
int ret = 0;
- mem = find_memory_block_by_id(block_id);
+ mem = memory_block_get(block_id);
if (mem) {
- put_device(&mem->dev);
+ memory_block_put(mem);
return -EEXIST;
}
mem = kzalloc_obj(*mem);
@@ -845,8 +835,8 @@ static void remove_memory_block(struct memory_block *memory)
memory->group = NULL;
}
- /* drop the ref. we got via find_memory_block() */
- put_device(&memory->dev);
+ /* drop the ref. we got via memory_block_get() */
+ memory_block_put(memory);
device_unregister(&memory->dev);
}
@@ -880,7 +870,7 @@ int create_memory_block_devices(unsigned long start, unsigned long size,
end_block_id = block_id;
for (block_id = start_block_id; block_id != end_block_id;
block_id++) {
- mem = find_memory_block_by_id(block_id);
+ mem = memory_block_get(block_id);
if (WARN_ON_ONCE(!mem))
continue;
remove_memory_block(mem);
@@ -908,7 +898,7 @@ void remove_memory_block_devices(unsigned long start, unsigned long size)
return;
for (block_id = start_block_id; block_id != end_block_id; block_id++) {
- mem = find_memory_block_by_id(block_id);
+ mem = memory_block_get(block_id);
if (WARN_ON_ONCE(!mem))
continue;
num_poisoned_pages_sub(-1UL, memblk_nr_poison(mem));
@@ -1015,12 +1005,12 @@ int walk_memory_blocks(unsigned long start, unsigned long size,
return 0;
for (block_id = start_block_id; block_id <= end_block_id; block_id++) {
- mem = find_memory_block_by_id(block_id);
+ mem = memory_block_get(block_id);
if (!mem)
continue;
ret = func(mem, arg);
- put_device(&mem->dev);
+ memory_block_put(mem);
if (ret)
break;
}
@@ -1228,22 +1218,22 @@ int walk_dynamic_memory_groups(int nid, walk_memory_groups_func_t func,
void memblk_nr_poison_inc(unsigned long pfn)
{
const unsigned long block_id = pfn_to_block_id(pfn);
- struct memory_block *mem = find_memory_block_by_id(block_id);
+ struct memory_block *mem = memory_block_get(block_id);
if (mem) {
atomic_long_inc(&mem->nr_hwpoison);
- put_device(&mem->dev);
+ memory_block_put(mem);
}
}
void memblk_nr_poison_sub(unsigned long pfn, long i)
{
const unsigned long block_id = pfn_to_block_id(pfn);
- struct memory_block *mem = find_memory_block_by_id(block_id);
+ struct memory_block *mem = memory_block_get(block_id);
if (mem) {
atomic_long_sub(i, &mem->nr_hwpoison);
- put_device(&mem->dev);
+ memory_block_put(mem);
}
}
diff --git a/drivers/base/node.c b/drivers/base/node.c
index 126f66aa2c3e..b3333ca92090 100644
--- a/drivers/base/node.c
+++ b/drivers/base/node.c
@@ -847,13 +847,13 @@ static void register_memory_blocks_under_nodes(void)
for (block_id = start_block_id; block_id <= end_block_id; block_id++) {
struct memory_block *mem;
- mem = find_memory_block_by_id(block_id);
+ mem = memory_block_get(block_id);
if (!mem)
continue;
memory_block_add_nid_early(mem, nid);
do_register_memory_block_under_node(nid, mem);
- put_device(&mem->dev);
+ memory_block_put(mem);
}
}
diff --git a/drivers/s390/char/sclp_mem.c b/drivers/s390/char/sclp_mem.c
index 78c054e26d17..6df1926d4c62 100644
--- a/drivers/s390/char/sclp_mem.c
+++ b/drivers/s390/char/sclp_mem.c
@@ -204,7 +204,7 @@ static ssize_t sclp_config_mem_store(struct kobject *kobj, struct kobj_attribute
addr = sclp_mem->id * block_size;
/*
* Hold device_hotplug_lock when adding/removing memory blocks.
- * Additionally, also protect calls to find_memory_block() and
+ * Additionally, also protect calls to memory_block_get() and
* sclp_attach_storage().
*/
rc = lock_device_hotplug_sysfs();
@@ -231,20 +231,19 @@ static ssize_t sclp_config_mem_store(struct kobject *kobj, struct kobj_attribute
sclp_mem_change_state(addr, block_size, 0);
goto out_unlock;
}
- mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(addr)));
- put_device(&mem->dev);
+ mem = memory_block_get(phys_to_block_id(addr));
+ memory_block_put(mem);
WRITE_ONCE(sclp_mem->config, 1);
} else {
if (!sclp_mem->config)
goto out_unlock;
- mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(addr)));
+ mem = memory_block_get(phys_to_block_id(addr));
if (mem->state != MEM_OFFLINE) {
- put_device(&mem->dev);
+ memory_block_put(mem);
rc = -EBUSY;
goto out_unlock;
}
- /* drop the ref just got via find_memory_block() */
- put_device(&mem->dev);
+ memory_block_put(mem);
sclp_mem_change_state(addr, block_size, 0);
__remove_memory(addr, block_size);
#ifdef CONFIG_KASAN
@@ -294,11 +293,11 @@ static ssize_t sclp_memmap_on_memory_store(struct kobject *kobj, struct kobj_att
return rc;
block_size = memory_block_size_bytes();
sclp_mem = container_of(kobj, struct sclp_mem, kobj);
- mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(sclp_mem->id * block_size)));
+ mem = memory_block_get(phys_to_block_id(sclp_mem->id * block_size));
if (!mem) {
WRITE_ONCE(sclp_mem->memmap_on_memory, value);
} else {
- put_device(&mem->dev);
+ memory_block_put(mem);
rc = -EBUSY;
}
unlock_device_hotplug();
diff --git a/include/linux/memory.h b/include/linux/memory.h
index 5bb5599c6b2b..463dc02f6cff 100644
--- a/include/linux/memory.h
+++ b/include/linux/memory.h
@@ -158,7 +158,11 @@ int create_memory_block_devices(unsigned long start, unsigned long size,
void remove_memory_block_devices(unsigned long start, unsigned long size);
extern void memory_dev_init(void);
extern int memory_notify(enum memory_block_state state, void *v);
-extern struct memory_block *find_memory_block(unsigned long section_nr);
+struct memory_block *memory_block_get(unsigned long block_id);
+static inline void memory_block_put(struct memory_block *mem)
+{
+ put_device(&mem->dev);
+}
typedef int (*walk_memory_blocks_func_t)(struct memory_block *, void *);
extern int walk_memory_blocks(unsigned long start, unsigned long size,
void *arg, walk_memory_blocks_func_t func);
@@ -171,7 +175,6 @@ struct memory_group *memory_group_find_by_id(int mgid);
typedef int (*walk_memory_groups_func_t)(struct memory_group *, void *);
int walk_dynamic_memory_groups(int nid, walk_memory_groups_func_t func,
struct memory_group *excluded, void *arg);
-struct memory_block *find_memory_block_by_id(unsigned long block_id);
#define hotplug_memory_notifier(fn, pri) ({ \
static __meminitdata struct notifier_block fn##_mem_nb =\
{ .notifier_call = fn, .priority = pri };\
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 462d8dcd636d..890c6453e887 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1417,14 +1417,13 @@ static void remove_memory_blocks_and_altmaps(u64 start, u64 size)
struct vmem_altmap *altmap = NULL;
struct memory_block *mem;
- mem = find_memory_block(pfn_to_section_nr(PFN_DOWN(cur_start)));
+ mem = memory_block_get(phys_to_block_id(cur_start));
if (WARN_ON_ONCE(!mem))
continue;
altmap = mem->altmap;
mem->altmap = NULL;
- /* drop the ref. we got via find_memory_block() */
- put_device(&mem->dev);
+ memory_block_put(mem);
remove_memory_block_devices(cur_start, memblock_size);
base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox