* [PATCH v6 04/21] RISC-V: Define indirect CSR access helpers
From: Atish Patra @ 2026-06-09 6:01 UTC (permalink / raw)
To: James Clark, Rob Herring, Atish Patra, Arnaldo Carvalho de Melo,
Jiri Olsa, Will Deacon, Mark Rutland, Anup Patel, Namhyung Kim,
Paul Walmsley, Krzysztof Kozlowski, Ian Rogers
Cc: linux-riscv, linux-kernel, linux-perf-users, Conor Dooley,
devicetree, linux-arm-kernel
In-Reply-To: <20260608-counter_delegation-v6-0-285b72ed65a9@meta.com>
From: Atish Patra <atishp@rivosinc.com>
The indriect CSR requires multiple instructions to read/write CSR.
Add a few helper functions for ease of usage.
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
arch/riscv/include/asm/csr_ind.h | 44 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/arch/riscv/include/asm/csr_ind.h b/arch/riscv/include/asm/csr_ind.h
new file mode 100644
index 000000000000..6fd7d44dc640
--- /dev/null
+++ b/arch/riscv/include/asm/csr_ind.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Rivos Inc.
+ */
+
+#ifndef _ASM_RISCV_CSR_IND_H
+#define _ASM_RISCV_CSR_IND_H
+
+#include <linux/irqflags.h>
+
+#include <asm/csr.h>
+
+#define csr_ind_read(iregcsr, iselbase, iseloff) ({ \
+ unsigned long __value = 0; \
+ unsigned long __flags; \
+ local_irq_save(__flags); \
+ csr_write(CSR_ISELECT, (iselbase) + (iseloff)); \
+ __value = csr_read(iregcsr); \
+ local_irq_restore(__flags); \
+ __value; \
+})
+
+#define csr_ind_write(iregcsr, iselbase, iseloff, value) ({ \
+ unsigned long __flags; \
+ local_irq_save(__flags); \
+ csr_write(CSR_ISELECT, (iselbase) + (iseloff)); \
+ csr_write(iregcsr, (value)); \
+ local_irq_restore(__flags); \
+})
+
+#define csr_ind_warl(iregcsr, iselbase, iseloff, warl_val) ({ \
+ unsigned long __old_val = 0, __value = 0; \
+ unsigned long __flags; \
+ local_irq_save(__flags); \
+ csr_write(CSR_ISELECT, (iselbase) + (iseloff)); \
+ __old_val = csr_read(iregcsr); \
+ csr_write(iregcsr, (warl_val)); \
+ __value = csr_read(iregcsr); \
+ csr_write(iregcsr, __old_val); \
+ local_irq_restore(__flags); \
+ __value; \
+})
+
+#endif
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v6 03/21] dt-bindings: riscv: add Sxcsrind ISA extension description
From: Atish Patra @ 2026-06-09 6:01 UTC (permalink / raw)
To: James Clark, Rob Herring, Atish Patra, Arnaldo Carvalho de Melo,
Jiri Olsa, Will Deacon, Mark Rutland, Anup Patel, Namhyung Kim,
Paul Walmsley, Krzysztof Kozlowski, Ian Rogers
Cc: linux-riscv, linux-kernel, linux-perf-users, Conor Dooley,
devicetree, linux-arm-kernel
In-Reply-To: <20260608-counter_delegation-v6-0-285b72ed65a9@meta.com>
From: Atish Patra <atishp@rivosinc.com>
Add the S[m|s]csrind ISA extension description.
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
Documentation/devicetree/bindings/riscv/extensions.yaml | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/Documentation/devicetree/bindings/riscv/extensions.yaml b/Documentation/devicetree/bindings/riscv/extensions.yaml
index 2b0a8a93bb21..4be557dc215d 100644
--- a/Documentation/devicetree/bindings/riscv/extensions.yaml
+++ b/Documentation/devicetree/bindings/riscv/extensions.yaml
@@ -181,6 +181,14 @@ properties:
changes to interrupts as frozen at commit ccbddab ("Merge pull
request #42 from riscv/jhauser-2023-RC4") of riscv-aia.
+ - const: smcsrind
+ description: |
+ The standard Smcsrind supervisor-level extension extends the
+ indirect CSR access mechanism defined by the Smaia extension. This
+ extension allows other ISA extension to use indirect CSR access
+ mechanism in M-mode as ratified in the 20240326 version of the
+ privileged ISA specification.
+
- const: smmpm
description: |
The standard Smmpm extension for M-mode pointer masking as
@@ -199,6 +207,14 @@ properties:
added by other RISC-V extensions in H/S/VS/U/VU modes and as
ratified at commit a28bfae (Ratified (#7)) of riscv-state-enable.
+ - const: sscsrind
+ description: |
+ The standard Sscsrind supervisor-level extension extends the
+ indirect CSR access mechanism defined by the Ssaia extension. This
+ extension allows other ISA extension to use indirect CSR access
+ mechanism in S-mode as ratified in the 20240326 version of the
+ privileged ISA specification.
+
- const: ssaia
description: |
The standard Ssaia supervisor-level extension for the advanced
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH v6 00/21] Add Counter delegation ISA extension support
From: Atish Patra @ 2026-06-09 6:01 UTC (permalink / raw)
To: James Clark, Rob Herring, Atish Patra, Arnaldo Carvalho de Melo,
Jiri Olsa, Will Deacon, Mark Rutland, Anup Patel, Namhyung Kim,
Paul Walmsley, Krzysztof Kozlowski, Ian Rogers
Cc: linux-riscv, linux-kernel, linux-perf-users, Conor Dooley,
devicetree, linux-arm-kernel
This series adds the counter delegation extension support. It is based on
very early PoC work done by Kevin Xue and mostly rewritten after that.
The counter delegation ISA extension(Smcdeleg/Ssccfg) actually depends
on multiple ISA extensions.
1. S[m|s]csrind : The indirect CSR extension[1] which defines additional
5 ([M|S|VS]IREG2-[M|S|VS]IREG6) register to address size limitation of
RISC-V CSR address space.
2. Smstateen: The stateen bit[60] controls the access to the registers
indirectly via the above indirect registers.
3. Smcdeleg/Ssccfg: The counter delegation extensions[2]
The counter delegation extension allows Supervisor mode to program the
hpmevent and hpmcounters directly without needing the assistance from the
M-mode via SBI calls. This results in a faster perf profiling and very
few traps. This extension also introduces a scountinhibit CSR which allows
to stop/start any counter directly from the S-mode. As the counter
delegation extension potentially can have more than 100 CSRs, the specification
leverages the indirect CSR extension to save the precious CSR address range.
Due to the dependency of these extensions, the following extensions must be
enabled in qemu to use the counter delegation feature in S-mode.
"smstateen=true,sscofpmf=true,ssccfg=true,smcdeleg=true,smcsrind=true,sscsrind=true"
or Virt machine users can just "max" cpu instead.
When we access the counters directly in S-mode, we also need to solve the
following problems.
1. Event to counter mapping
2. Event encoding discovery
The RISC-V ISA doesn't define any standard either for event encoding or the
event to counter mapping rules. Until now, the SBI PMU implementation relies
on device tree binding[3] to discover the event to counter mapping in RISC-V
platform in the firmware. The SBI PMU specification[4] defines event encoding
for standard perf events as well. Thus, the kernel can query the appropriate
counter for an given event from the firmware.
However, the kernel doesn't need any firmware interaction for hardware
counters if counter delegation is available in the hardware. Thus, the driver
needs to discover the above mappings/encodings by itself without any assistance
from firmware.
Solution to Problem #1:
This patch series solves the above problem #1 by extending the perf tool in a
way so that event json file can specify the counter constraints of each event
and that can be passed to the driver to choose the best counter for a given
event.
This series introduces a RISC-V specific event field in "CounterIDMask" in
event_fields that describes a bitmask of counters supported for a specific eventi.
This is the similar approach for few other existing properties in the event_fields
which were used by single architecture as well. The counter constraint bitmap is
passed to the perf driver via newly introduced "counterid_mask" property set in "config2".
The platform vendor have these three ways to encode/use the platform specific
events.
1. Directly in driver with appropriate constraints (discouraged due to bloating
of the driver)
2. Encode in Json with with CounterIDMask field (preferred as it is contained
within platform specific json file)
3. Directly pass counterid_mask at while invoking perf
cpu/event=<code>,counterid_mask=<mask>/
The last two patches show cases these use cases and not intended for merging.
Solution to problem #2:
The event encoding can come from the json or commandline as well.
The Qemu patches are available in upstream now.
The Linux kernel patches can be found here:
https://github.com/atishp04/linux/tree/b4/counter_delegation_v6
[1] https://github.com/riscv/riscv-indirect-csr-access
[2] https://github.com/riscv/riscv-smcdeleg-ssccfg
[3] https://www.kernel.org/doc/Documentation/devicetree/bindings/perf/riscv%2Cpmu.yaml
[4] https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/src/ext-pmu.adoc
To: Paul Walmsley <pjw@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Will Deacon <will@kernel.org>
To: Mark Rutland <mark.rutland@arm.com>
To: Atish Patra <atish.patra@linux.dev>
To: Anup Patel <anup@brainfault.org>
To: Arnaldo Carvalho de Melo <acme@kernel.org>
To: Namhyung Kim <namhyung@kernel.org>
To: Jiri Olsa <jolsa@kernel.org>
To: Ian Rogers <irogers@google.com>
To: James Clark <james.clark@linaro.org>
Cc: linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: Conor Dooley <conor@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-perf-users@vger.kernel.org
Signed-off-by: Atish Patra <atishp@meta.com>
Changes in v6:
- Reverted the file name changes as suggested by Will. Now pmu-sbi.c will continue
to support both counter delegation and SBI PMU with different function prefixes.
- No longer depends up old upstream patch for reusing the Counter property to
encode event to counter mapping property. It directly uses additional field in
json event fields similar to other architectures.
- Added few test patches (not intended for upstreaming) to show case different
possibilities of providing mapping/event encodings.
- Fixed review comments and miscellenous minor typos/fixes on v5
- Rebased on top of v7.1-rc6
Changes in v5:
- Fixed dt_binding_check errors.
- Added the ISA extension dependancy for counter delegation extensions.
- Replaced the boolean variables with static key conditional check required at boot time.
- Miscellaneous minor code restructuring.
- Link to v4: https://lore.kernel.org/r/20250205-counter_delegation-v4-0-835cfa88e3b1@rivosinc.com
Changes in v4:
- Added ISA dependencies as per dt schema instead of description.
- Fixed few compilation issues due to patch reordering in v3.
- Link to v3: https://lore.kernel.org/r/20250127-counter_delegation-v3-0-64894d7e16d5@rivosinc.com
Changes in v3:
- Fixed the dtb binding check failures.
- Inlcuded the fix reported by Rajnesh Kanwal for guest counter overflow.
- Rearranged the overflow handling more efficiently for better modularity.
- Link to v2: https://lore.kernel.org/r/20250114-counter_delegation-v2-0-8ba74cdb851b@rivosinc.com
Changes in v2:
- Dropped architecture specific overrides for event encoding.
- Dropped hwprobe bits.
- Added a vendor specific event encoding table to support vendor specific event
encoding and counter mapping.
- Fixed few bugs and cleanup.
- Link to v1: https://lore.kernel.org/r/20240217005738.3744121-1-atishp@rivosinc.com
---
Atish Patra (18):
RISC-V: Add Sxcsrind ISA extension definition and parsing
dt-bindings: riscv: add Sxcsrind ISA extension description
RISC-V: Define indirect CSR access helpers
RISC-V: Add Smcntrpmf extension parsing
dt-bindings: riscv: add Smcntrpmf ISA extension description
RISC-V: Add Ssccfg/Smcdeleg ISA extension definition and parsing
dt-bindings: riscv: add Counter delegation ISA extensions description
RISC-V: perf: Restructure the SBI PMU code
RISC-V: perf: Modify the counter discovery mechanism
RISC-V: perf: Add a mechanism to defined legacy event encoding
RISC-V: perf: Implement supervisor counter delegation support
RISC-V: perf: Use config2/vendor table for event to counter mapping
RISC-V: perf: Add legacy event encodings via sysfs
RISC-V: perf: Add Qemu virt machine events
tools/perf: Support event code for arch standard events
tools/perf: Add RISC-V CounterIDMask event field
TEST(do-not-upstream): fake qemu-virt PMU events for cdeleg counter-mask testing
TEST(do-not-upstream): fake qemu vendor JSON + mapfile entry for CounterIDMask path
Charlie Jenkins (1):
RISC-V: perf: Skip PMU SBI extension when not implemented
Kaiwen Xue (2):
RISC-V: Add Sxcsrind ISA extension CSR definitions
RISC-V: Add Sscfg extension CSR definition
.../devicetree/bindings/riscv/extensions.yaml | 67 ++
arch/riscv/include/asm/csr.h | 57 ++
arch/riscv/include/asm/csr_ind.h | 44 +
arch/riscv/include/asm/hwcap.h | 7 +
arch/riscv/include/asm/vendorid_list.h | 4 +
arch/riscv/kernel/cpufeature.c | 27 +
drivers/perf/Kconfig | 14 +-
drivers/perf/riscv_pmu_sbi.c | 1044 ++++++++++++++++----
include/linux/perf/riscv_pmu.h | 18 +
.../perf/pmu-events/arch/riscv/arch-standard.json | 10 +
tools/perf/pmu-events/arch/riscv/mapfile.csv | 1 +
.../pmu-events/arch/riscv/qemu/virt/events.json | 26 +
tools/perf/pmu-events/jevents.py | 7 +-
13 files changed, 1124 insertions(+), 202 deletions(-)
---
base-commit: ba3e43a9e601636f5edb54e259a74f96ca3b8fd8
change-id: 20240715-counter_delegation-628a32f8c9cc
Best regards,
--
Atish Patra <atishp@meta.com>
^ permalink raw reply
* [PATCH v6 01/21] RISC-V: Add Sxcsrind ISA extension CSR definitions
From: Atish Patra @ 2026-06-09 6:01 UTC (permalink / raw)
To: James Clark, Rob Herring, Atish Patra, Arnaldo Carvalho de Melo,
Jiri Olsa, Will Deacon, Mark Rutland, Anup Patel, Namhyung Kim,
Paul Walmsley, Krzysztof Kozlowski, Ian Rogers
Cc: linux-riscv, linux-kernel, linux-perf-users, Conor Dooley,
devicetree, linux-arm-kernel
In-Reply-To: <20260608-counter_delegation-v6-0-285b72ed65a9@meta.com>
From: Kaiwen Xue <kaiwenx@rivosinc.com>
This adds definitions of new CSRs and bits defined in Sxcsrind ISA
extension. These CSR enables indirect accesses mechanism to access
any CSRs in M-, S-, and VS-mode. The range of the select values
and ireg will be define by the ISA extension using Sxcsrind extension.
Signed-off-by: Kaiwen Xue <kaiwenx@rivosinc.com>
Reviewed-by: Clément Léger <cleger@rivosinc.com>
Signed-off-by: Atish Patra <atishp@rivosinc.com>
---
arch/riscv/include/asm/csr.h | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h
index 31b8988f4488..b4551a6cf7cb 100644
--- a/arch/riscv/include/asm/csr.h
+++ b/arch/riscv/include/asm/csr.h
@@ -347,6 +347,12 @@
/* Supervisor-Level Window to Indirectly Accessed Registers (AIA) */
#define CSR_SISELECT 0x150
#define CSR_SIREG 0x151
+/* Supervisor-Level Window to Indirectly Accessed Registers (Sxcsrind) */
+#define CSR_SIREG2 0x152
+#define CSR_SIREG3 0x153
+#define CSR_SIREG4 0x155
+#define CSR_SIREG5 0x156
+#define CSR_SIREG6 0x157
/* Supervisor-Level Interrupts (AIA) */
#define CSR_STOPEI 0x15c
@@ -394,6 +400,14 @@
/* VS-Level Window to Indirectly Accessed Registers (H-extension with AIA) */
#define CSR_VSISELECT 0x250
#define CSR_VSIREG 0x251
+/*
+ * VS-Level Window to Indirectly Accessed Registers (H-extension with Sxcsrind)
+ */
+#define CSR_VSIREG2 0x252
+#define CSR_VSIREG3 0x253
+#define CSR_VSIREG4 0x255
+#define CSR_VSIREG5 0x256
+#define CSR_VSIREG6 0x257
/* VS-Level Interrupts (H-extension with AIA) */
#define CSR_VSTOPEI 0x25c
@@ -436,6 +450,12 @@
/* Machine-Level Window to Indirectly Accessed Registers (AIA) */
#define CSR_MISELECT 0x350
#define CSR_MIREG 0x351
+/* Machine-Level Window to Indirectly Accessed Registers (Sxcsrind) */
+#define CSR_MIREG2 0x352
+#define CSR_MIREG3 0x353
+#define CSR_MIREG4 0x355
+#define CSR_MIREG5 0x356
+#define CSR_MIREG6 0x357
/* Machine-Level Interrupts (AIA) */
#define CSR_MTOPEI 0x35c
@@ -498,6 +518,11 @@
# define CSR_IEH CSR_MIEH
# define CSR_ISELECT CSR_MISELECT
# define CSR_IREG CSR_MIREG
+# define CSR_IREG2 CSR_MIREG2
+# define CSR_IREG3 CSR_MIREG3
+# define CSR_IREG4 CSR_MIREG4
+# define CSR_IREG5 CSR_MIREG5
+# define CSR_IREG6 CSR_MIREG6
# define CSR_IPH CSR_MIPH
# define CSR_TOPEI CSR_MTOPEI
# define CSR_TOPI CSR_MTOPI
@@ -523,6 +548,11 @@
# define CSR_IEH CSR_SIEH
# define CSR_ISELECT CSR_SISELECT
# define CSR_IREG CSR_SIREG
+# define CSR_IREG2 CSR_SIREG2
+# define CSR_IREG3 CSR_SIREG3
+# define CSR_IREG4 CSR_SIREG4
+# define CSR_IREG5 CSR_SIREG5
+# define CSR_IREG6 CSR_SIREG6
# define CSR_IPH CSR_SIPH
# define CSR_TOPEI CSR_STOPEI
# define CSR_TOPI CSR_STOPI
--
2.53.0-Meta
^ permalink raw reply related
* RE: [PATCH] PCI: cadence: skip the link polling when endpoint not connected
From: Manikandan Karunakaran Pillai @ 2026-06-09 5:36 UTC (permalink / raw)
To: Siddharth Vadapalli, Aksh Garg
Cc: vigneshr@ti.com, lpieralisi@kernel.org, kwilczynski@kernel.org,
mani@kernel.org, robh@kernel.org, bhelgaas@google.com,
unicorn_wang@outlook.com, me@ziyao.cc, 18255117159@163.com,
linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, danishanwar@ti.com
In-Reply-To: <bb190834-121f-4b71-ad12-bd2d5fcacd44@ti.com>
>-----Original Message-----
>From: Siddharth Vadapalli <s-vadapalli@ti.com>
>Sent: Tuesday, June 9, 2026 10:42 AM
>To: Aksh Garg <a-garg7@ti.com>
>Cc: s-vadapalli@ti.com; vigneshr@ti.com; lpieralisi@kernel.org;
>kwilczynski@kernel.org; mani@kernel.org; robh@kernel.org;
>bhelgaas@google.com; Manikandan Karunakaran Pillai
><mpillai@cadence.com>; unicorn_wang@outlook.com; me@ziyao.cc;
>18255117159@163.com; linux-pci@vger.kernel.org; linux-arm-
>kernel@lists.infradead.org; linux-kernel@vger.kernel.org; danishanwar@ti.com
>Subject: Re: [PATCH] PCI: cadence: skip the link polling when endpoint not
>connected
>
>EXTERNAL MAIL
>
>
>On 09/06/26 09:58, Aksh Garg wrote:
>>
>>
>> On 09/06/26 09:12, Siddharth Vadapalli wrote:
>>> On 05/06/26 12:49, Aksh Garg wrote:
>>>> cdns_pcie_host_wait_for_link() polls on link-up for 10 retries with a
>>>> delay of 90-100ms each (~1 second). A call to cdns_pcie_host_link_setup()
>>>> during the resume operation blocks the resume operation unnecessarily
>for
>>>> ~1s even when no endpoint device is connected.
>>>>
>>>> Add skip_link_polling flag to track link state across suspend/resume
>>>> cycles. If link was down before suspend, skip the expensive polling
>>>> in resume since no endpoint was present.
>>>>
>>>> Signed-off-by: Aksh Garg <a-garg7@ti.com>
>>>> ---
>>>> drivers/pci/controller/cadence/pci-j721e.c | 5 +++++
>>>> drivers/pci/controller/cadence/pcie-cadence-host-hpa.c | 3 +++
>>>> drivers/pci/controller/cadence/pcie-cadence-host.c | 3 +++
>>>> drivers/pci/controller/cadence/pcie-cadence.h | 3 +++
>>>> 4 files changed, 14 insertions(+)
>>>>
>>>> diff --git a/drivers/pci/controller/cadence/pci-j721e.c b/drivers/pci/
>>>> controller/cadence/pci-j721e.c
>>>> index bfdfe98d5aba..849eb8bb9e45 100644
>>>> --- a/drivers/pci/controller/cadence/pci-j721e.c
>>>> +++ b/drivers/pci/controller/cadence/pci-j721e.c
>>>> @@ -686,6 +686,11 @@ static int j721e_pcie_suspend_noirq(struct device
>>>> *dev)
>>>> struct j721e_pcie *pcie = dev_get_drvdata(dev);
>>>> if (pcie->mode == PCI_MODE_RC) {
>>>> + struct cdns_pcie_rc *rc = cdns_pcie_to_rc(pcie->cdns_pcie);
>>>> +
>>>> + /* If link is down before suspend, skip polling in resume */
>>>> + rc->skip_link_polling = !j721e_pcie_link_up(pcie->cdns_pcie);
>>>> +
>>>
>>> Please consider the following scenario:
>>> 1. Endpoint device is not connected initially and we suspend with
>>> 'skip_link_polling' set to true.
>>> 2. We resume and skip link polling.
>>> 3. An Endpoint device is connected to the PCIe Connector a while later.
>>> 4. We perform a bus rescan, but with skip_link_polling still set (in
>>> resume), wouldn't we skip polling for the link to be up?
>>>
>>> Shouldn't 'skip_link_polling' be set to false on resume, after we skip
>>> the polling initially, in order to allow waiting for a link up for a
>>> newly connected device? Although this may not be done in practice and the
>>> Endpoint device may always be left connected from the start, I am
>>> wondering if we should clear 'skip_link_polling' on resume to account for
>>> the aforementioned scenario. Let me know if I am overlooking something
>here.
>>
>> This path is not used during bus rescan. This implies that during the
>> rescan, we do not call cdns_pcie_host_link_setup() or
>> cdns_pcie_hpa_host_link_setup().
>Thank you for clarifying.
>
>Reviewed-by: Siddharth Vadapalli <s-vadapalli@ti.com>
>
Looks good to me
>Regards,
>Siddharth.
^ permalink raw reply
* [PATCH] spi: meson-spifc: fix runtime PM leak on remove
From: Ruoyu Wang @ 2026-06-09 5:26 UTC (permalink / raw)
To: Mark Brown, linux-spi
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Beniamino Galvani, linux-arm-kernel, linux-amlogic, linux-kernel,
Ruoyu Wang
pm_runtime_get_sync() increments the runtime PM usage counter even when it
returns an error. meson_spifc_remove() uses it to resume the controller
before disabling runtime PM, but never drops the usage counter again.
Balance the get with pm_runtime_put_noidle() after disabling runtime PM,
matching the teardown pattern used by other SPI controller drivers.
Found by static analysis. I do not have hardware to test this.
Fixes: c3e4bc5434d2 ("spi: meson: Add support for Amlogic Meson SPIFC")
Signed-off-by: Ruoyu Wang <ruoyuw560@gmail.com>
---
drivers/spi/spi-meson-spifc.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/spi/spi-meson-spifc.c b/drivers/spi/spi-meson-spifc.c
index b818950a8cb7d..e2d19c3873f71 100644
--- a/drivers/spi/spi-meson-spifc.c
+++ b/drivers/spi/spi-meson-spifc.c
@@ -351,6 +351,7 @@ static void meson_spifc_remove(struct platform_device *pdev)
{
pm_runtime_get_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
}
#ifdef CONFIG_PM_SLEEP
--
2.51.0
^ permalink raw reply related
* [PATCH 1/2] arm64: ftrace: prepare ftrace_modify_call() for use without CALL_OPS
From: Jose Fernandez (Anthropic) @ 2026-06-09 5:19 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Mark Rutland, Catalin Marinas,
Will Deacon, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt
Cc: linux-kernel, linux-trace-kernel, linux-arm-kernel, llvm, bpf,
Florent Revest, Puranjay Mohan, Xu Kuohai,
Jose Fernandez (Anthropic)
In-Reply-To: <20260609-arm64-ftrace-direct-calls-v1-0-4a46f266697f@linux.dev>
ftrace_modify_call() is guarded by CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
and calls ftrace_rec_set_ops(rec, arm64_rec_get_ops(rec)) directly,
which only exists when CALL_OPS is enabled.
Generic ftrace also needs ftrace_modify_call() when
CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS is enabled, to retarget a
callsite between two non-FTRACE_ADDR destinations, as happens when a
direct trampoline is modified. The next patch allows DIRECT_CALLS without
CALL_OPS, so widen the guard to cover both configurations and switch
the body to the ftrace_rec_update_ops() wrapper, which already has a
stub for the !CALL_OPS case. ftrace_make_call() already uses the same
wrapper today.
No functional change: with CALL_OPS enabled, ftrace_rec_update_ops()
expands to the exact call this replaces.
Assisted-by: Claude:unspecified
Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
---
arch/arm64/kernel/ftrace.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
index 5a1554a441628..e1a3c0b3a0514 100644
--- a/arch/arm64/kernel/ftrace.c
+++ b/arch/arm64/kernel/ftrace.c
@@ -409,7 +409,8 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
return ftrace_modify_code(pc, old, new, true);
}
-#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
+#if defined(CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS) || \
+ defined(CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS)
int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
unsigned long addr)
{
@@ -417,7 +418,7 @@ int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
u32 old, new;
int ret;
- ret = ftrace_rec_set_ops(rec, arm64_rec_get_ops(rec));
+ ret = ftrace_rec_update_ops(rec);
if (ret)
return ret;
--
2.52.0
^ permalink raw reply related
* [PATCH 0/2] arm64: ftrace: support DIRECT_CALLS without CALL_OPS
From: Jose Fernandez (Anthropic) @ 2026-06-09 5:19 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Mark Rutland, Catalin Marinas,
Will Deacon, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt
Cc: linux-kernel, linux-trace-kernel, linux-arm-kernel, llvm, bpf,
Florent Revest, Puranjay Mohan, Xu Kuohai,
Jose Fernandez (Anthropic)
On arm64, HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS is currently selected
only when DYNAMIC_FTRACE_WITH_CALL_OPS is available. CALL_OPS, in
turn, is mutually exclusive with kCFI: the pre-function NOPs it needs
would change the offset of the pre-function type hash (see
baaf553d3bc3 ("arm64: Implement HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS")),
and the compiler support needed to reconcile the two does not exist
yet.
The result is that a CONFIG_CFI=y arm64 kernel has no
ftrace direct calls at all, so register_fentry() fails with -ENOTSUPP
and no BPF trampoline can attach: fentry/fexit, fmod_ret and BPF LSM
programs are all unavailable. Deployments that want both kCFI
hardening and BPF-based security monitoring currently have to give
one of them up. systemd's bpf-restrict-fs feature hits this today:
https://lore.kernel.org/all/20250610232418.GA3544567@ax162/
CALL_OPS is an optimization for direct calls, not a dependency.
In-BL-range trampolines are reached by a direct branch without
consulting the ops pointer, and out-of-range trampolines already
fall back to ftrace_caller, where the DIRECT_CALLS machinery
(call_direct_funcs() storing the trampoline in ftrace_regs, the
ftrace_caller tail-call) is gated on DIRECT_CALLS alone. s390 and
loongarch ship HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS this way,
without having CALL_OPS at all.
Patch 1 prepares ftrace_modify_call() to build without CALL_OPS by
widening its #ifdef and using the existing ftrace_rec_update_ops()
wrapper (no functional change for current configurations). Patch 2
drops the CALL_OPS requirement from the DIRECT_CALLS select.
Configurations that keep CALL_OPS (clang !CFI, and GCC without
CC_OPTIMIZE_FOR_SIZE) are unchanged. We verified this: in an arm64
clang build, every object file is byte-identical before and after
the series except ftrace.o itself, and its disassembly is identical.
CFI builds (and GCC -Os builds) gain working direct calls, with
out-of-range attachments taking the ftrace_caller dispatch path
instead of the per-callsite fast path.
We tested on a 6.18.y-based kernel and on this base with clang
kCFI builds (CONFIG_CFI=y, enforcing) under qemu (TCG, and KVM on an
arm64 host) and on GB200-based arm64 hardware: fentry/fexit, fmod_ret
and BPF LSM programs load, attach and execute; the ftrace-direct
sample modules (including both modify samples, exercising
ftrace_modify_call()) run cleanly; no CFI violations observed. The
fentry_test, fexit_test, fentry_fexit, fexit_sleep, fexit_stress,
modify_return, tracing_struct, lsm and trampoline_count selftests and
the ftrace direct-call selftests (test.d/direct) pass on the new
configuration with results identical to a CALL_OPS kernel built from
the same tree, and a broader test_progs sweep showed no differences
attributable to this series. Without the series, all of the above
fail at attach time with -ENOTSUPP.
riscv has the same gap (its DIRECT_CALLS select also requires
CALL_OPS, and its CALL_OPS is likewise !CFI); if this approach is
acceptable for arm64 we can follow up there.
---
Jose Fernandez (Anthropic) (2):
arm64: ftrace: prepare ftrace_modify_call() for use without CALL_OPS
arm64: ftrace: allow DIRECT_CALLS without CALL_OPS
arch/arm64/Kconfig | 2 +-
arch/arm64/kernel/ftrace.c | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260607-arm64-ftrace-direct-calls-152230ef7077
Best regards,
--
Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
^ permalink raw reply
* [PATCH 2/2] arm64: ftrace: allow DIRECT_CALLS without CALL_OPS
From: Jose Fernandez (Anthropic) @ 2026-06-09 5:19 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Mark Rutland, Catalin Marinas,
Will Deacon, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt
Cc: linux-kernel, linux-trace-kernel, linux-arm-kernel, llvm, bpf,
Florent Revest, Puranjay Mohan, Xu Kuohai,
Jose Fernandez (Anthropic)
In-Reply-To: <20260609-arm64-ftrace-direct-calls-v1-0-4a46f266697f@linux.dev>
arm64 gained ftrace direct calls in commit 2aa6ac03516d ("arm64:
ftrace: Add direct call support") on top of
DYNAMIC_FTRACE_WITH_CALL_OPS, using the per-callsite ops pointer as a
fast path to reach the direct trampoline. Since commit baaf553d3bc3
("arm64: Implement HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS"), CALL_OPS is
mutually exclusive with CFI: the pre-function NOPs would change the
offset of the pre-function kCFI type hash, and the compiler support
needed to keep that offset consistent does not exist yet.
The result is that a CONFIG_CFI=y kernel loses CALL_OPS, and with it
DIRECT_CALLS, and with it every BPF trampoline attachment to kernel
functions: register_fentry() returns -ENOTSUPP, so fentry/fexit,
fmod_ret and BPF LSM programs cannot attach at all. This is a real
problem for hardened arm64 deployments that rely on BPF LSM for
security monitoring while keeping kCFI enabled.
CALL_OPS is an optimization for direct calls, not a dependency. When
the direct trampoline is within BL range, the callsite branches
straight to it and ftrace_caller is not involved. When it is out of
range, ftrace_find_callable_addr() already falls back to
ftrace_caller, and the DIRECT_CALLS machinery there
(FREGS_DIRECT_TRAMP, ftrace_caller_direct_late) is gated on
DIRECT_CALLS alone, not CALL_OPS: the ops dispatch invokes
call_direct_funcs(), which stores the trampoline address in
ftrace_regs, and ftrace_caller tail-calls it. s390 and loongarch use
this same mechanism for HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS without
having CALL_OPS at all, and DYNAMIC_FTRACE_WITH_ARGS without CALL_OPS
is already a supported arm64 configuration (GCC builds with
CC_OPTIMIZE_FOR_SIZE do not satisfy the CALL_OPS select condition).
Drop the CALL_OPS requirement from the
HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS select. Configurations that
keep CALL_OPS (!CFI clang builds, and GCC builds without
CC_OPTIMIZE_FOR_SIZE) are unchanged. CALL_OPS-less configurations
take the ftrace_caller ops-dispatch path for out-of-range direct
calls, trading the per-callsite fast path for working BPF
trampolines; in-range attachments still branch directly with no
overhead. GCC -Os builds also gain DIRECT_CALLS as a side effect.
That is intended: s390 and loongarch already ship DIRECT_CALLS
without any per-callsite fast path.
Assisted-by: Claude:unspecified
Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
---
arch/arm64/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index fe60738e5943b..2cd7d536671c9 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -188,7 +188,7 @@ config ARM64
if (GCC_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS || \
CLANG_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS)
select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS \
- if DYNAMIC_FTRACE_WITH_ARGS && DYNAMIC_FTRACE_WITH_CALL_OPS
+ if DYNAMIC_FTRACE_WITH_ARGS
select HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS \
if (DYNAMIC_FTRACE_WITH_ARGS && !CFI && \
(CC_IS_CLANG || !CC_OPTIMIZE_FOR_SIZE))
--
2.52.0
^ permalink raw reply related
* Re: [PATCH v4] i2c: cadence: Add shutdown handler
From: Pandey, Radhey Shyam @ 2026-06-09 5:12 UTC (permalink / raw)
To: Ajay Neeli, git, andi.shyti, linux-arm-kernel, linux-i2c,
linux-kernel
Cc: michal.simek, srinivas.goud, radhey.shyam.pandey
In-Reply-To: <20260430053050.3590173-1-ajay.neeli@amd.com>
> During system reboot or kexec, in-flight I2C transfers can cause
> spurious interrupts or leave the bus in an undefined state. Add a
> shutdown handler that marks the adapter suspended and resets the
> controller, ensuring a clean handoff.
>
> Signed-off-by: Ajay Neeli <ajay.neeli@amd.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@amd.com>
Thanks!
> ---
> Link: https://lore.kernel.org/linux-i2c/20250730122907.18909-1-ajay.neeli@amd.com/
>
> Changes v3->v4:
> - Replaced disable_irq() with cdns_i2c_master_reset() to avoid
> potential deadlocks during shutdown
> - Added pm_runtime_status_suspended() check to skip register access
> when clocks are disabled
>
> Changes v2->v3:
> Corrected a typo that caused a build error in v2
>
> Changes in v1->v2:
> Disable interrupts
> ---
> drivers/i2c/busses/i2c-cadence.c | 20 ++++++++++++++++++++
> 1 file changed, 20 insertions(+)
>
> diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
> index 0fb728ade92e..da8770182a18 100644
> --- a/drivers/i2c/busses/i2c-cadence.c
> +++ b/drivers/i2c/busses/i2c-cadence.c
> @@ -1635,6 +1635,25 @@ static void cdns_i2c_remove(struct platform_device *pdev)
> reset_control_assert(id->reset);
> }
>
> +/**
> + * cdns_i2c_shutdown - Prepare I2C controller for system shutdown
> + * @pdev: Handle to the platform device structure
> + *
> + * Mark the adapter as suspended and reset the controller to ensure a clean
> + * handoff during system reboot or kexec.
> + */
> +static void cdns_i2c_shutdown(struct platform_device *pdev)
> +{
> + struct cdns_i2c *id = platform_get_drvdata(pdev);
> +
> + /* Mark the adapter as suspended to prevent further I2C transfers */
> + i2c_mark_adapter_suspended(&id->adap);
> +
> + /* Reset the controller state if active - clocks are disabled when suspended */
> + if (!pm_runtime_status_suspended(&pdev->dev))
> + cdns_i2c_master_reset(&id->adap);
> +}
> +
> static struct platform_driver cdns_i2c_drv = {
> .driver = {
> .name = DRIVER_NAME,
> @@ -1643,6 +1662,7 @@ static struct platform_driver cdns_i2c_drv = {
> },
> .probe = cdns_i2c_probe,
> .remove = cdns_i2c_remove,
> + .shutdown = cdns_i2c_shutdown,
> };
>
> module_platform_driver(cdns_i2c_drv);
^ permalink raw reply
* Re: [PATCH] PCI: cadence: skip the link polling when endpoint not connected
From: Siddharth Vadapalli @ 2026-06-09 5:11 UTC (permalink / raw)
To: Aksh Garg
Cc: s-vadapalli, vigneshr, lpieralisi, kwilczynski, mani, robh,
bhelgaas, mpillai, unicorn_wang, me, 18255117159, linux-pci,
linux-arm-kernel, linux-kernel, danishanwar
In-Reply-To: <1362dccd-24b0-4b44-8e45-75cba81180ae@ti.com>
On 09/06/26 09:58, Aksh Garg wrote:
>
>
> On 09/06/26 09:12, Siddharth Vadapalli wrote:
>> On 05/06/26 12:49, Aksh Garg wrote:
>>> cdns_pcie_host_wait_for_link() polls on link-up for 10 retries with a
>>> delay of 90-100ms each (~1 second). A call to cdns_pcie_host_link_setup()
>>> during the resume operation blocks the resume operation unnecessarily for
>>> ~1s even when no endpoint device is connected.
>>>
>>> Add skip_link_polling flag to track link state across suspend/resume
>>> cycles. If link was down before suspend, skip the expensive polling
>>> in resume since no endpoint was present.
>>>
>>> Signed-off-by: Aksh Garg <a-garg7@ti.com>
>>> ---
>>> drivers/pci/controller/cadence/pci-j721e.c | 5 +++++
>>> drivers/pci/controller/cadence/pcie-cadence-host-hpa.c | 3 +++
>>> drivers/pci/controller/cadence/pcie-cadence-host.c | 3 +++
>>> drivers/pci/controller/cadence/pcie-cadence.h | 3 +++
>>> 4 files changed, 14 insertions(+)
>>>
>>> diff --git a/drivers/pci/controller/cadence/pci-j721e.c b/drivers/pci/
>>> controller/cadence/pci-j721e.c
>>> index bfdfe98d5aba..849eb8bb9e45 100644
>>> --- a/drivers/pci/controller/cadence/pci-j721e.c
>>> +++ b/drivers/pci/controller/cadence/pci-j721e.c
>>> @@ -686,6 +686,11 @@ static int j721e_pcie_suspend_noirq(struct device
>>> *dev)
>>> struct j721e_pcie *pcie = dev_get_drvdata(dev);
>>> if (pcie->mode == PCI_MODE_RC) {
>>> + struct cdns_pcie_rc *rc = cdns_pcie_to_rc(pcie->cdns_pcie);
>>> +
>>> + /* If link is down before suspend, skip polling in resume */
>>> + rc->skip_link_polling = !j721e_pcie_link_up(pcie->cdns_pcie);
>>> +
>>
>> Please consider the following scenario:
>> 1. Endpoint device is not connected initially and we suspend with
>> 'skip_link_polling' set to true.
>> 2. We resume and skip link polling.
>> 3. An Endpoint device is connected to the PCIe Connector a while later.
>> 4. We perform a bus rescan, but with skip_link_polling still set (in
>> resume), wouldn't we skip polling for the link to be up?
>>
>> Shouldn't 'skip_link_polling' be set to false on resume, after we skip
>> the polling initially, in order to allow waiting for a link up for a
>> newly connected device? Although this may not be done in practice and the
>> Endpoint device may always be left connected from the start, I am
>> wondering if we should clear 'skip_link_polling' on resume to account for
>> the aforementioned scenario. Let me know if I am overlooking something here.
>
> This path is not used during bus rescan. This implies that during the
> rescan, we do not call cdns_pcie_host_link_setup() or
> cdns_pcie_hpa_host_link_setup().
Thank you for clarifying.
Reviewed-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Regards,
Siddharth.
^ permalink raw reply
* Re: [PATCH] KVM: arm64: Expose PMMIR_EL1.SLOTS to guests
From: Oliver Upton @ 2026-06-09 4:54 UTC (permalink / raw)
To: Congkai Tan
Cc: blakgeof, catalin.marinas, harisokn, joey.gouly, kvmarm,
linux-arm-kernel, linux-kernel, mark.rutland, maz, suzuki.poulose,
will, yuzenghui
In-Reply-To: <20260608210359.1757125-1-congkai@amazon.com>
On Mon, Jun 08, 2026 at 09:03:59PM +0000, Congkai Tan wrote:
> On Mon, Jun 01, 2026 at 01:06:17PM -0700, Oliver Upton wrote:
> > We can't change the value of PMMIR_EL1 unconditionally since older KVM
> > treated this register as RAZ/WI. This also mixes poorly with the default
> > PMU garbage that we have since as the value of the register can change
> > based on where KVM_ARM_VCPU_INIT gets called...
> >
> > Considering everything, I'd like to see this wired up where:
> >
> > - PMMIR_EL1.SLOTS takes the value of the underlying hardware PMU only
> > if the VMM explicitly selects a particular PMU implementation
> >
> > - KVM allows userspace to set PMMIR_EL1.SLOTS=0 for backwards
> > compatibility
>
> Thanks a lot for the review! Makes sense. We'll work on v2, and here is
> the high-level design we plan to follow:
>
> - Introduce a new VM-wide field, e.g. kvm->arch.pmmir_slots.
>
> - Seed it from the underlying hardware value only when handling
> KVM_ARM_VCPU_PMU_V3_SET_PMU; otherwise it stays 0.
>
> - access_pmmir() returns whatever is in pmmir_slots.
>
> - set_pmmir() writes pmmir_slots to 0 if the user input is 0;
> otherwise it no-ops or rejects.
Reject is always better heh :)
So I was previously under the impression that we already expose PMMIR_EL1
to userspace but we actually don't. Grr.
The UAPI around PMUv3 is crappy enough that we should just add a new
vCPU feature flag. When that flag is set:
- KVM will not create a 'default' PMU, userspace must select a PMU
implementation to init the vCPU
- PMMIR_EL1 becomes a user-visible register with the behavior that you
outline above
- No PMCEID masking for STALL_SLOT* events
There's a couple larger PMU features underway (e.g. Colton's partitioned
PMU, Akihiko's fixed counters PMU) that we can also condition on the new
feature flag.
Does this sound OK to you?
Thanks,
Oliver
^ permalink raw reply
* Re: [PATCH] PCI: cadence: skip the link polling when endpoint not connected
From: Aksh Garg @ 2026-06-09 4:28 UTC (permalink / raw)
To: Siddharth Vadapalli
Cc: vigneshr, lpieralisi, kwilczynski, mani, robh, bhelgaas, mpillai,
unicorn_wang, me, 18255117159, linux-pci, linux-arm-kernel,
linux-kernel, danishanwar
In-Reply-To: <19ded2fa-4560-4b38-975c-6b490328e992@ti.com>
On 09/06/26 09:12, Siddharth Vadapalli wrote:
> On 05/06/26 12:49, Aksh Garg wrote:
>> cdns_pcie_host_wait_for_link() polls on link-up for 10 retries with a
>> delay of 90-100ms each (~1 second). A call to cdns_pcie_host_link_setup()
>> during the resume operation blocks the resume operation unnecessarily for
>> ~1s even when no endpoint device is connected.
>>
>> Add skip_link_polling flag to track link state across suspend/resume
>> cycles. If link was down before suspend, skip the expensive polling
>> in resume since no endpoint was present.
>>
>> Signed-off-by: Aksh Garg <a-garg7@ti.com>
>> ---
>> drivers/pci/controller/cadence/pci-j721e.c | 5 +++++
>> drivers/pci/controller/cadence/pcie-cadence-host-hpa.c | 3 +++
>> drivers/pci/controller/cadence/pcie-cadence-host.c | 3 +++
>> drivers/pci/controller/cadence/pcie-cadence.h | 3 +++
>> 4 files changed, 14 insertions(+)
>>
>> diff --git a/drivers/pci/controller/cadence/pci-j721e.c b/drivers/pci/
>> controller/cadence/pci-j721e.c
>> index bfdfe98d5aba..849eb8bb9e45 100644
>> --- a/drivers/pci/controller/cadence/pci-j721e.c
>> +++ b/drivers/pci/controller/cadence/pci-j721e.c
>> @@ -686,6 +686,11 @@ static int j721e_pcie_suspend_noirq(struct device
>> *dev)
>> struct j721e_pcie *pcie = dev_get_drvdata(dev);
>> if (pcie->mode == PCI_MODE_RC) {
>> + struct cdns_pcie_rc *rc = cdns_pcie_to_rc(pcie->cdns_pcie);
>> +
>> + /* If link is down before suspend, skip polling in resume */
>> + rc->skip_link_polling = !j721e_pcie_link_up(pcie->cdns_pcie);
>> +
>
> Please consider the following scenario:
> 1. Endpoint device is not connected initially and we suspend with
> 'skip_link_polling' set to true.
> 2. We resume and skip link polling.
> 3. An Endpoint device is connected to the PCIe Connector a while later.
> 4. We perform a bus rescan, but with skip_link_polling still set (in
> resume), wouldn't we skip polling for the link to be up?
>
> Shouldn't 'skip_link_polling' be set to false on resume, after we skip
> the polling initially, in order to allow waiting for a link up for a
> newly connected device? Although this may not be done in practice and
> the Endpoint device may always be left connected from the start, I am
> wondering if we should clear 'skip_link_polling' on resume to account
> for the aforementioned scenario. Let me know if I am overlooking
> something here.
This path is not used during bus rescan. This implies that during the
rescan, we do not call cdns_pcie_host_link_setup() or
cdns_pcie_hpa_host_link_setup().
>
>> gpiod_set_value_cansleep(pcie->reset_gpio, 0);
>> clk_disable_unprepare(pcie->refclk);
>> }
>
> [TRIMMED]
>
> Regards,
> Siddharth.
^ permalink raw reply
* Re: [PATCH v8 07/12] iommu/arm-smmu-v3: Add CMDQ_PROD_STOP_FLAG to gate CMDQ submissions
From: Daniel Mentz @ 2026-06-09 4:20 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Ashish Mhetre, linux-arm-kernel
In-Reply-To: <aiZe97OkmvcUwCzS@google.com>
On Sun, Jun 7, 2026 at 11:19 PM Pranjal Shrivastava <praan@google.com> wrote:
> > >
> > > static void queue_poll_init(struct arm_smmu_device *smmu,
> > > @@ -718,8 +719,25 @@ int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu,
> > > do {
> > > u64 old;
> > >
> > > + /*
> > > + * If the SMMU is suspended/suspending, any new CMDs are elided.
> > > + * This loop is the Point of Commitment. If we haven't cmpxchg'd
> > > + * our new indices yet, we can safely bail. Once the indices are
> > > + * committed, we MUST write valid commands to those slots to
> > > + * avoid indefinite polling in the drain function.
> > > + */
> > > + if (Q_STOP(llq.prod)) {
> > > + local_irq_restore(flags);
> > > + return 0;
> >
> > On second thought, I no longer believe that this is safe. I understand
> > that READ_ONCE(cmdq->q.llq.val) implies no ordering guarantees with
> > respect to any writes to translation tables. In the non-stopped case,
> > we can rely on the call to dma_wmb() further down in this function.
> > However, for the stopped case, I can't identify any barriers that
> > would ensure that the STOP flag is checked only after the writes are
> > visible to SMMU. Here is an example: Let's assume the following
> > program order:
> >
> > * Write to invalidate PTE
> > * Read from cmdq->q.llq.val, determine STOP flag is set, elide TLBI
> >
> > What prevents the CPU from reordering these operations as follows?
> >
> > * Read from cmdq->q.llq.val, determine STOP flag is set, elide TLBI
> > * Write to invalidate PTE
Now, I believe that the smp_mb() call in arm_smmu_domain_inv_range()
actually prevents this (if we remove arm_smmu_cmdq_can_elide or move
it to after the smp_mb() call)
> >
> > Can the following situation occur?
>
> Not really because:
>
> >
> > * Read from cmdq->q.llq.val, determine STOP flag is set, elide TLBI
> > * (Different CPU resumes SMMU)
>
> We do a full smp_mb() here to ensure all writes are seen before SMMUEN=1
> (note that the STOP_FLAG is already unset at that point, so we stop
> eliding commands much before the SMMU is physically able to access any
> config/xlation structures, I've explained everything below).
>
> > * SMMU loads old PTE value into TLB
> > * Write to invalidate PTE
> > * (stale PTE remains in TLB)
> >
> > I propose the following: If you find the STOP flag set, run dma_mb()
> > and check again. I'm afraid that running dma_mb() unconditionally
> > might incur too much of a performance penalty.
>
> IMHO, I think this might be overcomplicating things here..
> Here's why the current version works according to me:
>
> Till SMMUEN=0, the SMMU is spec-guaranteed to never access translation
> or configuration structures (Section 6.3.9.6).
>
> Our runtime_suspend callback sets SMMUEN=0 before setting the STOP_FLAG.
Does "before" refer to program order or the order in which other
agents can observe the STOP_FLAG relative to when SMMUEN=0 is set?
Your program might read "Set SMMUEN=0 then set STOP_FLAG". However, my
question is whether other observers might see the STOP_FLAG before
SMMUEN is cleared. I couldn't identify any barriers in
arm_smmu_runtime_suspend() that would prevent this type of
re-ordering.
> Even if the worker CPU reorders the PTE write after the STOP_FLAG check,
> it is benign because the SMMU is incapable of fetching that (or any) PTE
> while the gate is closed. Because GATE_CLOSED == SMMUEN = 0, implying no
> access to any HW structures whatsoever.
>
> The real synchronization happens in the Resume Path:
>
> 1. arm_smmu_device_reset() clears all caches / TLBs.
> (None of these can have entries before SMMUEN=1)
>
> 2. We execute a full smp_mb() before setting SMMUEN=1. (that's why we
> need smp_mb before SMMUEN=1). This barrier ensures that any PTE
> writes made by any thread—including those that were elided while the
> gate was closed, are globally visible before the SMMU hardware starts
> fetching into TLBs again. (This is why Jason suggested this in v6 [1])
A barrier on one CPU has no bearing on whether writes by any other CPU
can be observed by any particular agent in the system.
Let's compare this with the long comment in
arm_smmu_domain_inv_range() which is what I believe Jason was
referring to. In that example, you see smp_mb() in the code path on
CPU0 and dma_wmb() in the code path on CPU1. Hence, barriers exist on
both sides. If you compare the runtime PM design with
arm_smmu_domain_inv_range(), then smp_mb() belongs in the CPU thread
that performs the translation table updates not the one that performs
the suspend/resume operation.
Actually, now that I think of it: We could just piggy pack on the
existing smp_mb() in arm_smmu_domain_inv_range(). For your
arm_smmu_cmdq_can_elide() to work correctly, though, you would need to
move it to *after* the smp_mb() call. This being said, I would prefer
to remove arm_smmu_cmdq_can_elide() as I wrote on the other thread.
If we do take advantage of the existing smp_mb() in
arm_smmu_domain_inv_range(), then we should probably update that
comment to state that the runtime PM implementation now also depends
on it.
>
> Adding a dma_mb() to the elision path would be redundant given the
> SMMU can't access any structures and the resume barrier. We'd be
> needlessly injecting dma_mbs when no entity is actually accessing those
> structures while the STOP_FLAG is set.
> >
> > I have the same concerns with arm_smmu_cmdq_can_elide(): That
> > READ_ONCE in arm_smmu_cmdq_can_elide() provides no guarantees in
> > regards to ordering relative to PTE writes.
> >
>
> The same as above, No structures are accessed during SMMUEN=0 (spec) and
> during resume before setting SMMUEN=1, we do a full smp_mb() to ensure
> all concurrent PTE writes are acquired in our resume thread before we
> enable SMMUEN=1
I think the definition of "all concurrent PTE writes" is a bit vague.
I don't think the architecture allows you to wait for writes performed
by other CPUs to be observable by all other agents. The only thing you
can do is to ensure that other agents can observe that the STOP_FLAG
has been cleared *before* SMMUEN is set. However, this is already
achieved by the existing dma_wmb() in arm_smmu_cmdq_issue_cmdlist().
Hence, the extra smp_mb() is not required.
>
> Thanks,
> Praan
>
> [1] https://lore.kernel.org/all/20260424151639.GE3611611@ziepe.ca/
^ permalink raw reply
* [soc:soc/drivers] BUILD SUCCESS 6bd8fb9f1cd97f789db5f97cf569f7ed9d6575e0
From: kernel test robot @ 2026-06-09 4:03 UTC (permalink / raw)
To: Linus Walleij; +Cc: linux-arm-kernel, arm
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git soc/drivers
branch HEAD: 6bd8fb9f1cd97f789db5f97cf569f7ed9d6575e0 Merge tag 'zynqmp-soc-for-7.2' of https://github.com/Xilinx/linux-xlnx into soc/drivers
elapsed time: 6892m
configs tested: 58
configs skipped: 0
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-16.1.0
alpha allyesconfig gcc-16.1.0
arc allmodconfig gcc-16.1.0
arc allnoconfig gcc-16.1.0
arc allyesconfig gcc-16.1.0
arm allnoconfig clang-23
arm allyesconfig gcc-16.1.0
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-16.1.0
csky allmodconfig gcc-16.1.0
csky allnoconfig gcc-16.1.0
hexagon allmodconfig clang-23
hexagon allnoconfig clang-23
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allyesconfig gcc-14
loongarch allmodconfig clang-19
loongarch allnoconfig clang-20
m68k allmodconfig gcc-16.1.0
m68k allnoconfig gcc-16.1.0
m68k allyesconfig gcc-16.1.0
microblaze allnoconfig gcc-16.1.0
microblaze allyesconfig gcc-16.1.0
mips allmodconfig gcc-16.1.0
mips allnoconfig gcc-16.1.0
mips allyesconfig gcc-16.1.0
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig gcc-11.5.0
openrisc allmodconfig gcc-16.1.0
openrisc allnoconfig gcc-16.1.0
parisc allmodconfig gcc-16.1.0
parisc allnoconfig gcc-16.1.0
parisc allyesconfig gcc-16.1.0
powerpc allmodconfig gcc-16.1.0
powerpc allnoconfig gcc-16.1.0
riscv allmodconfig clang-23
riscv allnoconfig gcc-16.1.0
riscv allyesconfig clang-23
s390 allmodconfig clang-23
s390 allnoconfig clang-23
s390 allyesconfig gcc-16.1.0
sh allmodconfig gcc-16.1.0
sh allnoconfig gcc-16.1.0
sh allyesconfig gcc-16.1.0
sparc allnoconfig gcc-16.1.0
sparc64 allmodconfig clang-20
um allmodconfig clang-23
um allnoconfig clang-16
um allyesconfig gcc-14
x86_64 allmodconfig clang-22
x86_64 allnoconfig clang-22
x86_64 allyesconfig clang-22
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-22
xtensa allnoconfig gcc-16.1.0
xtensa allyesconfig gcc-16.1.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] PCI: cadence: skip the link polling when endpoint not connected
From: Siddharth Vadapalli @ 2026-06-09 3:42 UTC (permalink / raw)
To: Aksh Garg
Cc: vigneshr, lpieralisi, kwilczynski, mani, robh, bhelgaas, mpillai,
unicorn_wang, me, 18255117159, linux-pci, linux-arm-kernel,
linux-kernel, danishanwar, s-vadapalli
In-Reply-To: <20260605071922.1724499-1-a-garg7@ti.com>
On 05/06/26 12:49, Aksh Garg wrote:
> cdns_pcie_host_wait_for_link() polls on link-up for 10 retries with a
> delay of 90-100ms each (~1 second). A call to cdns_pcie_host_link_setup()
> during the resume operation blocks the resume operation unnecessarily for
> ~1s even when no endpoint device is connected.
>
> Add skip_link_polling flag to track link state across suspend/resume
> cycles. If link was down before suspend, skip the expensive polling
> in resume since no endpoint was present.
>
> Signed-off-by: Aksh Garg <a-garg7@ti.com>
> ---
> drivers/pci/controller/cadence/pci-j721e.c | 5 +++++
> drivers/pci/controller/cadence/pcie-cadence-host-hpa.c | 3 +++
> drivers/pci/controller/cadence/pcie-cadence-host.c | 3 +++
> drivers/pci/controller/cadence/pcie-cadence.h | 3 +++
> 4 files changed, 14 insertions(+)
>
> diff --git a/drivers/pci/controller/cadence/pci-j721e.c b/drivers/pci/controller/cadence/pci-j721e.c
> index bfdfe98d5aba..849eb8bb9e45 100644
> --- a/drivers/pci/controller/cadence/pci-j721e.c
> +++ b/drivers/pci/controller/cadence/pci-j721e.c
> @@ -686,6 +686,11 @@ static int j721e_pcie_suspend_noirq(struct device *dev)
> struct j721e_pcie *pcie = dev_get_drvdata(dev);
>
> if (pcie->mode == PCI_MODE_RC) {
> + struct cdns_pcie_rc *rc = cdns_pcie_to_rc(pcie->cdns_pcie);
> +
> + /* If link is down before suspend, skip polling in resume */
> + rc->skip_link_polling = !j721e_pcie_link_up(pcie->cdns_pcie);
> +
Please consider the following scenario:
1. Endpoint device is not connected initially and we suspend with
'skip_link_polling' set to true.
2. We resume and skip link polling.
3. An Endpoint device is connected to the PCIe Connector a while later.
4. We perform a bus rescan, but with skip_link_polling still set (in
resume), wouldn't we skip polling for the link to be up?
Shouldn't 'skip_link_polling' be set to false on resume, after we skip the
polling initially, in order to allow waiting for a link up for a newly
connected device? Although this may not be done in practice and the
Endpoint device may always be left connected from the start, I am wondering
if we should clear 'skip_link_polling' on resume to account for the
aforementioned scenario. Let me know if I am overlooking something here.
> gpiod_set_value_cansleep(pcie->reset_gpio, 0);
> clk_disable_unprepare(pcie->refclk);
> }
[TRIMMED]
Regards,
Siddharth.
^ permalink raw reply
* [PATCH v4 net-next 9/9] net: dsa: netc: implement dynamic FDB entry ageing
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
The NETC switch does not age out dynamic FDB entries automatically.
Without software management, stale entries persist after topology
changes and cause incorrect forwarding.
Add a delayed work that periodically removes entries that have not been
refreshed within the specified cycles. The effective ageing time is:
ageing_time = fdbt_ageing_delay * 100
Default values are 3s interval and 100 cycles (300s total), matching
the IEEE 802.1Q default ageing time. The work starts when the first
port joins a bridge (tracked via br_cnt) and is cancelled when the
last port leaves. All FDB operations are serialized under fdbt_lock.
Implement .set_ageing_time() to allow the bridge layer to reconfigure
ageing parameters on demand.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 67 ++++++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_switch.h | 7 ++++
2 files changed, 74 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 299a9e76b9aa..c6082c6f8fd9 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -447,6 +447,25 @@ static void netc_free_ntmp_user(struct netc_switch *priv)
netc_free_ntmp_bitmaps(priv);
}
+static void netc_clean_fdbt_ageing_entries(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct netc_switch *priv;
+
+ priv = container_of(dwork, struct netc_switch, fdbt_ageing_work);
+
+ /* Update the activity element in FDB table */
+ mutex_lock(&priv->fdbt_lock);
+ ntmp_fdbt_update_activity_element(&priv->ntmp);
+ /* Delete the ageing entries after the activity element is updated */
+ ntmp_fdbt_delete_ageing_entries(&priv->ntmp, NETC_FDBT_AGEING_THRESH);
+ mutex_unlock(&priv->fdbt_lock);
+
+ if (atomic_read(&priv->br_cnt))
+ schedule_delayed_work(&priv->fdbt_ageing_work,
+ READ_ONCE(priv->fdbt_ageing_delay));
+}
+
static void netc_switch_dos_default_config(struct netc_switch *priv)
{
struct netc_switch_regs *regs = &priv->regs;
@@ -872,6 +891,10 @@ static int netc_setup(struct dsa_switch *ds)
INIT_HLIST_HEAD(&priv->fdb_list);
mutex_init(&priv->fdbt_lock);
+ priv->fdbt_ageing_delay = NETC_FDBT_AGEING_DELAY;
+ atomic_set(&priv->br_cnt, 0);
+ INIT_DELAYED_WORK(&priv->fdbt_ageing_work,
+ netc_clean_fdbt_ageing_entries);
INIT_HLIST_HEAD(&priv->vlan_list);
mutex_init(&priv->vft_lock);
@@ -936,6 +959,7 @@ static void netc_teardown(struct dsa_switch *ds)
{
struct netc_switch *priv = ds->priv;
+ disable_delayed_work_sync(&priv->fdbt_ageing_work);
netc_destroy_all_lists(priv);
netc_free_host_flood_rules(priv);
netc_free_ntmp_user(priv);
@@ -1970,6 +1994,7 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
struct netc_port *np = NETC_PORT(ds, port);
+ struct netc_switch *priv = ds->priv;
u16 vlan_unaware_pvid;
int err;
@@ -1997,6 +2022,10 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
out:
netc_port_remove_host_flood(np, np->host_flood);
+ if (atomic_inc_return(&priv->br_cnt) == 1)
+ schedule_delayed_work(&priv->fdbt_ageing_work,
+ READ_ONCE(priv->fdbt_ageing_delay));
+
return 0;
disable_mlo:
@@ -2023,6 +2052,7 @@ static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
{
struct netc_port *np = NETC_PORT(ds, port);
struct net_device *ndev = np->dp->user;
+ struct netc_switch *priv = ds->priv;
u16 vlan_unaware_pvid;
bool mc, uc;
@@ -2030,6 +2060,9 @@ static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
netc_port_set_pvid(np, NETC_STANDALONE_PVID);
np->pvid = NETC_STANDALONE_PVID;
+ if (atomic_dec_and_test(&priv->br_cnt))
+ cancel_delayed_work_sync(&priv->fdbt_ageing_work);
+
netc_port_remove_dynamic_entries(np);
uc = ndev->flags & IFF_PROMISC;
mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
@@ -2066,6 +2099,37 @@ static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
netc_port_del_vlan_entry(np, vlan_unaware_pvid);
}
+static int netc_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+ struct netc_switch *priv = ds->priv;
+ unsigned long delay_jiffies;
+
+ /* The dynamic FDB entry is deleted when its activity counter reaches
+ * NETC_FDBT_AGEING_THRESH (100). Each delayed_work tick increments
+ * the counter by 1 if the entry is inactive.
+ *
+ * Therefore:
+ * msecs (ms) = NETC_FDBT_AGEING_THRESH * delay_ms (ms)
+ * delay_ms = msecs / NETC_FDBT_AGEING_THRESH
+ * delay_jiffies = (delay_ms / 1000) * HZ
+ * = (msecs * HZ) / (1000 * NETC_FDBT_AGEING_THRESH)
+ *
+ * Use DIV_ROUND_CLOSEST_ULL to perform a single nearest-jiffy
+ * rounding, avoiding the two-step rounding error of the intermediate
+ * delay_ms approach.
+ * Maximum error = +/-0.5 jiffy * 100 = +/-50000/HZ ms.
+ */
+ delay_jiffies = DIV_ROUND_CLOSEST_ULL((u64)msecs * HZ,
+ 1000 * NETC_FDBT_AGEING_THRESH);
+ WRITE_ONCE(priv->fdbt_ageing_delay, delay_jiffies);
+
+ if (atomic_read(&priv->br_cnt))
+ mod_delayed_work(system_percpu_wq, &priv->fdbt_ageing_work,
+ READ_ONCE(priv->fdbt_ageing_delay));
+
+ return 0;
+}
+
static void netc_port_fast_age(struct dsa_switch *ds, int port)
{
struct netc_port *np = NETC_PORT(ds, port);
@@ -2357,6 +2421,7 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_vlan_del = netc_port_vlan_del,
.port_bridge_join = netc_port_bridge_join,
.port_bridge_leave = netc_port_bridge_leave,
+ .set_ageing_time = netc_set_ageing_time,
.port_fast_age = netc_port_fast_age,
.get_pause_stats = netc_port_get_pause_stats,
.get_rmon_stats = netc_port_get_rmon_stats,
@@ -2406,6 +2471,8 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->phylink_mac_ops = &netc_phylink_mac_ops;
ds->fdb_isolation = true;
ds->max_num_bridges = priv->info->num_ports - 1;
+ ds->ageing_time_min = 1000;
+ ds->ageing_time_max = U32_MAX;
ds->priv = priv;
priv->ds = ds;
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 982c8d3a3fbf..305f2a92e2f9 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -50,6 +50,9 @@
/* PAUSE refresh threshold: send refresh when timer reaches this value */
#define NETC_PAUSE_THRESH 0x7FFF
+#define NETC_FDBT_AGEING_DELAY (3 * HZ)
+#define NETC_FDBT_AGEING_THRESH 100
+
struct netc_switch;
struct netc_switch_info {
@@ -124,6 +127,10 @@ struct netc_switch {
struct ntmp_user ntmp;
struct hlist_head fdb_list;
struct mutex fdbt_lock; /* FDB table lock */
+ struct delayed_work fdbt_ageing_work;
+ /* (fdbt_ageing_delay * NETC_FDBT_AGEING_THRESH) is ageing time */
+ unsigned long fdbt_ageing_delay;
+ atomic_t br_cnt;
struct hlist_head vlan_list;
struct mutex vft_lock; /* VLAN filter table lock */
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 8/9] net: dsa: netc: add bridge mode support
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
Wire up the port_bridge_join, port_bridge_leave and port_vlan_filtering
DSA callbacks to support both VLAN-unaware and VLAN-aware bridge modes.
For VLAN-unaware bridges, each bridge instance is assigned a dedicated
internal PVID via NETC_VLAN_UNAWARE_PVID(bridge.num), counting down
from VID 4095. A VFT entry is created for this PVID with hardware MAC
learning and flood-on-miss forwarding enabled. The CPU port is included
as a VFT member so frames can reach the host. The reserved VID range is
blocked in port_vlan_add to prevent user-space conflicts.
Only one VLAN-aware bridge is supported at a time; this constraint is
enforced in port_bridge_join and port_vlan_filtering. The per-port PVID
is tracked in software and written to the BPDVR register whenever VLAN
filtering is active.
When a port leaves the bridge, its dynamic FDB entries are flushed right
away in port_bridge_leave(), without waiting for the ageing cycle. When
a link down event occurs on a port, netc_mac_link_down() will also clear
the port's dynamic FDB entries via netc_port_remove_dynamic_entries().
Non-bridge ports have no dynamic FDB entries, so this call is always
safe. Additionally, .port_fast_age() callback is added to flush the
dynamic FDB entries associated to a port.
Host flood rules are removed from the ingress port filter table when a
port joins a bridge to avoid bypassing FDB lookup and MAC learning.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 377 +++++++++++++++++++++++++++--
drivers/net/dsa/netc/netc_switch.h | 2 +
2 files changed, 363 insertions(+), 16 deletions(-)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index fcca22da08d8..299a9e76b9aa 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -509,6 +509,17 @@ 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_set_pvid(struct netc_port *np, u16 pvid)
+{
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_VID, pvid);
+}
+
+static void netc_port_set_vlan_aware(struct netc_port *np, bool aware)
+{
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM,
+ aware ? 0 : BPDVR_RXVAM);
+}
+
static void netc_port_fixed_config(struct netc_port *np)
{
/* Default IPV and DR setting */
@@ -534,7 +545,7 @@ 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);
+ netc_port_set_vlan_aware(np, false);
if (dsa_port_is_cpu(np->dp))
/* For CPU port, source port pruning is disabled */
@@ -695,10 +706,16 @@ static int netc_port_del_fdb_entry(struct netc_port *np,
entry = netc_lookup_fdb_entry(priv, addr, vid);
if (unlikely(!entry))
- /* Currently only single port mode is supported, MAC learning
- * is disabled, so there is no dynamically learned FDB entry.
- * We need to support deleting dynamically FDB entry when the
- * bridge mode is supported.
+ /* The hardware-learned dynamic FDB entries cannot be deleted
+ * through .port_fdb_del() interface.
+ * For NTF_MASTER path: Since hardware-learned dynamic FDB
+ * entries are never synchronized back to the bridge software
+ * database. br_fdb_delete() -> br_fdb_find() cannot find the
+ * FDB entry, so .port_fdb_del() will not be called.
+ * For NTF_SELF path: dsa_user_netdev_ops does not implement
+ * ndo_fdb_del(), so rtnl_fdb_del() falls back to
+ * ndo_dflt_fdb_del(), which only supports NUD_PERMANENT static
+ * entries and rejects all others with -EINVAL.
*/
goto unlock_fdbt;
@@ -1277,6 +1294,16 @@ static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
entry->ect_gid = NTMP_NULL_ENTRY_ID;
bitmap_stg = BIT(index) | VFT_STG_ID(0);
+ /* If the VID is a VLAN-unaware PVID, the CPU port needs to be
+ * a member of this VLAN.
+ */
+ if (dsa_port_is_user(np->dp) &&
+ vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+ struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+ bitmap_stg |= BIT(cpu_dp->index);
+ }
+
cfg = FIELD_PREP(VFT_MLO, MLO_HW) |
FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD);
@@ -1314,11 +1341,16 @@ static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
return err;
}
-static bool netc_port_vlan_egress_rule_changed(struct netc_vlan_entry *entry,
+static bool netc_port_vlan_egress_rule_changed(struct netc_switch *priv,
+ struct netc_vlan_entry *entry,
int port, bool untagged)
{
bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port));
+ /* VLAN-unaware VIDs have no egress rules, so return 'false' */
+ if (entry->vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges))
+ return false;
+
return old_untagged != untagged;
}
@@ -1341,7 +1373,8 @@ static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid,
}
/* Check whether the egress VLAN rule is changed */
- changed = netc_port_vlan_egress_rule_changed(entry, port, untagged);
+ changed = netc_port_vlan_egress_rule_changed(priv, entry, port,
+ untagged);
if (changed) {
entry->untagged_port_bitmap ^= BIT(port);
err = netc_port_update_vlan_egress_rule(np, entry);
@@ -1405,6 +1438,17 @@ static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid)
cfge = &entry->cfge;
vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP,
le32_to_cpu(cfge->bitmap_stg));
+ /* If the VID is a VLAN-unaware PVID, we need to clear the CPU
+ * port bit of vlan_port_bitmap, so that the VLAN entry can be
+ * deleted if no user ports use this VLAN.
+ */
+ if (dsa_port_is_user(np->dp) &&
+ vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+ struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+ vlan_port_bitmap &= ~BIT(cpu_dp->index);
+ }
+
/* If the VLAN only belongs to the current port */
if (vlan_port_bitmap == BIT(port)) {
err = ntmp_vft_delete_entry(&priv->ntmp, vid);
@@ -1510,17 +1554,50 @@ static int netc_port_max_mtu(struct dsa_switch *ds, int port)
return NETC_MAX_FRAME_LEN - VLAN_ETH_HLEN - ETH_FCS_LEN;
}
+static struct net_device *netc_classify_db(struct dsa_db db)
+{
+ switch (db.type) {
+ case DSA_DB_PORT:
+ return NULL;
+ case DSA_DB_BRIDGE:
+ return db.bridge.dev;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+}
+
+static u16 netc_vlan_unaware_pvid(struct dsa_bridge *bridge)
+{
+ u32 br_num;
+
+ if (!bridge)
+ return NETC_STANDALONE_PVID;
+
+ br_num = bridge->num;
+
+ /* The br_num is supposed to be 1 ~ ds->max_num_bridges, see
+ * dsa_bridge_num_get(). Since max_num_bridges is non-zero,
+ * so dsa_port_bridge_create() will return an error if
+ * dsa_bridge_num_get() returns 0.
+ */
+ if (WARN_ON(!br_num))
+ return NETC_STANDALONE_PVID;
+
+ return NETC_VLAN_UNAWARE_PVID(br_num);
+}
+
static int netc_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
+ struct net_device *br_ndev = netc_classify_db(db);
struct netc_port *np = NETC_PORT(ds, port);
- /* Currently, only support standalone port mode, so only
- * NETC_STANDALONE_PVID (= 0) is supported here.
- */
- if (vid != NETC_STANDALONE_PVID)
- return -EOPNOTSUPP;
+ if (IS_ERR(br_ndev))
+ return PTR_ERR(br_ndev);
+
+ if (!vid)
+ vid = netc_vlan_unaware_pvid(br_ndev ? &db.bridge : NULL);
return netc_port_set_fdb_entry(np, addr, vid);
}
@@ -1529,10 +1606,14 @@ static int netc_port_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
+ struct net_device *br_ndev = netc_classify_db(db);
struct netc_port *np = NETC_PORT(ds, port);
- if (vid != NETC_STANDALONE_PVID)
- return -EOPNOTSUPP;
+ if (IS_ERR(br_ndev))
+ return PTR_ERR(br_ndev);
+
+ if (!vid)
+ vid = netc_vlan_unaware_pvid(br_ndev ? &db.bridge : NULL);
return netc_port_del_fdb_entry(np, addr, vid);
}
@@ -1568,6 +1649,8 @@ static int netc_port_fdb_dump(struct dsa_switch *ds, int port,
cfg = le32_to_cpu(cfge->cfg);
is_static = (cfg & FDBT_DYNAMIC) ? false : true;
vid = le16_to_cpu(keye->fid);
+ if (vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges))
+ vid = 0;
err = cb(keye->mac_addr, vid, is_static, data);
if (err)
@@ -1670,12 +1753,23 @@ static void netc_port_remove_host_flood(struct netc_port *np,
struct ipft_entry_data *host_flood)
{
struct netc_switch *priv = np->switch_priv;
+ bool disable_host_flood = false;
if (!host_flood)
return;
+ if (np->host_flood == host_flood)
+ disable_host_flood = true;
+
ntmp_ipft_delete_entry(&priv->ntmp, host_flood->entry_id);
kfree(host_flood);
+
+ if (disable_host_flood) {
+ np->host_flood = NULL;
+ np->uc = false;
+ np->mc = false;
+ netc_port_wr(np, NETC_PIPFCR, 0);
+ }
}
static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
@@ -1684,6 +1778,17 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
struct netc_port *np = NETC_PORT(ds, port);
struct ipft_entry_data *old_host_flood;
+ /* Do not add host flood rule to ingress port filter table when
+ * the port has joined a bridge. Otherwise, the ingress frames
+ * will bypass FDB table lookup and MAC learning, so the frames
+ * will be redirected directly to the CPU port.
+ */
+ if (dsa_port_bridge_dev_get(np->dp)) {
+ netc_port_remove_host_flood(np, np->host_flood);
+
+ return;
+ }
+
if (np->uc == uc && np->mc == mc)
return;
@@ -1705,12 +1810,90 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
netc_port_remove_host_flood(np, old_host_flood);
}
+static int netc_single_vlan_aware_bridge(struct dsa_switch *ds,
+ struct netlink_ext_ack *extack)
+{
+ struct net_device *br_ndev = NULL;
+ struct dsa_port *dp;
+
+ dsa_switch_for_each_available_port(dp, ds) {
+ struct net_device *port_br = dsa_port_bridge_dev_get(dp);
+
+ if (!port_br || !br_vlan_enabled(port_br))
+ continue;
+
+ if (!br_ndev) {
+ br_ndev = port_br;
+ continue;
+ }
+
+ if (br_ndev == port_br)
+ continue;
+
+ NL_SET_ERR_MSG_MOD(extack,
+ "Only one VLAN-aware bridge is supported");
+
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int netc_port_vlan_filtering(struct dsa_switch *ds,
+ int port, bool vlan_aware,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ u16 pvid;
+ int err;
+
+ /* Before calling port_vlan_filtering(), br_vlan_filter_toggle() has
+ * already updated the BROPT_VLAN_ENABLED bit of br->options. So the
+ * VLAN filtering status of the switch ports can be checked by the
+ * br_vlan_enabled() function.
+ */
+ err = netc_single_vlan_aware_bridge(ds, extack);
+ if (err)
+ return err;
+
+ pvid = netc_vlan_unaware_pvid(np->dp->bridge);
+ if (pvid == NETC_STANDALONE_PVID) {
+ vlan_aware = false;
+ goto bpdvr_config;
+ }
+
+ if (vlan_aware) {
+ /* The FDB entries associated with unaware_pvid do not need
+ * to be deleted, so that when switching from VLAN-aware to
+ * VLAN-unaware mode, these FDB entries do not need to be
+ * re-added.
+ */
+ err = netc_port_del_vlan_entry(np, pvid);
+ if (err)
+ return err;
+
+ pvid = np->pvid;
+ } else {
+ err = netc_port_set_vlan_entry(np, pvid, false);
+ if (err)
+ return err;
+ }
+
+bpdvr_config:
+ netc_port_set_vlan_aware(np, vlan_aware);
+ netc_port_set_pvid(np, pvid);
+
+ return 0;
+}
+
static int netc_port_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
{
struct netc_port *np = NETC_PORT(ds, port);
+ struct dsa_port *dp = np->dp;
bool untagged;
+ int err;
/* The 8021q layer may attempt to change NETC_STANDALONE_PVID
* (VID 0), so we need to ignore it.
@@ -1718,20 +1901,176 @@ static int netc_port_vlan_add(struct dsa_switch *ds, int port,
if (vlan->vid == NETC_STANDALONE_PVID)
return 0;
+ if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "VID %d~4095 reserved for VLAN-unaware bridge",
+ NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges));
+ return -EINVAL;
+ }
+
untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+ err = netc_port_set_vlan_entry(np, vlan->vid, untagged);
+ if (err)
+ return err;
+
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
+ np->pvid = vlan->vid;
+ if (dsa_port_is_vlan_filtering(dp))
+ netc_port_set_pvid(np, vlan->vid);
- return netc_port_set_vlan_entry(np, vlan->vid, untagged);
+ return 0;
+ }
+
+ if (np->pvid != vlan->vid)
+ return 0;
+
+ /* Delete PVID */
+ np->pvid = NETC_STANDALONE_PVID;
+ if (dsa_port_is_vlan_filtering(dp))
+ netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+
+ return 0;
}
static int netc_port_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct netc_port *np = NETC_PORT(ds, port);
+ int err;
if (vlan->vid == NETC_STANDALONE_PVID)
return 0;
- return netc_port_del_vlan_entry(np, vlan->vid);
+ if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges))
+ return -EINVAL;
+
+ err = netc_port_del_vlan_entry(np, vlan->vid);
+ if (err)
+ return err;
+
+ if (np->pvid == vlan->vid) {
+ np->pvid = NETC_STANDALONE_PVID;
+
+ /* Set the port PVID to NETC_STANDALONE_PVID if the VLAN-aware
+ * bridge port has no PVID. The untagged frames will not be
+ * forwarded to other user ports, as NETC_STANDALONE_PVID VLAN
+ * entry has disabled MAC learning and flooding, and other user
+ * ports do not have FDB entries with NETC_STANDALONE_PVID.
+ */
+ if (dsa_port_is_vlan_filtering(np->dp))
+ netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+ }
+
+ return 0;
+}
+
+static int netc_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ u16 vlan_unaware_pvid;
+ int err;
+
+ if (!bridge.num) {
+ NL_SET_ERR_MSG_MOD(extack, "Bridge number 0 is unsupported");
+ return -EINVAL;
+ }
+
+ err = netc_single_vlan_aware_bridge(ds, extack);
+ if (err)
+ return err;
+
+ netc_port_set_mlo(np, MLO_NOT_OVERRIDE);
+
+ if (br_vlan_enabled(bridge.dev))
+ goto out;
+
+ vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+ err = netc_port_set_vlan_entry(np, vlan_unaware_pvid, false);
+ if (err)
+ goto disable_mlo;
+
+ netc_port_set_pvid(np, vlan_unaware_pvid);
+
+out:
+ netc_port_remove_host_flood(np, np->host_flood);
+
+ return 0;
+
+disable_mlo:
+ netc_port_set_mlo(np, MLO_DISABLE);
+
+ return err;
+}
+
+static void netc_port_remove_dynamic_entries(struct netc_port *np)
+{
+ struct netc_switch *priv = np->switch_priv;
+
+ /* Return if the port is not available */
+ if (!np->dp)
+ return;
+
+ mutex_lock(&priv->fdbt_lock);
+ ntmp_fdbt_delete_port_dynamic_entries(&priv->ntmp, np->dp->index);
+ mutex_unlock(&priv->fdbt_lock);
+}
+
+static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev = np->dp->user;
+ u16 vlan_unaware_pvid;
+ bool mc, uc;
+
+ netc_port_set_mlo(np, MLO_DISABLE);
+ netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+ np->pvid = NETC_STANDALONE_PVID;
+
+ netc_port_remove_dynamic_entries(np);
+ uc = ndev->flags & IFF_PROMISC;
+ mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
+
+ if (netc_port_add_host_flood_rule(np, uc, mc))
+ dev_warn(ds->dev,
+ "Failed to restore host flood rule on port %d\n",
+ port);
+
+ /* When a port leaves a VLAN-aware bridge, dsa_port_bridge_leave()
+ * follows the sequence below:
+ *
+ * 1. dsa_port_bridge_destroy() is called to set dp->bridge to NULL.
+ * 2. dsa_broadcast() is called, which eventually invokes
+ * ds->ops->port_bridge_leave()
+ * 3. dsa_port_switchdev_unsync_attrs() is called, which triggers
+ * dsa_port_reset_vlan_filtering() and ultimately calls
+ * ds->ops->port_vlan_filtering() to transition the port from
+ * VLAN-aware mode to VLAN-unaware mode.
+ *
+ * At step 3, since dp->bridge has already been set to NULL in step 1,
+ * netc_port_vlan_filtering() will detect this and skip the creation
+ * of an unaware PVID entry in the VLAN filter table. Therefore, it is
+ * safe to return directly here.
+ */
+ if (br_vlan_enabled(bridge.dev))
+ return;
+
+ vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+ /* There is no need to check the return value even if it fails.
+ * Because the PVID has been set to NETC_STANDALONE_PVID, the
+ * frames will not match this VLAN entry.
+ */
+ netc_port_del_vlan_entry(np, vlan_unaware_pvid);
+}
+
+static void netc_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ netc_port_remove_dynamic_entries(np);
}
static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -1988,6 +2327,7 @@ static void netc_mac_link_down(struct phylink_config *config,
np = NETC_PORT(dp->ds, dp->index);
netc_port_mac_rx_graceful_stop(np);
netc_port_mac_tx_graceful_stop(np);
+ netc_port_remove_dynamic_entries(np);
}
static const struct phylink_mac_ops netc_phylink_mac_ops = {
@@ -2012,8 +2352,12 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_mdb_add = netc_port_mdb_add,
.port_mdb_del = netc_port_mdb_del,
.port_set_host_flood = netc_port_set_host_flood,
+ .port_vlan_filtering = netc_port_vlan_filtering,
.port_vlan_add = netc_port_vlan_add,
.port_vlan_del = netc_port_vlan_del,
+ .port_bridge_join = netc_port_bridge_join,
+ .port_bridge_leave = netc_port_bridge_leave,
+ .port_fast_age = netc_port_fast_age,
.get_pause_stats = netc_port_get_pause_stats,
.get_rmon_stats = netc_port_get_rmon_stats,
.get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
@@ -2061,6 +2405,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->ops = &netc_switch_ops;
ds->phylink_mac_ops = &netc_phylink_mac_ops;
ds->fdb_isolation = true;
+ ds->max_num_bridges = priv->info->num_ports - 1;
ds->priv = priv;
priv->ds = ds;
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 9ff334301fbc..982c8d3a3fbf 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -33,6 +33,7 @@
#define NETC_MAX_FRAME_LEN 9600
#define NETC_STANDALONE_PVID 0
+#define NETC_VLAN_UNAWARE_PVID(br_id) (4096 - (br_id))
/* Threshold format: MANT (bits 11:4) * 2^EXP (bits 3:0)
* Unit: Memory words (average of 20 bytes each)
@@ -79,6 +80,7 @@ struct netc_port {
u16 enable:1;
u16 uc:1;
u16 mc:1;
+ u16 pvid;
struct ipft_entry_data *host_flood;
};
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 7/9] net: dsa: netc: add VLAN filter table and egress treatment management
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
Implement the DSA .port_vlan_add and .port_vlan_del operations to enable
VLAN-aware bridge offloading on the NETC switch.
VLAN membership is maintained in the VLAN Filter Table (VFT). Adding the
first port to a VLAN creates a new VFT entry with hardware MAC learning
and flood-on-miss forwarding; subsequent ports update the existing
entry's membership bitmap. Removing the last port deletes the entry.
Egress tagging is handled through the Egress Treatment Table (ETT). Each
VLAN is allocated a group of ETT entries, one per available port. Ports
are assigned a sequential ett_offset during initialisation, used to
address each port's entry within the group. Untagged ports configure the
ETT to strip the outer VLAN tag; tagged ports pass frames through
unmodified. Each ETT group is optionally paired with an Egress Counter
Table (ECT) group for per-port frame counting, allocated on a best-effort
basis. When the egress rule of an ETT entry changes, the counter of the
corresponding ECT entry will be recounted to track the number of frames
that match the new egress rule.
A software shadow list serialised by vft_lock tracks active VLAN state
across both port membership and egress tagging. VID 0 is used for single
port mode and is ignored by both callbacks.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 439 +++++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_switch.h | 24 ++
include/linux/fsl/ntmp.h | 15 +
3 files changed, 478 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index d4475ad7ed6c..fcca22da08d8 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -37,6 +37,27 @@ static void netc_destroy_fdb_list(struct netc_switch *priv)
netc_del_fdb_entry(entry);
}
+static struct netc_vlan_entry *
+netc_lookup_vlan_entry(struct netc_switch *priv, u16 vid)
+{
+ struct netc_vlan_entry *entry;
+
+ hlist_for_each_entry(entry, &priv->vlan_list, node)
+ if (entry->vid == vid)
+ return entry;
+
+ return NULL;
+}
+
+static void netc_destroy_vlan_list(struct netc_switch *priv)
+{
+ struct netc_vlan_entry *entry;
+ struct hlist_node *tmp;
+
+ hlist_for_each_entry_safe(entry, tmp, &priv->vlan_list, node)
+ netc_del_vlan_entry(entry);
+}
+
static enum dsa_tag_protocol
netc_get_tag_protocol(struct dsa_switch *ds, int port,
enum dsa_tag_protocol mprot)
@@ -222,6 +243,7 @@ static int netc_init_all_ports(struct netc_switch *priv)
struct device *dev = priv->dev;
struct netc_port *np;
struct dsa_port *dp;
+ int ett_offset = 0;
int err;
priv->ports = devm_kcalloc(dev, priv->info->num_ports,
@@ -251,6 +273,8 @@ static int netc_init_all_ports(struct netc_switch *priv)
dsa_switch_for_each_available_port(dp, priv->ds) {
np = priv->ports[dp->index];
np->dp = dp;
+ np->ett_offset = ett_offset++;
+ priv->port_bitmap |= BIT(dp->index);
err = netc_port_get_info_from_dt(np, dp->dn, dev);
if (err)
@@ -831,6 +855,8 @@ static int netc_setup(struct dsa_switch *ds)
INIT_HLIST_HEAD(&priv->fdb_list);
mutex_init(&priv->fdbt_lock);
+ INIT_HLIST_HEAD(&priv->vlan_list);
+ mutex_init(&priv->vft_lock);
netc_switch_fixed_config(priv);
@@ -858,6 +884,7 @@ static int netc_setup(struct dsa_switch *ds)
* hardware state.
*/
mutex_destroy(&priv->fdbt_lock);
+ mutex_destroy(&priv->vft_lock);
netc_free_ntmp_user(priv);
return err;
@@ -867,6 +894,8 @@ static void netc_destroy_all_lists(struct netc_switch *priv)
{
netc_destroy_fdb_list(priv);
mutex_destroy(&priv->fdbt_lock);
+ netc_destroy_vlan_list(priv);
+ mutex_destroy(&priv->vft_lock);
}
static void netc_free_host_flood_rules(struct netc_switch *priv)
@@ -1025,6 +1054,385 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv)
priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
}
+static void netc_init_ett_cfge(struct ett_cfge_data *cfge,
+ bool untagged, u32 ect_eid)
+{
+ u32 vuda_sqta = FMTEID_VUDA_SQTA;
+ u16 efm_cfg = 0;
+
+ if (ect_eid != NTMP_NULL_ENTRY_ID) {
+ /* Increase egress frame counter */
+ efm_cfg |= FIELD_PREP(ETT_ECA, ETT_ECA_INC);
+ cfge->ec_eid = cpu_to_le32(ect_eid);
+ }
+
+ /* If egress rule is VLAN untagged */
+ if (untagged) {
+ /* delete outer VLAN tag */
+ vuda_sqta |= FIELD_PREP(FMTEID_VUDA, FMTEID_VUDA_DEL_OTAG);
+ /* length change: twos-complement notation */
+ efm_cfg |= FIELD_PREP(ETT_EFM_LEN_CHANGE,
+ ETT_FRM_LEN_DEL_VLAN);
+ }
+
+ cfge->efm_eid = cpu_to_le32(vuda_sqta);
+ cfge->efm_cfg = cpu_to_le16(efm_cfg);
+}
+
+static int netc_add_ett_entry(struct netc_switch *priv, bool untagged,
+ u32 ett_eid, u32 ect_eid)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+ struct ett_cfge_data cfge = {};
+
+ netc_init_ett_cfge(&cfge, untagged, ect_eid);
+
+ return ntmp_ett_add_entry(ntmp, ett_eid, &cfge);
+}
+
+static int netc_update_ett_entry(struct netc_switch *priv, bool untagged,
+ u32 ett_eid, u32 ect_eid)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+ struct ett_cfge_data cfge = {};
+
+ netc_init_ett_cfge(&cfge, untagged, ect_eid);
+
+ return ntmp_ett_update_entry(ntmp, ett_eid, &cfge);
+}
+
+static int netc_add_ett_group_entries(struct netc_switch *priv,
+ u32 untagged_port_bitmap,
+ u32 ett_base_eid,
+ u32 ect_base_eid)
+{
+ struct netc_port **ports = priv->ports;
+ u32 ett_eid, ect_eid;
+ bool untagged;
+ int i, err;
+
+ for (i = 0; i < priv->info->num_ports; i++) {
+ if (!ports[i]->dp)
+ continue;
+
+ untagged = !!(untagged_port_bitmap & BIT(i));
+ ett_eid = ett_base_eid + ports[i]->ett_offset;
+ ect_eid = NTMP_NULL_ENTRY_ID;
+ if (ect_base_eid != NTMP_NULL_ENTRY_ID)
+ ect_eid = ect_base_eid + ports[i]->ett_offset;
+
+ err = netc_add_ett_entry(priv, untagged, ett_eid, ect_eid);
+ if (err)
+ goto clear_ett_entries;
+ }
+
+ return 0;
+
+clear_ett_entries:
+ while (--i >= 0) {
+ if (!ports[i]->dp)
+ continue;
+
+ ett_eid = ett_base_eid + ports[i]->ett_offset;
+ ntmp_ett_delete_entry(&priv->ntmp, ett_eid);
+ }
+
+ return err;
+}
+
+static int netc_add_vlan_egress_rule(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 ect_eid = NTMP_NULL_ENTRY_ID;
+ u32 ett_eid, ett_gid, ect_gid;
+ int err;
+
+ /* Step 1: Find available egress counter table entries and update
+ * these entries.
+ */
+ ect_gid = ntmp_lookup_free_eid(ntmp->ect_gid_bitmap,
+ ntmp->ect_bitmap_size);
+ if (ect_gid == NTMP_NULL_ENTRY_ID) {
+ dev_info(priv->dev,
+ "No egress counter table entries available\n");
+ } else {
+ ect_eid = ect_gid * num_ports;
+ for (int i = 0; i < num_ports; i++)
+ /* There is no need to check the return value, the only
+ * issue is that the entry's counter might be inaccurate,
+ * but it will not affect the functionality, it is only
+ * for future debugging.
+ */
+ ntmp_ect_update_entry(ntmp, ect_eid + i);
+ }
+
+ /* Step 2: Find available egress treatment table entries and add
+ * these entries.
+ */
+ ett_gid = ntmp_lookup_free_eid(ntmp->ett_gid_bitmap,
+ ntmp->ett_bitmap_size);
+ if (ett_gid == NTMP_NULL_ENTRY_ID) {
+ dev_err(priv->dev,
+ "No egress treatment table entries available\n");
+ err = -ENOSPC;
+ goto clear_ect_gid;
+ }
+
+ ett_eid = ett_gid * num_ports;
+ err = netc_add_ett_group_entries(priv, entry->untagged_port_bitmap,
+ ett_eid, ect_eid);
+ if (err)
+ goto clear_ett_gid;
+
+ entry->cfge.et_eid = cpu_to_le32(ett_eid);
+ entry->ect_gid = ect_gid;
+
+ return 0;
+
+clear_ett_gid:
+ ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+
+clear_ect_gid:
+ if (ect_gid != NTMP_NULL_ENTRY_ID)
+ ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, ect_gid);
+
+ return err;
+}
+
+static void netc_delete_vlan_egress_rule(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 ett_eid, ett_gid;
+
+ ett_eid = le32_to_cpu(entry->cfge.et_eid);
+ if (ett_eid == NTMP_NULL_ENTRY_ID)
+ return;
+
+ ett_gid = ett_eid / num_ports;
+ ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+ for (int i = 0; i < num_ports; i++)
+ ntmp_ett_delete_entry(ntmp, ett_eid + i);
+
+ if (entry->ect_gid == NTMP_NULL_ENTRY_ID)
+ return;
+
+ ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, entry->ect_gid);
+}
+
+static int netc_port_update_vlan_egress_rule(struct netc_port *np,
+ struct netc_vlan_entry *entry)
+{
+ bool untagged = !!(entry->untagged_port_bitmap & BIT(np->dp->index));
+ u32 num_ports = netc_num_available_ports(np->switch_priv);
+ u32 ett_eid = le32_to_cpu(entry->cfge.et_eid);
+ struct netc_switch *priv = np->switch_priv;
+ u32 ect_eid = NTMP_NULL_ENTRY_ID;
+ int err;
+
+ if (ett_eid == NTMP_NULL_ENTRY_ID)
+ return 0;
+
+ if (entry->ect_gid != NTMP_NULL_ENTRY_ID)
+ /* Each ETT entry maps to an ECT entry if ect_gid is not NULL
+ * entry ID. The offset of the ECT entry corresponding to the
+ * port in the group is equal to ett_offset.
+ */
+ ect_eid = entry->ect_gid * num_ports + np->ett_offset;
+
+ ett_eid += np->ett_offset;
+ err = netc_update_ett_entry(priv, untagged, ett_eid, ect_eid);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to update VLAN %u egress rule on port %d\n",
+ entry->vid, np->dp->index);
+ return err;
+ }
+
+ if (ett_eid != NTMP_NULL_ENTRY_ID)
+ ntmp_ect_update_entry(&priv->ntmp, ect_eid);
+
+ return 0;
+}
+
+static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
+ bool untagged)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ u32 index = np->dp->index;
+ u32 bitmap_stg;
+ int err;
+ u16 cfg;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->vid = vid;
+ entry->ect_gid = NTMP_NULL_ENTRY_ID;
+
+ bitmap_stg = BIT(index) | VFT_STG_ID(0);
+ cfg = FIELD_PREP(VFT_MLO, MLO_HW) |
+ FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD);
+
+ cfge = &entry->cfge;
+ cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ cfge->bitmap_stg = cpu_to_le32(bitmap_stg);
+ cfge->fid = cpu_to_le16(vid);
+ cfge->cfg = cpu_to_le16(cfg);
+ cfge->eta_port_bitmap = cpu_to_le32(priv->port_bitmap);
+
+ if (untagged)
+ entry->untagged_port_bitmap = BIT(index);
+
+ err = netc_add_vlan_egress_rule(priv, entry);
+ if (err)
+ goto free_vlan_entry;
+
+ err = ntmp_vft_add_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to add VLAN %u entry on port %d\n",
+ vid, index);
+ goto delete_vlan_egress_rule;
+ }
+
+ netc_add_vlan_entry(priv, entry);
+
+ return 0;
+
+delete_vlan_egress_rule:
+ netc_delete_vlan_egress_rule(priv, entry);
+free_vlan_entry:
+ kfree(entry);
+
+ return err;
+}
+
+static bool netc_port_vlan_egress_rule_changed(struct netc_vlan_entry *entry,
+ int port, bool untagged)
+{
+ bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port));
+
+ return old_untagged != untagged;
+}
+
+static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid,
+ bool untagged)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ int port = np->dp->index;
+ bool changed;
+ int err = 0;
+
+ mutex_lock(&priv->vft_lock);
+
+ entry = netc_lookup_vlan_entry(priv, vid);
+ if (!entry) {
+ err = netc_port_add_vlan_entry(np, vid, untagged);
+ goto unlock_vft;
+ }
+
+ /* Check whether the egress VLAN rule is changed */
+ changed = netc_port_vlan_egress_rule_changed(entry, port, untagged);
+ if (changed) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ err = netc_port_update_vlan_egress_rule(np, entry);
+ if (err) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ goto unlock_vft;
+ }
+ }
+
+ cfge = &entry->cfge;
+ if (cfge->bitmap_stg & cpu_to_le32(BIT(port)))
+ goto unlock_vft;
+
+ cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+ err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to update VLAN %u entry on port %d\n",
+ vid, port);
+
+ goto restore_bitmap_stg;
+ }
+
+ mutex_unlock(&priv->vft_lock);
+
+ return 0;
+
+restore_bitmap_stg:
+ cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+ if (changed) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ /* Recover the corresponding ETT entry. It doesn't matter
+ * if it fails because the bit corresponding to the port
+ * in the port bitmap of the VFT entry is not set. so the
+ * frame will not match that ETT entry.
+ */
+ if (netc_port_update_vlan_egress_rule(np, entry))
+ entry->untagged_port_bitmap ^= BIT(port);
+ }
+unlock_vft:
+ mutex_unlock(&priv->vft_lock);
+
+ return err;
+}
+
+static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ int port = np->dp->index;
+ u32 vlan_port_bitmap;
+ int err = 0;
+
+ mutex_lock(&priv->vft_lock);
+
+ entry = netc_lookup_vlan_entry(priv, vid);
+ if (!entry)
+ goto unlock_vft;
+
+ cfge = &entry->cfge;
+ vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP,
+ le32_to_cpu(cfge->bitmap_stg));
+ /* If the VLAN only belongs to the current port */
+ if (vlan_port_bitmap == BIT(port)) {
+ err = ntmp_vft_delete_entry(&priv->ntmp, vid);
+ if (err)
+ goto unlock_vft;
+
+ netc_delete_vlan_egress_rule(priv, entry);
+ netc_del_vlan_entry(entry);
+
+ goto unlock_vft;
+ }
+
+ if (!(vlan_port_bitmap & BIT(port)))
+ goto unlock_vft;
+
+ cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+ err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+ goto unlock_vft;
+ }
+
+unlock_vft:
+ mutex_unlock(&priv->vft_lock);
+
+ return err;
+}
+
static int netc_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phy)
{
@@ -1297,6 +1705,35 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
netc_port_remove_host_flood(np, old_host_flood);
}
+static int netc_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ bool untagged;
+
+ /* The 8021q layer may attempt to change NETC_STANDALONE_PVID
+ * (VID 0), so we need to ignore it.
+ */
+ if (vlan->vid == NETC_STANDALONE_PVID)
+ return 0;
+
+ untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+
+ return netc_port_set_vlan_entry(np, vlan->vid, untagged);
+}
+
+static int netc_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ if (vlan->vid == NETC_STANDALONE_PVID)
+ return 0;
+
+ return netc_port_del_vlan_entry(np, vlan->vid);
+}
+
static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
@@ -1575,6 +2012,8 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_mdb_add = netc_port_mdb_add,
.port_mdb_del = netc_port_mdb_del,
.port_set_host_flood = netc_port_set_host_flood,
+ .port_vlan_add = netc_port_vlan_add,
+ .port_vlan_del = netc_port_vlan_del,
.get_pause_stats = netc_port_get_pause_stats,
.get_rmon_stats = netc_port_get_rmon_stats,
.get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 4fbd12825b67..9ff334301fbc 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -74,6 +74,7 @@ struct netc_port {
struct dsa_port *dp;
struct clk *ref_clk; /* RGMII/RMII reference clock */
struct mii_bus *emdio;
+ int ett_offset;
u16 enable:1;
u16 uc:1;
@@ -94,6 +95,14 @@ struct netc_fdb_entry {
struct hlist_node node;
};
+struct netc_vlan_entry {
+ u16 vid;
+ u32 ect_gid;
+ u32 untagged_port_bitmap;
+ struct vft_cfge_data cfge;
+ struct hlist_node node;
+};
+
struct netc_port_stat {
int reg;
char name[ETH_GSTRING_LEN] __nonstring;
@@ -108,10 +117,13 @@ struct netc_switch {
const struct netc_switch_info *info;
struct netc_switch_regs regs;
struct netc_port **ports;
+ u32 port_bitmap; /* bitmap of available ports */
struct ntmp_user ntmp;
struct hlist_head fdb_list;
struct mutex fdbt_lock; /* FDB table lock */
+ struct hlist_head vlan_list;
+ struct mutex vft_lock; /* VLAN filter table lock */
/* Switch hardware capabilities */
u32 htmcapr_num_words;
@@ -153,6 +165,18 @@ static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry)
kfree(entry);
}
+static inline void netc_add_vlan_entry(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ hlist_add_head(&entry->node, &priv->vlan_list);
+}
+
+static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry)
+{
+ hlist_del(&entry->node);
+ kfree(entry);
+}
+
int netc_switch_platform_probe(struct netc_switch *priv);
/* ethtool APIs */
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 4d329488763d..d3b6c476b91a 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -262,6 +262,21 @@ struct bpt_cfge_data {
__le32 fc_ports;
};
+union ntmp_fmt_eid {
+ __le32 index;
+#define FMTEID_INDEX GENMASK(12, 0)
+ __le32 vuda_sqta;
+#define FMTEID_VUDA GENMASK(1, 0)
+#define FMTEID_VUDA_DEL_OTAG 2
+#define FMTEID_SQTA GENMASK(4, 2)
+#define FMTEID_SQTA_DEL 2
+#define FMTEID_VUDA_SQTA BIT(13)
+ __le32 vara_vid;
+#define FMTEID_VID GENMASK(11, 0)
+#define FMTEID_VARA GENMASK(13, 12)
+#define FMTEID_VARA_VID BIT(14)
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 6/9] net: enetc: add helpers to set/clear table bitmap
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
NTMP index tables require software to allocate and manage entry IDs.
Add two bitmap helper functions to facilitate this management:
ntmp_lookup_free_eid(): finds the first zero bit in the given bitmap,
sets it to mark the entry as in-use, and returns the corresponding entry
ID. Returns NTMP_NULL_ENTRY_ID if no free entry is available.
ntmp_clear_eid_bitmap(): clears the bit associated with the given entry
ID in the bitmap to mark the entry as free. It is a no-op if the entry
ID is NTMP_NULL_ENTRY_ID.
Both functions are exported for use by other modules, such as the NETC
switch driver which needs to manage group index bitmaps for the Egress
Treatment Table (ETT) and Egress Count Table (ECT).
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 24 +++++++++++++++++++++
include/linux/fsl/ntmp.h | 2 ++
2 files changed, 26 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 601435966ed1..9f38f885ebb5 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -47,6 +47,30 @@
#define RSST_STSE_DATA_SIZE(n) ((n) * 8)
#define RSST_CFGE_DATA_SIZE(n) (n)
+u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size)
+{
+ u32 entry_id;
+
+ entry_id = find_first_zero_bit(bitmap, size);
+ if (entry_id == size)
+ return NTMP_NULL_ENTRY_ID;
+
+ /* Set the bit once we found it */
+ set_bit(entry_id, bitmap);
+
+ return entry_id;
+}
+EXPORT_SYMBOL_GPL(ntmp_lookup_free_eid);
+
+void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id)
+{
+ if (entry_id == NTMP_NULL_ENTRY_ID)
+ return;
+
+ clear_bit(entry_id, bitmap);
+}
+EXPORT_SYMBOL_GPL(ntmp_clear_eid_bitmap);
+
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
{
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index e8b1bd802f19..4d329488763d 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -266,6 +266,8 @@ struct bpt_cfge_data {
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
void ntmp_free_cbdr(struct netc_cbdr *cbdr);
+u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size);
+void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id);
/* NTMP APIs */
int ntmp_maft_add_entry(struct ntmp_user *user, u32 entry_id,
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 5/9] net: dsa: netc: initialize the group bitmap of ETT and ECT
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
The Egress Treatment Table (ETT) and Egress Count Table (ECT) are both
index tables whose entry IDs are allocated by software. Every num_ports
entries form a group, where each entry in the group corresponds to one
port. To facilitate group allocation and management, initialize the group
index bitmaps for both tables based on hardware capabilities reported by
ETTCAPR and ECTCAPR registers.
The bitmap size per table is calculated as the total number of hardware
entries divided by the number of available ports, which gives the number
of groups available for software allocation. A set bit in the bitmap
represents a group index that has been allocated.
These bitmaps will be used by subsequent patches that add VLAN support.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 90 ++++++++++++++++++++++++++-
drivers/net/dsa/netc/netc_switch_hw.h | 6 ++
include/linux/fsl/ntmp.h | 7 +++
3 files changed, 102 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index fa7dd307ce13..d4475ad7ed6c 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -323,16 +323,104 @@ static void netc_remove_all_cbdrs(struct netc_switch *priv)
ntmp_free_cbdr(&ntmp->ring[i]);
}
+static u32 netc_num_available_ports(struct netc_switch *priv)
+{
+ struct dsa_port *dp;
+ u32 num_ports = 0;
+
+ dsa_switch_for_each_available_port(dp, priv->ds)
+ num_ports++;
+
+ return num_ports;
+}
+
+static int netc_init_ntmp_bitmap_sizes(struct netc_switch *priv)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct netc_switch_regs *regs = &priv->regs;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 val;
+
+ if (!num_ports)
+ return -EINVAL;
+
+ val = netc_base_rd(regs, NETC_ETTCAPR);
+ ntmp->ett_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports;
+ if (!ntmp->ett_bitmap_size)
+ return -EINVAL;
+
+ val = netc_base_rd(regs, NETC_ECTCAPR);
+ ntmp->ect_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports;
+ if (!ntmp->ect_bitmap_size)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int netc_init_ntmp_bitmaps(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ ntmp->ett_gid_bitmap = bitmap_zalloc(ntmp->ett_bitmap_size,
+ GFP_KERNEL);
+ if (!ntmp->ett_gid_bitmap)
+ return -ENOMEM;
+
+ ntmp->ect_gid_bitmap = bitmap_zalloc(ntmp->ect_bitmap_size,
+ GFP_KERNEL);
+ if (!ntmp->ect_gid_bitmap)
+ goto free_ett_gid_bitmap;
+
+ return 0;
+
+free_ett_gid_bitmap:
+ bitmap_free(ntmp->ett_gid_bitmap);
+ ntmp->ett_gid_bitmap = NULL;
+
+ return -ENOMEM;
+}
+
+static void netc_free_ntmp_bitmaps(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ bitmap_free(ntmp->ect_gid_bitmap);
+ ntmp->ect_gid_bitmap = NULL;
+
+ bitmap_free(ntmp->ett_gid_bitmap);
+ ntmp->ett_gid_bitmap = NULL;
+}
+
static int netc_init_ntmp_user(struct netc_switch *priv)
{
+ int err;
+
netc_init_ntmp_tbl_versions(priv);
- return netc_init_all_cbdrs(priv);
+ err = netc_init_ntmp_bitmap_sizes(priv);
+ if (err)
+ return err;
+
+ err = netc_init_ntmp_bitmaps(priv);
+ if (err)
+ return err;
+
+ err = netc_init_all_cbdrs(priv);
+ if (err)
+ goto free_ntmp_bitmaps;
+
+ return 0;
+
+free_ntmp_bitmaps:
+ netc_free_ntmp_bitmaps(priv);
+
+ return err;
}
static void netc_free_ntmp_user(struct netc_switch *priv)
{
netc_remove_all_cbdrs(priv);
+ netc_free_ntmp_bitmaps(priv);
}
static void netc_switch_dos_default_config(struct netc_switch *priv)
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index 1d976882a6cc..1404ae41c7bc 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -36,6 +36,12 @@
#define DOSL3CR_SAMEADDR BIT(0)
#define DOSL3CR_IPSAMCC BIT(1)
+#define NETC_ETTCAPR 0x18c4
+#define NETC_ECTCAPR 0x18ec
+/* Index table NUM_ENTRIES mask */
+#define NETC_NUM_ENTRIES GENMASK(15, 0)
+#define NETC_GET_NUM_ENTRIES(v) FIELD_GET(NETC_NUM_ENTRIES, (v))
+
/* Hash table memory capability register, the memory is shared by
* the following tables:
*
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 1222901f48a7..e8b1bd802f19 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -3,6 +3,7 @@
#ifndef __NETC_NTMP_H
#define __NETC_NTMP_H
+#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/if_ether.h>
@@ -70,6 +71,12 @@ struct ntmp_user {
struct device *dev;
struct netc_cbdr *ring;
struct netc_tbl_vers tbl;
+
+ /* NTMP table bitmaps for resource management */
+ u32 ett_bitmap_size;
+ u32 ect_bitmap_size;
+ unsigned long *ett_gid_bitmap; /* only valid for switch */
+ unsigned long *ect_gid_bitmap; /* only valid for switch */
};
struct maft_entry_data {
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 4/9] net: enetc: add "Update" operation to the egress count table
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
The egress count table is a static bounded index table, egress related
statistics are maintained in this table. The table is implemented as a
linear array of entries accessed using an index (0, 1, 2, ..., n) that
uniquely identifies an entry within the array. Egress Counter Entry ID
(EC_EID) is used as an index to an entry in this table. The EC_EID is
specified in the egress treatment table.
Egress count table entries are always present and enabled. The table
only supports access via entry ID, which is assigned by the software.
And it supports Update, Query and Query followed by Update operations.
Currently, only Update operation is supported.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 45 +++++++++++++++++++++
include/linux/fsl/ntmp.h | 2 +
2 files changed, 47 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 9249f78219ed..601435966ed1 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -25,6 +25,7 @@
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
#define NTMP_ETT_ID 33
+#define NTMP_ECT_ID 39
#define NTMP_BPT_ID 41
/* Generic Update Actions for most tables */
@@ -33,6 +34,7 @@
/* Specific Update Actions for some tables */
#define FDBT_UA_ACTEU BIT(1)
+#define ECT_UA_STSEU BIT(0)
#define BPT_UA_BPSEU BIT(1)
/* Query Action: 0: Full query. 1: Query entry ID, the fields after entry
@@ -287,6 +289,8 @@ static const char *ntmp_table_name(int tbl_id)
return "VLAN Filter Table";
case NTMP_ETT_ID:
return "Egress Treatment Table";
+ case NTMP_ECT_ID:
+ return "Egress Count Table";
case NTMP_BPT_ID:
return "Buffer Pool Table";
default:
@@ -1197,6 +1201,47 @@ int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id)
}
EXPORT_SYMBOL_GPL(ntmp_ett_delete_entry);
+/**
+ * ntmp_ect_update_entry - reset the statistics element data of the
+ * specified egress counter table entry
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id)
+{
+ struct ntmp_req_by_eid *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd_eid(req, user->tbl.ect_ver, 0, ECT_UA_STSEU, entry_id);
+
+ /* Request header */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0),
+ NTMP_ECT_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update %s entry 0x%x, err: %pe\n",
+ ntmp_table_name(NTMP_ECT_ID), entry_id, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ect_update_entry);
+
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge)
{
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 0c951e1c763d..1222901f48a7 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -37,6 +37,7 @@ struct netc_tbl_vers {
u8 bpt_ver;
u8 ipft_ver;
u8 ett_ver;
+ u8 ect_ver;
};
struct netc_swcbd {
@@ -294,6 +295,7 @@ int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id,
int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id,
const struct ett_cfge_data *cfge);
int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id);
+int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge);
#else
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 3/9] net: enetc: add interfaces to manage egress treatment table
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
Each entry in the egress treatment table contains the egress packet
processing actions to be applied to a grouping or scope of packets
exiting on a particular egress port of the switch. A scope of packets,
for example, could be the packets exiting a particular VLAN, matching
a particular 802.1Q bridge forwarding entry or belonging to a stream
identified at ingress. The egress treatment table is implemented as a
linear array of entries accessed using an index (0,1, 2, ..., n) that
uniquely identifies an entry within the array.
The egress treatment table only supports access vid entry ID, which is
assigned by the software. It supports Add, Update, Delete and Query
operations. Note that only Query operation is not supported yet.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 106 ++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 8 ++
include/linux/fsl/ntmp.h | 23 ++++
3 files changed, 137 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 4e60bbc38cfa..9249f78219ed 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -24,6 +24,7 @@
#define NTMP_IPFT_ID 13
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
+#define NTMP_ETT_ID 33
#define NTMP_BPT_ID 41
/* Generic Update Actions for most tables */
@@ -284,6 +285,8 @@ static const char *ntmp_table_name(int tbl_id)
return "FDB Table";
case NTMP_VFT_ID:
return "VLAN Filter Table";
+ case NTMP_ETT_ID:
+ return "Egress Treatment Table";
case NTMP_BPT_ID:
return "Buffer Pool Table";
default:
@@ -1091,6 +1094,109 @@ int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid)
}
EXPORT_SYMBOL_GPL(ntmp_vft_delete_entry);
+/**
+ * ntmp_ett_set_entry - add a new entry to the egress treatment table or
+ * update the configuration element data of the specified entry
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ * @cmd: command type, NTMP_CMD_ADD or NTMP_CMD_UPDATE
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+static int ntmp_ett_set_entry(struct ntmp_user *user, u32 entry_id,
+ int cmd, const struct ett_cfge_data *cfge)
+{
+ struct netc_swcbd swcbd;
+ struct ett_req_ua *req;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ int err;
+
+ if (cmd != NTMP_CMD_ADD && cmd != NTMP_CMD_UPDATE)
+ return -EINVAL;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd_eid(&req->rbe, user->tbl.ett_ver, 0,
+ NTMP_GEN_UA_CFGEU, entry_id);
+ req->cfge = *cfge;
+
+ /* Request header */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0),
+ NTMP_ETT_ID, cmd, NTMP_AM_ENTRY_ID);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+
+/**
+ * ntmp_ett_add_entry - add a new entry to the egress treatment table
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id,
+ const struct ett_cfge_data *cfge)
+{
+ int err;
+
+ err = ntmp_ett_set_entry(user, entry_id, NTMP_CMD_ADD, cfge);
+ if (err)
+ dev_err(user->dev, "Failed to add %s entry 0x%x, err: %pe\n",
+ ntmp_table_name(NTMP_ETT_ID), entry_id, ERR_PTR(err));
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ett_add_entry);
+
+/**
+ * ntmp_ett_update_entry - update the configuration element data of the
+ * specified entry
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct ett_cfge_data *cfge)
+{
+ int err;
+
+ err = ntmp_ett_set_entry(user, entry_id, NTMP_CMD_UPDATE, cfge);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update %s entry 0x%x, err: %pe\n",
+ ntmp_table_name(NTMP_ETT_ID), entry_id, ERR_PTR(err));
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ett_update_entry);
+
+/**
+ * ntmp_ett_delete_entry - delete the specified egress treatment table entry
+ * @user: target ntmp_user struct
+ * @entry_id: entry ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ return ntmp_delete_entry_by_id(user, NTMP_ETT_ID, user->tbl.ett_ver,
+ entry_id, NTMP_EID_REQ_LEN, 0);
+}
+EXPORT_SYMBOL_GPL(ntmp_ett_delete_entry);
+
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge)
{
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 9d30f128849a..531ea7ddd145 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -217,6 +217,14 @@ struct vft_req_qd {
union vft_access_key ak;
};
+/* Egress Treatment Table Request Data Buffer Format of Update and Add
+ * actions
+ */
+struct ett_req_ua {
+ struct ntmp_req_by_eid rbe;
+ struct ett_cfge_data cfge;
+};
+
/* Buffer Pool Table Request Data Buffer Format of Update action */
struct bpt_req_update {
struct ntmp_req_by_eid rbe;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 36a9089526ad..0c951e1c763d 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -36,6 +36,7 @@ struct netc_tbl_vers {
u8 vft_ver;
u8 bpt_ver;
u8 ipft_ver;
+ u8 ett_ver;
};
struct netc_swcbd {
@@ -214,6 +215,23 @@ struct vft_cfge_data {
__le32 et_eid;
};
+struct ett_cfge_data {
+ __le16 efm_cfg;
+#define ETT_EFM_MODE GENMASK(1, 0)
+#define ETT_ESQA GENMASK(5, 4)
+#define ETT_ECA GENMASK(8, 6)
+#define ETT_ECA_INC 1
+#define ETT_EFM_LEN_CHANGE GENMASK(15, 9)
+#define ETT_FRM_LEN_DEL_VLAN 0x7c
+#define ETT_FRM_LEN_DEL_RTAG 0x7a
+#define ETT_FRM_LEN_DEL_VLAN_RTAG 0x76
+ __le16 efm_data_len;
+#define ETT_EFM_DATA_LEN GENMASK(10, 0)
+ __le32 efm_eid;
+ __le32 ec_eid;
+ __le32 esqa_tgt_eid;
+};
+
struct bpt_bpse_data {
__le32 amount_used;
__le32 amount_used_hwm;
@@ -271,6 +289,11 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid);
+int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id,
+ const struct ett_cfge_data *cfge);
+int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct ett_cfge_data *cfge);
+int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge);
#else
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 2/9] net: enetc: add "Update" and "Delete" operations to VLAN filter table
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
Add two interfaces to manage entries in the VLAN filter table:
ntmp_vft_update_entry(): Update the configuration element data of the
specified VLAN filter entry based on the given VLAN ID. It uses the
exact key access method to locate the entry.
ntmp_vft_delete_entry(): Delete the VLAN filter entry corresponding to
the specified VLAN ID. It also uses the exact key access method to
identify the target entry.
In addition, introduce struct vft_req_qd to describe the request data
buffer format for Query and Delete actions of the VLAN filter table,
which contains a common request data header and a VLAN access key.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 103 ++++++++++++++++--
.../ethernet/freescale/enetc/ntmp_private.h | 6 +
include/linux/fsl/ntmp.h | 3 +
3 files changed, 105 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index bda26fe93b8d..4e60bbc38cfa 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -956,15 +956,17 @@ int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port)
EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_port_dynamic_entries);
/**
- * ntmp_vft_add_entry - add an entry into the VLAN filter table
+ * ntmp_vft_set_entry - add an entry into the VLAN filter table or update
+ * the configuration element data of the specified VLAN filter entry
* @user: target ntmp_user struct
* @vid: VLAN ID
+ * @cmd: command type, NTMP_CMD_ADD or NTMP_CMD_UPDATE
* @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)
+static int ntmp_vft_set_entry(struct ntmp_user *user, u16 vid, int cmd,
+ const struct vft_cfge_data *cfge)
{
struct netc_swcbd swcbd;
struct vft_req_ua *req;
@@ -973,34 +975,121 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
u32 len;
int err;
+ if (cmd != NTMP_CMD_ADD && cmd != NTMP_CMD_UPDATE)
+ return -EINVAL;
+
swcbd.size = sizeof(*req);
err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
if (err)
return err;
/* Request data */
- ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0,
- NTMP_GEN_UA_CFGEU);
+ 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);
+ cmd, NTMP_AM_EXACT_KEY);
ntmp_select_and_lock_cbdr(user, &cbdr);
err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+
+/**
+ * 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)
+{
+ int err;
+
+ err = ntmp_vft_set_entry(user, vid, NTMP_CMD_ADD, cfge);
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));
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+
+/**
+ * ntmp_vft_update_entry - update the configuration element data of the
+ * specified VLAN filter entry
+ * @user: target ntmp_user struct
+ * @vid: VLAN ID
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge)
+{
+ int err;
+
+ err = ntmp_vft_set_entry(user, vid, NTMP_CMD_UPDATE, cfge);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update %s entry, vid: %u, err: %pe\n",
+ ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err));
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_vft_update_entry);
+
+/**
+ * ntmp_vft_delete_entry - delete the VLAN filter entry based on the
+ * specified VLAN ID
+ * @user: target ntmp_user struct
+ * @vid: VLAN ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid)
+{
+ struct netc_swcbd swcbd;
+ struct vft_req_qd *req;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, 0);
+ req->ak.exact.vid = cpu_to_le16(vid);
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID,
+ NTMP_CMD_DELETE, NTMP_AM_EXACT_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to delete %s entry, vid: %u, err: %pe\n",
+ ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err));
+
ntmp_unlock_cbdr(cbdr);
return err;
}
-EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+EXPORT_SYMBOL_GPL(ntmp_vft_delete_entry);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index ad532b059ba8..9d30f128849a 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -211,6 +211,12 @@ struct vft_req_ua {
struct vft_cfge_data cfge;
};
+/* VLAN Filter Table Request Data Buffer Format of Query and Delete actions */
+struct vft_req_qd {
+ struct ntmp_cmn_req_data crd;
+ union vft_access_key ak;
+};
+
/* Buffer Pool Table Request Data Buffer Format of Update action */
struct bpt_req_update {
struct ntmp_req_by_eid rbe;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 5db078e1caa0..36a9089526ad 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -268,6 +268,9 @@ int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt);
int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port);
int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
+int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge);
+int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
const struct bpt_cfge_data *cfge);
#else
--
2.34.1
^ permalink raw reply related
* [PATCH v4 net-next 1/9] net: enetc: add interfaces to manage dynamic FDB entries
From: wei.fang @ 2026-06-09 3:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, chleroy, andrew, olteanv, linux
Cc: wei.fang, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260609032955.2066089-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
Add three interfaces to manage dynamic entries in the FDB table:
ntmp_fdbt_update_activity_element(): Update the activity element of all
dynamic FDB entries. For each entry, if its activity flag is not set,
which means no packet has matched this entry since the last update, the
activity counter is incremented. Otherwise, both the activity flag and
activity counter are reset. The activity counter is used to track how
long an FDB entry has been inactive, which is useful for implementing
an ageing mechanism.
ntmp_fdbt_delete_ageing_entries(): Delete all dynamic FDB entries whose
activity flag is not set and whose activity counter is greater than or
equal to the specified threshold. This is used to remove stale entries
that have been inactive for too long.
ntmp_fdbt_delete_port_dynamic_entries(): Delete all dynamic FDB entries
associated with the specified switch port. This is typically called when
a port goes down or is removed from a bridge.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 162 ++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 4 +-
include/linux/fsl/ntmp.h | 3 +
3 files changed, 167 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index f71cad943424..bda26fe93b8d 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -31,6 +31,7 @@
#define NTMP_GEN_UA_STSEU BIT(1)
/* Specific Update Actions for some tables */
+#define FDBT_UA_ACTEU BIT(1)
#define BPT_UA_BPSEU BIT(1)
/* Query Action: 0: Full query. 1: Query entry ID, the fields after entry
@@ -793,6 +794,167 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
}
EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+/**
+ * ntmp_fdbt_update_activity_element - update the activity element of all
+ * the dynamic entries in the FDB table.
+ * @user: target ntmp_user struct
+ *
+ * A single activity update management could be used to process all the
+ * dynamic entries in the FDB table. When hardware process an activity
+ * update management command for an entry in the FDB table and the entry
+ * does not have its activity flag set, the activity counter is incremented.
+ * However, if the activity flag is set, then both the activity flag and
+ * activity counter are reset. Software can issue the activity update
+ * management commands at predefined times and the value of the activity
+ * counter can then be used to estimate the period of how long an FDB
+ * entry has been inactive.
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_update_activity_element(struct ntmp_user *user)
+{
+ struct fdbt_req_ua *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, FDBT_UA_ACTEU);
+ req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC);
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ /* For activity update, the access method must be search */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_UPDATE, NTMP_AM_SEARCH);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update activity of %s, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_update_activity_element);
+
+/**
+ * ntmp_fdbt_delete_ageing_entries - delete all the ageing dynamic entries
+ * in the FDB table
+ * @user: target ntmp_user struct
+ * @act_cnt: the target value of the activity counter
+ *
+ * The matching rule is that the activity flag is not set and the activity
+ * counter is greater than or equal to act_cnt
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt)
+{
+ struct fdbt_req_qd *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ if (act_cnt > FDBT_ACT_CNT)
+ return -EINVAL;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0);
+ req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC);
+ req->ak.search.acte = act_cnt;
+ /* Exact match with ACTE_DATA[ACT_FLAG] AND
+ * match >= ACTE_DATA[ACT_CNT]
+ */
+ req->ak.search.acte_mc = FDBT_ACTE_MC;
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_DELETE, NTMP_AM_SEARCH);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to delete ageing entries of %s, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_ageing_entries);
+
+/**
+ * ntmp_fdbt_delete_port_dynamic_entries - delete all dynamic FDB entries
+ * associated with the specified switch port
+ * @user: target ntmp_user struct
+ * @port: the specified switch port ID
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port)
+{
+ struct fdbt_req_qd *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0);
+ req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port));
+ req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC);
+ /* Match CFGE_DATA[DYNAMIC & PORT_BITMAP] field */
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_DELETE, NTMP_AM_SEARCH);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to delete dynamic %s entries on port %d, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_port_dynamic_entries);
+
/**
* ntmp_vft_add_entry - add an entry into the VLAN filter table
* @user: target ntmp_user struct
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 0a9b87286105..ad532b059ba8 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -155,8 +155,8 @@ struct fdbt_ak_search {
#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_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;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 88166f9ad3a2..5db078e1caa0 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -263,6 +263,9 @@ 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_fdbt_update_activity_element(struct ntmp_user *user);
+int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt);
+int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port);
int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
--
2.34.1
^ permalink raw reply related
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