Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH v4 1/3] dt-bindings: hwmon: pmbus: add Delta E50SN12051 binding
From: Colin Huang @ 2026-05-08  8:20 UTC (permalink / raw)
  To: Conor Dooley
  Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Kevin Chang, Jonathan Corbet, Shuah Khan, linux-hwmon, devicetree,
	linux-kernel, linux-doc, Colin Huang
In-Reply-To: <20260507-squealing-vanish-16fea3c114f5@spud>

Conor Dooley <conor@kernel.org> 於 2026年5月8日週五 上午1:07寫道:
>
> On Thu, May 07, 2026 at 01:12:26PM +0800, Colin Huang via B4 Relay wrote:
> > From: Colin Huang <u8813345@gmail.com>
> >
> > Add devicetree binding documentation for the Delta E50SN12051
> > PMBus-compliant device.
> >
> > Signed-off-by: Colin Huang <u8813345@gmail.com>
> > ---
> >  .../bindings/hwmon/pmbus/delta,e50sn12051.yaml     | 42 ++++++++++++++++++++++
> >  1 file changed, 42 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/delta,e50sn12051.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/delta,e50sn12051.yaml
> > new file mode 100644
> > index 000000000000..72aefe212d17
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/hwmon/pmbus/delta,e50sn12051.yaml
> > @@ -0,0 +1,42 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/hwmon/pmbus/delta,e50sn12051.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Delta E50SN12051 PMBus Sensor
> > +
> > +maintainers:
> > +  - Kevin Chang <kevin.chang2@amd.com>
> > +
> > +description: |
> > +  Delta E50SN12051 is a non-isolated 1/8th brick DC-DC power module.
> > +  It is a PMBus-compliant device accessible via an I2C/SMBus interface
> > +  and provides standard telemetry such as voltage, current, and
> > +  temperature measurements.
> > +
> > +properties:
> > +  compatible:
> > +    const: delta,e50sn12051
> > +
> > +  reg:
> > +    maxItems: 1
> > +    description: I2C bus address of the PMBus device
> > +
> > +required:
> > +  - compatible
> > +  - reg
>
> With only these two properties, shouldn't this be in trivial-devices?
Thanks for your comment.
I will remove this file, delta,e50sn12051.yaml,
and add device into trivial-devices.yaml.
>
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    i2c {
> > +        #address-cells = <1>;
> > +        #size-cells = <0>;
> > +
> > +        power-module@40 {
> > +            compatible = "delta,e50sn12051";
> > +            reg = <0x40>;
> > +        };
> > +    };
> >
> > --
> > 2.34.1
> >
> >

^ permalink raw reply

* Re: [PATCH v12 10/16] KVM: guest_memfd: Add flag to remove from direct map
From: Takahiro Itazuri @ 2026-05-08  8:18 UTC (permalink / raw)
  To: fvdl, seanjc, ljs
  Cc: Liam.Howlett, ackerleytng, agordeev, ajones, akpm, alex, andrii,
	aou, ast, baolu.lu, borntraeger, bp, bpf, catalin.marinas,
	chenhuacai, corbet, coxu, daniel, dave.hansen, david, derekmn,
	dev.jain, eddyz87, gerald.schaefer, gor, haoluo, hca, hpa, itazur,
	jackabt, jackmanb, jannh, jgg, jgross, jhubbard, jiayuan.chen,
	jmattson, joey.gouly, john.fastabend, jolsa, jthoughton, kalyazin,
	kas, kernel, kpsingh, kvm, kvmarm, lenb, linux-arm-kernel,
	linux-doc, linux-fsdevel, linux-kernel, linux-kselftest, linux-mm,
	linux-pm, linux-riscv, linux-s390, loongarch, lorenzo.stoakes,
	luto, maobibo, martin.lau, maz, mhocko, mingo, mlevitsk,
	nikita.kalyazin, oupton, palmer, patrick.roy, pavel, pbonzini,
	peterx, peterz, pfalcato, pjw, prsampat, rafael, riel, rppt,
	ryan.roberts, sdf, shijie, skhan, song, surenb, suzuki.poulose,
	svens, tabba, tglx, thuth, urezki, vannapurve, vbabka, will,
	willy, wu.fei9, x86, yang, yangyicong, yonghong.song, yosry,
	yu-cheng.yu, yuzenghui, zhengqi.arch, zulinx86
In-Reply-To: <CAPTztWb67XZvfcMVnbegDNNW0LJa9UsaTGx3M898xJUJrekk0w@mail.gmail.com>

Hi Sean, Frank, Lorenzo,

On Tue, Apr 21, 2026 at 10:08:48AM -0700, Frank van der Linden wrote:
> On Tue, Apr 21, 2026 at 9:31 AM Sean Christopherson <seanjc@google.com> wrote:
> > Making guest_memfd responsible for zapping and restoring the direct map on a per-
> > folio basis feels wrong given the addition of AS_NO_DIRECT_MAP.  I especially don't
> > like that the "rules" for when an AS_NO_DIRECT_MAP folio has a direct map will vary
> > based on the owner, and even within an owner (e.g. guest_memfd) will be ad hoc.
> >
> > E.g. as per the series to add guest_memfd write() support[*]:
> >
> >   When direct map removal is implemented [2]
> >    - write() will not be allowed to access pages that have already
> >      been removed from direct map
> >    - on completion, write() will remove the populated pages from
> >      direct map
> >
> > That's pretty gross ABI, because with KVM_GMEM_FOLIO_NO_DIRECT_MAP, userspace can
> > write() exactly once.  To re-write memory, I assume userspace would need to do a
> > PUNCH_HOLE or truncate.
> >
> > What's preventing us from handling this automagically in e.g. filemap_add_folio()
> > and filemap_remove_folio()?  Then the usage rules are pretty straightforward: the
> > kernel must *always* assume the direct map is invalid for folios from
> > AS_NO_DIRECT_MAP mappings.
> >
> > Then if KVM needs to utilize a kernel mapping, e.g. in kvm_gmem_populate(), KVM
> > could use dedicated variants of kmap_local_xxx() to deal with a local mapping for
> > a folio/page without a direct map.  Or, KVM could simply disallow the specific
> > sequence that would require KVM to do the memcpy (I'm pretty sure we can do that
> > with in-place shared=>private conversion support).
> >
> > I realize that could throw a big wrench into write() performance, but IMO, before
> > merging either series, we need a complete story for exactly how this will all fit
> > together, in a maintainable fashion and with sane ABI.
>
> I agree with this - this approach would also allow for memory that was
> never in the direct map to begin with, or has been taken out already
> (for which I happen to have a use case :-)). guest_memfd and other
> code can then assume that AS_NO_DIRECT_MAP means they have to take
> explicit action to map it if needed. It's a clean, simple ABI.
>
> With the current set of patches, it seems like this couldn't be done
> in a clean manner.

Agreed with both of you.  I'll adopt the filemap-level approach:

- Move the zap/restore hooks from guest_memfd into filemap_add_folio()
  / filemap_remove_folio().
- Tighten AS_NO_DIRECT_MAP semantics so that, for folios in such a
  mapping, the direct map is invalid for the entire time the folio
  resides in the page cache.
- Drop the per-folio KVM_GMEM_FOLIO_NO_DIRECT_MAP bookkeeping in
  folio->private, since the existence of the folio in the mapping is
  itself the state.

On each guest memory population path,

- memcpy-based population from userspace goes through the userspace
  mapping of guest_memfd, not through the kernel direct map, so the
  filemap-level invariant doesn't affect it.  But this is slow, which
  is what motivated the write() syscall support.

- write(): meant to speed up the userspace-memcpy case above by doing
  the copy in the kernel.  I believe Brendan's __GFP_UNMAPPED/mermap
  work [1] would give us a low-overhead way to get temporary kernel
  access to an AS_NO_DIRECT_MAP.  Landing mermap may take a while, but
  this series does not introduce the write() path, so mermap is not a
  blocker for now.

- kvm_gmem_populate(): this is a TDX/SNP-only path, and NO_DIRECT_MAP
  is not available on those VM types —
  kvm_arch_gmem_supports_no_direct_map() returns false for
  KVM_X86_TDX_VM and KVM_X86_SNP_VM, which are its only callers
  today.  So it doesn't interact with the filemap invariant IIUC.

So, unless I'm missing any path, adopting the filemap-level approach in
this series should be fine.


I'd like to consult with you folks on how to proceed in advance.  In a
separate reply on the cover letter thread [2], Lorenzo and Sean
suggested that the mm pieces should go through the mm subsystem:

On Tue, Apr 21, 2026 at 04:36:00PM +0000, Sean Christopherson wrote:
> Yeah, when the time comes, the mm pieces definitely need to go through the mm
> tree.  Ideally, I think this would be merged in two separate parts, with all mm
> changes going through the mm tree, and then the KVM changes through the KVM tree
> using a stable topic branch/tag from Andrew.

I see two reasonable paths to get there, and would appreciate your
input on which you prefer:

Path A — validate on KVM side first, then split:
  - Post v13 as a single series on the KVM list, gather feedback and
    make sure the design is acceptable to KVM reviewers.
  - Once v13 looks good ("the time comes"), do the MM/KVM split,
    rebase the MM part onto the appropriate MM branch, and post the
    MM part to linux-mm to build consensus with MM maintainers.

Path B — split early and seek MM consensus in parallel:
  - With the filemap rework already in place, do the MM/KVM split
    now and post the MM part to linux-mm directly.  The KVM part follows
    on top of a stable topic from MM.

Which of the two would you rather see?  Happy to go either way.


[1] https://lore.kernel.org/all/20260320-page_alloc-unmapped-v2-0-28bf1bd54f41@google.com/
[2] https://lore.kernel.org/all/20260506080753.14517-1-itazur@amazon.com/

Takahiro


^ permalink raw reply

* Re: [PATCH 0/3] iio: dac: Add support for AD5529R DAC
From: Andy Shevchenko @ 2026-05-08  7:37 UTC (permalink / raw)
  To: Janani Sunil
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, Jonathan Corbet,
	Shuah Khan, linux-iio, devicetree, linux-kernel, linux-doc,
	Janani Sunil
In-Reply-To: <20260507-ad5529r-driver-v1-0-b4460f3cb44f@analog.com>

On Thu, May 07, 2026 at 06:28:08PM +0200, Janani Sunil wrote:
> This patch series adds support for Analog Devices AD5529R, a 16 channel
> 16 and 12 bit voltage Digital-to-Analog Converter (DAC) with integrated
> precision reference. The AD5529R operates from both unipolar and
> bipolar supplies. The device communicates via SPI interface.
> 
> **Device Overview:**
> The AD5529R features 16 independent DAC channels, with 16 or 12 bit
> resolution, allowing independently programmable output ranges. The
> internal 4.096V precision reference sets the accuracy of the output
> voltage.
> 
> **Features Implemented:**
> - Automatic detection of 12/16 bit generic with product ID read.
> - Reset support via GPIO.
> - Dual regmap configuration to handle 8 and 16 bit registers.
> 
> **Patch Summary:**
> 1. **dt-bindings**: Binding documentation with channel configuration.
> 2. **driver**: Implement IIO DAC Driver with regmap support.
> 3. **documentation**: Add driver documentation with usage examples.
> 
> **Testing:**
> The driver was compiled and tested on the EVAL-AD5529R-ARDZ using a
> coraZ7 with a mainline v7.0 kernel.

Missed section for a new driver. Id est answer the question "Why a new brand
driver? Do we have something similar in IIO  already to be expanded to cover
this HW part?"

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v2 11/11] MAINTAINERS: use a URL for pin-init maintainer's profile entry
From: Mauro Carvalho Chehab @ 2026-05-08  7:16 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Gary Guo, Benno Lossin, Jonathan Corbet, Linux Doc Mailing List,
	linux-kernel, rust-for-linux, Björn Roy Baron, Alice Ryhl,
	Andreas Hindborg, Boqun Feng, Danilo Krummrich, Miguel Ojeda,
	Trevor Gross
In-Reply-To: <CANiq72kN=2RsE0Wm_489Sc-VKO40A0uYinCj1McT=YMu=cM4aQ@mail.gmail.com>

On Wed, 6 May 2026 10:38:07 +0200
Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> wrote:

> On Wed, May 6, 2026 at 8:49 AM Mauro Carvalho Chehab
> <mchehab+huawei@kernel.org> wrote:
> >
> > Maybe it is just me, but placing doc files in the crowd together with code
> > on a project where documentation has its own directory sounds weird(*).
> >
> > (*) except for userspace tools and staging drivers where we want
> >     everything, including documentation, contained on a single place.  
> 
> It is a similar case -- they are vendored libraries developed and used
> elsewhere (e.g. in userspace too).

I see.

> > As you're already using some scripting to do a bidirectional sync,
> > you could add there some logic to convert rust pin-init documentation
> > to rst and place it at Documentation/rust/.  
> 
> Most of the pin-init documentation is elsewhere (embedded in the Rust
> source code) and already rendered at rust.docs.kernel.org.
> 
> (Using Sphinx for Rust has been discussed in the list a few times in
> the past too, and that has its own set of constraints).
> 
> > Btw, on a quick check, only Rust has markdown files:
> >
> >         $ git ls-files|grep ".md$"|grep -v tools/
> >         rust/pin-init/CONTRIBUTING.md
> >         rust/pin-init/README.md
> >         rust/proc-macro2/README.md
> >         rust/quote/README.md
> >         rust/syn/README.md
> >
> > As one of the people who spent years helping improving the Kernel
> > documentation and doing lots of conversions from markdown
> > and other non-structured text formats to RST, I have to say that I'm
> > concerned with any trends that would add doc files that aren't
> > properly integrated with the Kernel documentation system like those.  
> 
> All those are vendored libraries, and most of those `README.md` files
> are about clarifying where they are taken from and their licensing,
> e.g.
> 
>     # `quote`
> 
>     These source files come from the Rust `quote` crate, version 1.0.40
>     (released 2025-03-12), hosted in the <https://github.com/dtolnay/quote>
>     repository, licensed under "Apache-2.0 OR MIT" and only modified to add
>     the SPDX license identifiers.
> 
>     For copyright details, please see:
> 
>         https://github.com/dtolnay/quote/blob/1.0.40/README.md#license
>         https://github.com/dtolnay/quote/blob/1.0.40/LICENSE-APACHE
>         https://github.com/dtolnay/quote/blob/1.0.40/LICENSE-MIT

Just my two cents, but all license files that are applies to the Kernel
are already under LICENSES/ directory, and there is a document defining
how SPDX and LICENSES should be used:

	Documentation/process/license-rules.rst.

IMO it would make more sense to point to the corresponding LICENSES/ files.

> 
> i.e. they are not intended to be kernel documentation, and moving them
> away from the source files doesn't sound like an improvement,
> especially since we were explicitly requested to make the provenance
> very clear.

In this case, this sounds OK.

> We could perhaps have a list of vendored libraries in
> Documentation/rust/vendored-libraries.rst` -- that could perhaps be
> useful for some folks.

Makes sense to me.

> In any case, I understand your desire to keep things integrated in the
> kernel documentation, and I appreciate that (I also like to have
> everything neatly integrated), but please do not assume we have been
> randomly adding Markdown for no reason or that we are not trying to
> keep things properly integrated... :(

I didn't intend to imply that tose aren't properly integrated. It is
just that it raised my eyebrows seeing more doc-like files over there.

---

With regards to patch 11/11, I'll send a new version just changing
the "P" tag without dropping the .md file.

Thanks,
Mauro

^ permalink raw reply

* Re: [PATCH v3 1/2] usb: xhci-pci: add generic auxiliary device interface
From: Jihong Min @ 2026-05-08  7:04 UTC (permalink / raw)
  To: Mathias Nyman, Jihong Min, Greg Kroah-Hartman, Mathias Nyman
  Cc: Guenter Roeck, Jonathan Corbet, Shuah Khan, Mario Limonciello,
	Basavaraj Natikar, linux-usb, linux-hwmon, linux-doc, linux-pci,
	linux-kernel
In-Reply-To: <a5739875-b8a5-4918-8850-fa4b32d5279d@linux.intel.com>

Hi Mathias,

I tried the xhci-pci-prom21.c approach you suggested, with a PROM21-specific
PCI glue driver calling xhci_pci_common_probe() and creating the auxiliary
hwmon child device from that driver.

While doing that I noticed a possible boot-time regression with the module
case.

If CONFIG_USB_XHCI_PCI=y and CONFIG_USB_XHCI_PCI_PROM21=m, then generic
xhci-pci sees CONFIG_USB_XHCI_PCI_PROM21 as enabled and refuses the PROM21
PCI ID:

     if (IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21) &&
         pci_match_id(pci_ids_prom21, dev))
             return -ENODEV;

That means the PROM21 xHCI controller is handled only by
xhci-pci-prom21.ko. If that module is not present in the initramfs or is not
loaded early enough, the PROM21 xHCI controller remains unbound during early
boot. Devices behind that controller, such as a USB keyboard used for early
boot or disk unlock, would not work even though the generic xhci-pci 
driver is
built in and could otherwise operate the controller.

This seems different from the Renesas case, where the separate PCI driver is
needed for controller-specific firmware handling. For PROM21, the USB/xHCI
operation itself is still generic; the only extra function is publishing an
optional hwmon child device.

So I am not sure what the preferred direction should be:

   1. Keep the separate xhci-pci-prom21.c PCI glue driver and make
      USB_XHCI_PCI_PROM21 built-in only, or otherwise constrain the 
Kconfig so
      the generic xhci-pci handoff cannot break early boot.

   2. Keep PROM21 handled by generic xhci-pci and add only a small
      PROM21-specific auxiliary-device creation hook in xhci-pci after the
      common probe succeeds. In that model, failure to create the 
optional hwmon
      auxiliary device would not affect USB operation.

   3. Some other split that keeps PROM21-specific sensor code outside
      xhci-pci, but does not prevent generic xhci-pci from binding the
      controller when the optional PROM21 glue is not available early.

Do you still prefer the separate xhci-pci-prom21.c PCI driver for this case,
or would the minimal xhci-pci auxiliary-device hook be more appropriate 
given
the built-in xhci-pci / modular PROM21 glue case?

Sincerely,
Jihong Min

On 5/7/26 18:31, Mathias Nyman wrote:
> On 5/7/26 06:31, Jihong Min wrote:
>> Some xHCI PCI controllers expose controller-specific functionality 
>> that is
>> not part of generic xHCI operation and is better handled by optional 
>> child
>> drivers in other subsystems. Add a small auxiliary device 
>> registration path
>> for selected xHCI PCI controllers.
>>
>> The initial PCI ID match table lists AMD Promontory 21 (PROM21) 
>> 1022:43fd
>> controllers. For matching controllers, xhci-pci creates an auxiliary
>> device and stores it in devres so the remove path destroys it before HCD
>> teardown.
>>
>> Subsystem-specific child drivers can then bind to those devices through
>> the auxiliary bus and keep their hardware-specific logic outside 
>> xhci-pci.
>>
>> Assisted-by: Codex:gpt-5.5
>> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
>> ---
>>   drivers/usb/host/Kconfig    | 10 +++++
>>   drivers/usb/host/xhci-pci.c | 83 +++++++++++++++++++++++++++++++++++++
>>   2 files changed, 93 insertions(+)
>>
>> diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
>> index 0a277a07cf70..e0c2c7ac5c97 100644
>> --- a/drivers/usb/host/Kconfig
>> +++ b/drivers/usb/host/Kconfig
>> @@ -42,6 +42,16 @@ config USB_XHCI_PCI
>>       depends on USB_PCI
>>       default y
>>   +config USB_XHCI_PCI_AUXDEV
>> +    bool "xHCI PCI auxiliary device support"
>> +    depends on USB_XHCI_PCI
>> +    select AUXILIARY_BUS
>> +    help
>> +      This enables xHCI PCI support for registering auxiliary devices
>> +      for selected controllers. It is used by optional child drivers
>> +      that bind to xHCI PCI controller-specific functionality through
>> +      the auxiliary bus.
>> +
>>   config USB_XHCI_PCI_RENESAS
>>       tristate "Support for additional Renesas xHCI controller with 
>> firmware"
>>       depends on USB_XHCI_PCI
>> diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
>> index 585b2f3117b0..618d6840e108 100644
>> --- a/drivers/usb/host/xhci-pci.c
>> +++ b/drivers/usb/host/xhci-pci.c
>> @@ -8,6 +8,8 @@
>>    * Some code borrowed from the Linux EHCI driver.
>>    */
>>   +#include <linux/auxiliary_bus.h>
>> +#include <linux/device/devres.h>
>>   #include <linux/pci.h>
>>   #include <linux/slab.h>
>>   #include <linux/module.h>
>> @@ -80,6 +82,7 @@
>>   #define PCI_DEVICE_ID_AMD_RAVEN_15E1_XHCI        0x15e1
>>   #define PCI_DEVICE_ID_AMD_RAVEN2_XHCI            0x15e5
>>   #define PCI_DEVICE_ID_AMD_RENOIR_XHCI            0x1639
>> +#define PCI_DEVICE_ID_AMD_PROM21_XHCI            0x43fd
>>   #define PCI_DEVICE_ID_AMD_PROMONTORYA_4            0x43b9
>>   #define PCI_DEVICE_ID_AMD_PROMONTORYA_3            0x43ba
>>   #define PCI_DEVICE_ID_AMD_PROMONTORYA_2            0x43bb
>> @@ -103,6 +106,80 @@ static int xhci_pci_run(struct usb_hcd *hcd);
>>   static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct 
>> usb_device *hdev,
>>                         struct usb_tt *tt, gfp_t mem_flags);
>>   +static const struct pci_device_id pci_ids_have_aux[] = {
>> +    { PCI_DEVICE_DATA(AMD, PROM21_XHCI, "prom21_hwmon") },
>> +    { /* end: all zeroes */ }
>> +};
>> +
>> +struct xhci_pci_aux_devres {
>> +    struct auxiliary_device *auxdev;
>> +};
>> +
>> +static const char *xhci_pci_aux_dev_name(struct pci_dev *pdev)
>> +{
>> +    const struct pci_device_id *id;
>> +
>> +    id = pci_match_id(pci_ids_have_aux, pdev);
>> +    if (!id)
>> +        return NULL;
>> +
>> +    return (const char *)id->driver_data;
>> +}
>> +
>> +static void xhci_pci_aux_devres_release(struct device *dev, void *res)
>> +{
>> +    struct xhci_pci_aux_devres *devres = res;
>> +
>> +    if (devres->auxdev)
>> +        auxiliary_device_destroy(devres->auxdev);
>> +}
>> +
>> +static void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
>> +{
>> +    struct xhci_pci_aux_devres *devres;
>> +    struct auxiliary_device *auxdev;
>> +    const char *aux_dev_name;
>> +
>> +    aux_dev_name = xhci_pci_aux_dev_name(pdev);
>> +    if (!aux_dev_name)
>> +        return;
>> +
>> +    devres = devres_alloc(xhci_pci_aux_devres_release, sizeof(*devres),
>> +                  GFP_KERNEL);
>> +    if (!devres) {
>> +        dev_warn(&pdev->dev,
>> +             "failed to allocate auxiliary device state\n");
>> +        return;
>> +    }
>> +
>> +    auxdev = auxiliary_device_create(&pdev->dev, KBUILD_MODNAME,
>> +                     aux_dev_name, NULL,
>> +                     (pci_domain_nr(pdev->bus) << 16) |
>> +                         pci_dev_id(pdev));
>> +    if (!auxdev) {
>> +        devres_free(devres);
>> +        dev_warn(&pdev->dev, "failed to add %s auxiliary device\n",
>> +             aux_dev_name);
>> +        return;
>> +    }
>> +
>> +    devres->auxdev = auxdev;
>> +    devres_add(&pdev->dev, devres);
>> +}
>> +
>> +static void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
>> +{
>> +    struct xhci_pci_aux_devres *devres;
>> +
>> +    devres = devres_find(&pdev->dev, xhci_pci_aux_devres_release, NULL,
>> +                 NULL);
>> +    if (!devres || !devres->auxdev)
>> +        return;
>> +
>> +    auxiliary_device_destroy(devres->auxdev);
>> +    devres->auxdev = NULL;
>> +}
>> +
>>   static const struct xhci_driver_overrides xhci_pci_overrides 
>> __initconst = {
>>       .reset = xhci_pci_setup,
>>       .start = xhci_pci_run,
>> @@ -677,6 +754,9 @@ int xhci_pci_common_probe(struct pci_dev *dev, 
>> const struct pci_device_id *id)
>>       if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
>>           pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
>>   +    if (IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV))
>> +        xhci_pci_try_add_aux_device(dev);
>> +
>>       return 0;
>
> I think this should be turned around so that the vendor specific code 
> calls the common code.
> xhci-pci-renesas.c does this nicely.
>
> In your case it would be adding something like a xhci-pci-prom21.c pci 
> driver:
>
> xhci_pci_prom21_probe(struct pci_dev *dev, const struct pci_device_id 
> *id)
> {
>     crate_auxiliary_device(dev);
>     return xhci_pci_common_probe(dev, id);
> }
>
> xhci_pci_prom21_remove(struct pci_dev *dev)
> {
>     destroy_auxiliary_device(dev);
>     xhci_pci_remove(dev);
> }
>
> static const struct pci_device_id pci_ids[] = {
>     { PCI_DEVICE(YOUR_AMD_PCI_VENDOR_ID, YOUR_PROM21_DEVICE_ID) },
>     { /* end: all zeroes */ }
> };
> MODULE_DEVICE_TABLE(pci, pci_ids);
>
> static struct pci_driver xhci_prom21_pci_driver = {
>     .name =        "xhci-pci-prom21",
>     .id_table =    pci_ids,
>
>     .probe =    xhci_pci_prom21_probe,
>     .remove =    xhci_pci_prom21_remove,
>
>     .shutdown =     usb_hcd_pci_shutdown,
>     .driver = {
>         .pm = pm_ptr(&usb_hcd_pci_pm_ops),
>     },
> };
> module_pci_driver(xhci_prom21_pci_driver);
>
> MODULE_DESCRIPTION("AMD Promontory 21 xHCI PCI Host Controller Driver");
> MODULE_IMPORT_NS("xhci");
> MODULE_LICENSE("GPL v2");
>
> -Mathias

^ permalink raw reply

* Re: [PATCH v8 1/4] bug/kunit: Core support for suppressing warning backtraces
From: Albert Esteve @ 2026-05-08  6:52 UTC (permalink / raw)
  To: David Gow
  Cc: Arnd Bergmann, Brendan Higgins, Rae Moar, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Jonathan Corbet, Shuah Khan, Andrew Morton, Paul Walmsley,
	Palmer Dabbelt, Albert Ou, Alexandre Ghiti, linux-kernel,
	linux-arch, linux-kselftest, kunit-dev, dri-devel, workflows,
	linux-riscv, linux-doc, peterz, Alessandro Carminati,
	Guenter Roeck, Kees Cook
In-Reply-To: <CADSE00KLhyUpRD031-YveOv+o71jJ-NojX6VteSyF+SqxG0n4g@mail.gmail.com>

On Wed, May 6, 2026 at 12:11 PM Albert Esteve <aesteve@redhat.com> wrote:
>
> On Wed, May 6, 2026 at 11:40 AM David Gow <david@davidgow.net> wrote:
> >
> > Le 04/05/2026 à 3:41 PM, Albert Esteve a écrit :
> > > From: Alessandro Carminati <acarmina@redhat.com>
> > >
> > > Some unit tests intentionally trigger warning backtraces by passing bad
> > > parameters to kernel API functions. Such unit tests typically check the
> > > return value from such calls, not the existence of the warning backtrace.
> > >
> > > Such intentionally generated warning backtraces are neither desirable
> > > nor useful for a number of reasons:
> > > - They can result in overlooked real problems.
> > > - A warning that suddenly starts to show up in unit tests needs to be
> > >    investigated and has to be marked to be ignored, for example by
> > >    adjusting filter scripts. Such filters are ad hoc because there is
> > >    no real standard format for warnings. On top of that, such filter
> > >    scripts would require constant maintenance.
> > >
> > > Solve the problem by providing a means to suppress warning backtraces
> > > originating from the current kthread while executing test code. Since
> > > each KUnit test runs in its own kthread, this effectively scopes
> > > suppression to the test that enabled it. Limit changes to generic code
> > > to the absolute minimum.
> > >
> > > Implementation details:
> > > Suppression is integrated into the existing KUnit hooks infrastructure
> > > in test-bug.h, reusing the kunit_running static branch for zero
> > > overhead when no tests are running.
> > >
> > > Suppression is checked at three points in the warning path:
> > > - In warn_slowpath_fmt(), the check runs before any output, fully
> > >    suppressing both message and backtrace. This covers architectures
> > >    without __WARN_FLAGS.
> > > - In __warn_printk(), the check suppresses the warning message text.
> > >    This covers architectures that define __WARN_FLAGS but not their own
> > >    __WARN_printf (arm64, loongarch, parisc, powerpc, riscv, sh), where
> > >    the message is printed before the trap enters __report_bug().
> > > - In __report_bug(), the check runs before __warn() is called,
> > >    suppressing the backtrace and stack dump.
> > >
> > > To avoid double-counting on architectures where both __warn_printk()
> > > and __report_bug() run for the same warning, kunit_is_suppressed_warning()
> > > takes a bool parameter: true to increment the suppression counter
> > > (used in warn_slowpath_fmt and __report_bug), false to check only
> > > (used in __warn_printk).
> > >
> > > The suppression state is dynamically allocated via kunit_kzalloc() and
> > > tied to the KUnit test lifecycle via kunit_add_action(), ensuring
> > > automatic cleanup at test exit. Writer-side access to the global
> > > suppression list is serialized with a spinlock; readers use RCU.
> > >
> > > Three API forms are provided:
> > > - kunit_warning_suppress(test) { ... }: scoped, uses __cleanup for
> > >    automatic teardown on scope exit, kunit_add_action() as safety net
> > >    for abnormal exits (e.g. kthread_exit from failed assertions).
> > >    Suppression handle is only accessible inside the block.
> > > - KUNIT_START/END_SUPPRESSED_WARNING(test): manual macros for larger
> > >    blocks or when warning counts need to be checked after suppression
> > >    ends. Limited to one pair per scope.
> > > - kunit_start/end_suppress_warning(test): direct functions returning
> > >    an explicit handle, for retaining the handle within the test,
> > >    or for cross-function usage.
> > >
> > > Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> > > Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
> > > Reviewed-by: Kees Cook <kees@kernel.org>
> > > Signed-off-by: Albert Esteve <aesteve@redhat.com>
> > > ---
> >
> > This looks pretty good to me, thanks.
> >
> > Reviewed-by: David Gow <david@davidgow.net>
> >
> > It's maybe slightly over-the-top to now have three different ways of
> > enabling warning suppression: I'd probably personally get rid of
> > KUNIT_START/END_SUPPRESSED_WARNING() if we had to lose one. But if
> > there's a real reason to prefer keeping all three, it's not actually a
> > problem to do so.
>
> Thanks for the review!
>
> I think the three forms earn their keep: the scoped form is the go-to
> for most cases, but the macros avoid indentation without requiring
> users to manage a raw pointer. I initially removed the macros and
> added them back later. Direct calls to the functions will be less
> frequent, used only when you need the handle.
>
> That said, if it becomes a maintenance burden, the macros are the
> easiest to drop since they're thin wrappers. Let me know if you prefer
> them to be dropped, and I will send a v9 with that and the
> `KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT` additions to the drm test
> patch.
>
> BR,
> Albert.
>
> >
> > Regardless, this series is looking pretty ready to me. Let me know if
> > you're planning a v9, otherwise we'll take this when you're ready.

I have been thinking, and despite my last response arguing against it,
I think I will send that v9 and remove
KUNIT_START/END_SUPPRESSED_WARNING(). Since the scoped approach with a
reduced extent should be the default style for most cases, the direct
calls can cover any other use cases without needing macros. Plus,
since macros set the handler name for you, it seems odd not to be able
to use them more than once per test.

I hope that's ok. After that I think it should be ready (at least from my side).

BR,
Albert

> >
> > Cheers,
> > -- David
> >
> > >   include/kunit/test-bug.h |  25 +++++++++
> > >   include/kunit/test.h     | 138 +++++++++++++++++++++++++++++++++++++++++++++++
> > >   kernel/panic.c           |  15 +++++-
> > >   lib/bug.c                |  10 ++++
> > >   lib/kunit/Makefile       |   3 +-
> > >   lib/kunit/bug.c          | 115 +++++++++++++++++++++++++++++++++++++++
> > >   lib/kunit/hooks-impl.h   |   2 +
> > >   7 files changed, 305 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
> > > index 47aa8f21ccce8..6237e48ceadfd 100644
> > > --- a/include/kunit/test-bug.h
> > > +++ b/include/kunit/test-bug.h
> > > @@ -23,6 +23,7 @@ DECLARE_STATIC_KEY_FALSE(kunit_running);
> > >   extern struct kunit_hooks_table {
> > >       __printf(3, 4) void (*fail_current_test)(const char*, int, const char*, ...);
> > >       void *(*get_static_stub_address)(struct kunit *test, void *real_fn_addr);
> > > +     bool (*is_suppressed_warning)(bool count);
> > >   } kunit_hooks;
> > >
> > >   /**
> > > @@ -60,9 +61,33 @@ static inline struct kunit *kunit_get_current_test(void)
> > >               }                                                               \
> > >       } while (0)
> > >
> > > +/**
> > > + * kunit_is_suppressed_warning() - Check if warnings are being suppressed
> > > + *                                 by the current KUnit test.
> > > + * @count: if true, increment the suppression counter on match.
> > > + *
> > > + * Returns true if the current task has active warning suppression.
> > > + * Uses the kunit_running static branch for zero overhead when no tests run.
> > > + *
> > > + * A single WARN*() may traverse multiple call sites in the warning path
> > > + * (e.g., __warn_printk() and __report_bug()). Pass @count = true at the
> > > + * primary suppression point to count each warning exactly once, and
> > > + * @count = false at secondary points to suppress output without
> > > + * inflating the count.
> > > + */
> > > +static inline bool kunit_is_suppressed_warning(bool count)
> > > +{
> > > +     if (!static_branch_unlikely(&kunit_running))
> > > +             return false;
> > > +
> > > +     return kunit_hooks.is_suppressed_warning &&
> > > +            kunit_hooks.is_suppressed_warning(count);
> > > +}
> > > +
> > >   #else
> > >
> > >   static inline struct kunit *kunit_get_current_test(void) { return NULL; }
> > > +static inline bool kunit_is_suppressed_warning(bool count) { return false; }
> > >
> > >   #define kunit_fail_current_test(fmt, ...) do {} while (0)
> > >
> > > diff --git a/include/kunit/test.h b/include/kunit/test.h
> > > index 9cd1594ab697d..f278ec028019c 100644
> > > --- a/include/kunit/test.h
> > > +++ b/include/kunit/test.h
> > > @@ -1795,4 +1795,142 @@ do {                                                                         \
> > >   // include resource.h themselves if they need it.
> > >   #include <kunit/resource.h>
> > >
> > > +/*
> > > + * Warning backtrace suppression API.
> > > + *
> > > + * Suppresses WARN*() backtraces on the current task while active. Three forms
> > > + * are provided, in order of convenience:
> > > + *
> > > + * - Scoped: kunit_warning_suppress(test) { ... }
> > > + *   Suppression is active for the duration of the block. On normal exit,
> > > + *   the for-loop increment deactivates suppression. On early exit (break,
> > > + *   return, goto), the __cleanup attribute fires. On kthread_exit() (e.g.,
> > > + *   a failed KUnit assertion), kunit_add_action() cleans up at test
> > > + *   teardown. The suppression handle is only accessible inside the block,
> > > + *   so warning counts must be checked before the block exits.
> > > + *
> > > + * - Manual macros: KUNIT_[START|END]_SUPPRESSED_WARNING(test)
> > > + *   Suppression spans an explicit range in the same scope. kunit_add_action()
> > > + *   guarantees cleanup even if KUNIT_END_SUPPRESSED_WARNING() is not reached.
> > > + *   Prefer this form when suppressing warnings across a large block where
> > > + *   extra indentation is undesirable, or when the warning count needs to be
> > > + *   checked after suppression ends. Limited to one pair per scope.
> > > + *
> > > + * - Direct: kunit_start_suppress_warning() / kunit_end_suppress_warning()
> > > + *   The underlying functions, returning an explicit handle pointer. Use
> > > + *   when the handle needs to be retained (e.g., for post-suppression
> > > + *   count checks) or passed across helper functions.
> > > + */
> > > +struct kunit_suppressed_warning;
> > > +
> > > +struct kunit_suppressed_warning *
> > > +kunit_start_suppress_warning(struct kunit *test);
> > > +void kunit_end_suppress_warning(struct kunit *test,
> > > +                             struct kunit_suppressed_warning *w);
> > > +int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w);
> > > +void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp);
> > > +bool kunit_has_active_suppress_warning(void);
> > > +
> > > +/**
> > > + * kunit_warning_suppress() - Suppress WARN*() backtraces for the duration
> > > + *                            of a block.
> > > + * @test: The test context object.
> > > + *
> > > + * Scoped form of the suppression API. Suppression starts when the block is
> > > + * entered and ends automatically when the block exits through any path. See
> > > + * the section comment above for the cleanup guarantees on each exit path.
> > > + * Fails the test if suppression is already active; nesting is not supported.
> > > + *
> > > + * The warning count can be checked inside the block via
> > > + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(). The handle is not accessible
> > > + * after the block exits.
> > > + *
> > > + * Example::
> > > + *
> > > + *   kunit_warning_suppress(test) {
> > > + *       trigger_warning();
> > > + *       KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> > > + *   }
> > > + */
> > > +#define kunit_warning_suppress(test)                                 \
> > > +     for (struct kunit_suppressed_warning *__kunit_suppress          \
> > > +          __cleanup(__kunit_suppress_auto_cleanup) =                 \
> > > +          kunit_start_suppress_warning(test);                        \
> > > +          __kunit_suppress;                                          \
> > > +          kunit_end_suppress_warning(test, __kunit_suppress),        \
> > > +          __kunit_suppress = NULL)
> > > +
> > > +/**
> > > + * KUNIT_START_SUPPRESSED_WARNING() - Begin suppressing WARN*() backtraces.
> > > + * @test: The test context object.
> > > + *
> > > + * Manual form of the suppression API. Must be paired with
> > > + * KUNIT_END_SUPPRESSED_WARNING() in the same scope. See the section comment
> > > + * above for cleanup guarantees. Fails the test if suppression is already
> > > + * active; nesting is not supported. Limited to one pair per scope; use
> > > + * sequential kunit_warning_suppress() blocks or the direct function API
> > > + * when more than one suppression region is needed.
> > > + *
> > > + * Example::
> > > + *
> > > + *   KUNIT_START_SUPPRESSED_WARNING(test);
> > > + *   trigger_code_that_should_warn_once();
> > > + *   KUNIT_END_SUPPRESSED_WARNING(test);
> > > + *   KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> > > + */
> > > +#define KUNIT_START_SUPPRESSED_WARNING(test)                         \
> > > +     struct kunit_suppressed_warning *__kunit_suppress =             \
> > > +             kunit_start_suppress_warning(test)
> > > +
> > > +/**
> > > + * KUNIT_END_SUPPRESSED_WARNING() - End suppressing WARN*() backtraces.
> > > + * @test: The test context object.
> > > + *
> > > + * Deactivates suppression started by KUNIT_START_SUPPRESSED_WARNING().
> > > + * The warning count remains readable via KUNIT_SUPPRESSED_WARNING_COUNT()
> > > + * after this call.
> > > + */
> > > +#define KUNIT_END_SUPPRESSED_WARNING(test)                           \
> > > +     kunit_end_suppress_warning(test, __kunit_suppress)
> > > +
> > > +/**
> > > + * KUNIT_SUPPRESSED_WARNING_COUNT() - Returns the suppressed warning count.
> > > + *
> > > + * Returns the number of WARN*() calls suppressed since the current
> > > + * suppression block started, or 0 if the handle is NULL. Usable inside a
> > > + * kunit_warning_suppress() block or after KUNIT_END_SUPPRESSED_WARNING().
> > > + */
> > > +#define KUNIT_SUPPRESSED_WARNING_COUNT() \
> > > +     kunit_suppressed_warning_count(__kunit_suppress)
> > > +
> > > +/**
> > > + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT() - Sets an expectation that the
> > > + *                                           suppressed warning count equals
> > > + *                                           @expected.
> > > + * @test: The test context object.
> > > + * @expected: an expression that evaluates to the expected warning count.
> > > + *
> > > + * Sets an expectation that the number of suppressed WARN*() calls equals
> > > + * @expected. This is semantically equivalent to
> > > + * KUNIT_EXPECT_EQ(@test, KUNIT_SUPPRESSED_WARNING_COUNT(), @expected).
> > > + * See KUNIT_EXPECT_EQ() for more information.
> > > + */
> > > +#define KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected) \
> > > +     KUNIT_EXPECT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
> > > +
> > > +/**
> > > + * KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT() - Sets an assertion that the
> > > + *                                           suppressed warning count equals
> > > + *                                           @expected.
> > > + * @test: The test context object.
> > > + * @expected: an expression that evaluates to the expected warning count.
> > > + *
> > > + * Sets an assertion that the number of suppressed WARN*() calls equals
> > > + * @expected. This is the same as KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(),
> > > + * except it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the
> > > + * assertion is not met.
> > > + */
> > > +#define KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT(test, expected) \
> > > +     KUNIT_ASSERT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
> > > +
> > >   #endif /* _KUNIT_TEST_H */
> > > diff --git a/kernel/panic.c b/kernel/panic.c
> > > index c78600212b6c1..697d8ca054bef 100644
> > > --- a/kernel/panic.c
> > > +++ b/kernel/panic.c
> > > @@ -39,6 +39,7 @@
> > >   #include <linux/sys_info.h>
> > >   #include <trace/events/error_report.h>
> > >   #include <asm/sections.h>
> > > +#include <kunit/test-bug.h>
> > >
> > >   #define PANIC_TIMER_STEP 100
> > >   #define PANIC_BLINK_SPD 18
> > > @@ -1080,9 +1081,14 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
> > >   void warn_slowpath_fmt(const char *file, int line, unsigned taint,
> > >                      const char *fmt, ...)
> > >   {
> > > -     bool rcu = warn_rcu_enter();
> > > +     bool rcu;
> > >       struct warn_args args;
> > >
> > > +     if (kunit_is_suppressed_warning(true))
> > > +             return;
> > > +
> > > +     rcu = warn_rcu_enter();
> > > +
> > >       pr_warn(CUT_HERE);
> > >
> > >       if (!fmt) {
> > > @@ -1102,9 +1108,14 @@ EXPORT_SYMBOL(warn_slowpath_fmt);
> > >   #else
> > >   void __warn_printk(const char *fmt, ...)
> > >   {
> > > -     bool rcu = warn_rcu_enter();
> > > +     bool rcu;
> > >       va_list args;
> > >
> > > +     if (kunit_is_suppressed_warning(false))
> > > +             return;
> > > +
> > > +     rcu = warn_rcu_enter();
> > > +
> > >       pr_warn(CUT_HERE);
> > >
> > >       va_start(args, fmt);
> > > diff --git a/lib/bug.c b/lib/bug.c
> > > index 623c467a8b76c..a5cebde554ed8 100644
> > > --- a/lib/bug.c
> > > +++ b/lib/bug.c
> > > @@ -48,6 +48,7 @@
> > >   #include <linux/rculist.h>
> > >   #include <linux/ftrace.h>
> > >   #include <linux/context_tracking.h>
> > > +#include <kunit/test-bug.h>
> > >
> > >   extern struct bug_entry __start___bug_table[], __stop___bug_table[];
> > >
> > > @@ -223,6 +224,15 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga
> > >       no_cut   = bug->flags & BUGFLAG_NO_CUT_HERE;
> > >       has_args = bug->flags & BUGFLAG_ARGS;
> > >
> > > +#ifdef CONFIG_KUNIT
> > > +     /*
> > > +      * Before the once logic so suppressed warnings do not consume
> > > +      * the single-fire budget of WARN_ON_ONCE().
> > > +      */
> > > +     if (warning && kunit_is_suppressed_warning(true))
> > > +             return BUG_TRAP_TYPE_WARN;
> > > +#endif
> > > +
> > >       if (warning && once) {
> > >               if (done)
> > >                       return BUG_TRAP_TYPE_WARN;
> > > diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> > > index 656f1fa35abcc..4592f9d0aa8dd 100644
> > > --- a/lib/kunit/Makefile
> > > +++ b/lib/kunit/Makefile
> > > @@ -10,7 +10,8 @@ kunit-objs +=                               test.o \
> > >                                       executor.o \
> > >                                       attributes.o \
> > >                                       device.o \
> > > -                                     platform.o
> > > +                                     platform.o \
> > > +                                     bug.o
> > >
> > >   ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
> > >   kunit-objs +=                               debugfs.o
> > > diff --git a/lib/kunit/bug.c b/lib/kunit/bug.c
> > > new file mode 100644
> > > index 0000000000000..b0b6778d7399a
> > > --- /dev/null
> > > +++ b/lib/kunit/bug.c
> > > @@ -0,0 +1,115 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * KUnit helpers for backtrace suppression
> > > + *
> > > + * Copyright (C) 2025 Alessandro Carminati <acarmina@redhat.com>
> > > + * Copyright (C) 2024 Guenter Roeck <linux@roeck-us.net>
> > > + */
> > > +
> > > +#include <kunit/resource.h>
> > > +#include <linux/export.h>
> > > +#include <linux/rculist.h>
> > > +#include <linux/sched.h>
> > > +#include <linux/spinlock.h>
> > > +
> > > +#include "hooks-impl.h"
> > > +
> > > +struct kunit_suppressed_warning {
> > > +     struct list_head node;
> > > +     struct task_struct *task;
> > > +     struct kunit *test;
> > > +     int counter;
> > > +};
> > > +
> > > +static LIST_HEAD(suppressed_warnings);
> > > +static DEFINE_SPINLOCK(suppressed_warnings_lock);
> > > +
> > > +static void kunit_suppress_warning_remove(struct kunit_suppressed_warning *w)
> > > +{
> > > +     unsigned long flags;
> > > +
> > > +     spin_lock_irqsave(&suppressed_warnings_lock, flags);
> > > +     list_del_rcu(&w->node);
> > > +     spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
> > > +     synchronize_rcu(); /* Wait for readers to finish */
> > > +}
> > > +
> > > +KUNIT_DEFINE_ACTION_WRAPPER(kunit_suppress_warning_cleanup,
> > > +                         kunit_suppress_warning_remove,
> > > +                         struct kunit_suppressed_warning *);
> > > +
> > > +bool kunit_has_active_suppress_warning(void)
> > > +{
> > > +     return __kunit_is_suppressed_warning_impl(false);
> > > +}
> > > +EXPORT_SYMBOL_GPL(kunit_has_active_suppress_warning);
> > > +
> > > +struct kunit_suppressed_warning *
> > > +kunit_start_suppress_warning(struct kunit *test)
> > > +{
> > > +     struct kunit_suppressed_warning *w;
> > > +     unsigned long flags;
> > > +     int ret;
> > > +
> > > +     if (kunit_has_active_suppress_warning()) {
> > > +             KUNIT_FAIL(test, "Another suppression block is already active");
> > > +             return NULL;
> > > +     }
> > > +
> > > +     w = kunit_kzalloc(test, sizeof(*w), GFP_KERNEL);
> > > +     if (!w)
> > > +             return NULL;
> > > +
> > > +     w->task = current;
> > > +     w->test = test;
> > > +
> > > +     spin_lock_irqsave(&suppressed_warnings_lock, flags);
> > > +     list_add_rcu(&w->node, &suppressed_warnings);
> > > +     spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
> > > +
> > > +     ret = kunit_add_action_or_reset(test,
> > > +                                     kunit_suppress_warning_cleanup, w);
> > > +     if (ret)
> > > +             return NULL;
> > > +
> > > +     return w;
> > > +}
> > > +EXPORT_SYMBOL_GPL(kunit_start_suppress_warning);
> > > +
> > > +void kunit_end_suppress_warning(struct kunit *test,
> > > +                             struct kunit_suppressed_warning *w)
> > > +{
> > > +     if (!w)
> > > +             return;
> > > +     kunit_release_action(test, kunit_suppress_warning_cleanup, w);
> > > +}
> > > +EXPORT_SYMBOL_GPL(kunit_end_suppress_warning);
> > > +
> > > +void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp)
> > > +{
> > > +     if (*wp)
> > > +             kunit_end_suppress_warning((*wp)->test, *wp);
> > > +}
> > > +EXPORT_SYMBOL_GPL(__kunit_suppress_auto_cleanup);
> > > +
> > > +int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w)
> > > +{
> > > +     return w ? w->counter : 0;
> > > +}
> > > +EXPORT_SYMBOL_GPL(kunit_suppressed_warning_count);
> > > +
> > > +bool __kunit_is_suppressed_warning_impl(bool count)
> > > +{
> > > +     struct kunit_suppressed_warning *w;
> > > +
> > > +     guard(rcu)();
> > > +     list_for_each_entry_rcu(w, &suppressed_warnings, node) {
> > > +             if (w->task == current) {
> > > +                     if (count)
> > > +                             w->counter++;
> > > +                     return true;
> > > +             }
> > > +     }
> > > +
> > > +     return false;
> > > +}
> > > diff --git a/lib/kunit/hooks-impl.h b/lib/kunit/hooks-impl.h
> > > index 4e71b2d0143ba..d8720f2616925 100644
> > > --- a/lib/kunit/hooks-impl.h
> > > +++ b/lib/kunit/hooks-impl.h
> > > @@ -19,6 +19,7 @@ void __printf(3, 4) __kunit_fail_current_test_impl(const char *file,
> > >                                                  int line,
> > >                                                  const char *fmt, ...);
> > >   void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr);
> > > +bool __kunit_is_suppressed_warning_impl(bool count);
> > >
> > >   /* Code to set all of the function pointers. */
> > >   static inline void kunit_install_hooks(void)
> > > @@ -26,6 +27,7 @@ static inline void kunit_install_hooks(void)
> > >       /* Install the KUnit hook functions. */
> > >       kunit_hooks.fail_current_test = __kunit_fail_current_test_impl;
> > >       kunit_hooks.get_static_stub_address = __kunit_get_static_stub_address_impl;
> > > +     kunit_hooks.is_suppressed_warning = __kunit_is_suppressed_warning_impl;
> > >   }
> > >
> > >   #endif /* _KUNIT_HOOKS_IMPL_H */
> > >
> >


^ permalink raw reply

* [PATCH v7 RESEND] hwmon: add driver for ARCTIC Fan Controller
From: Aureo Serrano de Souza @ 2026-05-08  6:44 UTC (permalink / raw)
  To: linux-hwmon
  Cc: linux, linux, corbet, skhan, linux-doc, linux-kernel,
	Aureo Serrano de Souza

Add hwmon driver for the ARCTIC Fan Controller, a USB HID device
(VID 0x3904, PID 0xF001) with 10 fan channels. Exposes fan speed in
RPM (read-only) and PWM duty cycle (0-255, read/write) via sysfs.

The device pushes IN reports at ~1 Hz containing RPM readings. PWM is
set via OUT reports; the device applies the new duty cycle and sends
back a 2-byte ACK (Report ID 0x02). The driver waits up to 1 s for
the ACK using a completion. Measured device latency: max ~563 ms over
500 iterations. PWM control is manual-only: the device never changes
duty cycle autonomously.

raw_event() may run in hardirq context, so fan_rpm[] is protected by
a spinlock with irq-save. pwm_duty[] is also protected by this spinlock
because reset_resume() clears it outside the hwmon core lock. The OUT
report buffer is built and write_pending is armed under the same lock so
that no reset_resume() can race with the pwm_duty[] snapshot. priv->buf
is exclusively accessed by write(), which the hwmon core serializes.

Signed-off-by: Aureo Serrano de Souza <aureo.serrano@arctic.de>
---
Resend of v7; rebased on top of hwmon-next to fix the context
mismatch that prevented the previous submission from applying.
No code changes from v7.

Thanks to Guenter Roeck and Thomas Weißschuh for the reviews.

Changes since v6:
- arctic_fan_remove(): drop the redundant hid_device_io_stop() call.
  The HID core clears hdev->io_started before invoking ->remove(), so
  the driver's call hit the "io already stopped" warning path in
  hid_device_io_stop() (drivers/hid/hid-core.c clears io_started
  before calling hdrv->remove; include/linux/hid.h's inline
  hid_device_io_stop() warns when io_started is false). The
  hid_device_io_stop() in probe()'s out_close label is kept because
  probe itself calls hid_device_io_start().
- struct arctic_fan_data: wrap buf[] in
  __dma_from_device_group_begin()/_end() instead of
  ____cacheline_aligned, as suggested by Thomas. The macro pair
  expresses the DMA-buffer semantic directly and lets the surrounding
  comment shrink. Same pattern used by drivers/virtio/virtio_input.c,
  drivers/scsi/virtio_scsi.c, drivers/char/hw_random/virtio-rng.c,
  drivers/gpio/gpio-virtio.c and net/vmw_vsock/virtio_transport.c.
- #include <linux/dma-mapping.h> for the new macros; drop the now
  unused <linux/cache.h>.

Changes since v5:
- arctic_fan_probe(): switch from devm_hwmon_device_register_with_info()
  to hwmon_device_register_with_info(); store the returned pointer in
  priv->hwmon_dev for explicit teardown in remove()
- arctic_fan_remove(): call hwmon_device_unregister(priv->hwmon_dev)
  before hid_device_io_stop/hid_hw_close/hid_hw_stop; this closes the
  use-after-free window where a concurrent sysfs write could call
  hid_hw_output_report() on an already-stopped device; matches the
  removal pattern used by nzxt-smart2 and aquacomputer_d5next
- arctic_fan_write(): expand write_pending comment to document the
  residual theoretical late-ACK race (unfixable without a correlation
  ID in the device ACK report) and its practical impossibility (observed
  max ACK latency ~563 ms, timeout 1 s; a delay > 1 s indicates a
  non-functional device)
- arctic_fan_reset_resume(), arctic_fan_read(), arctic_fan_write():
  extend in_report_lock coverage to pwm_duty[]; reset_resume() clears
  pwm_duty[] outside the hwmon core lock, so all paths that read or
  write pwm_duty[] now hold in_report_lock to prevent a data race
  during resume
- arctic_fan_write(): build the OUT report buffer inside in_report_lock
  so reset_resume() cannot clear pwm_duty[] between the pwm_duty[]
  snapshot and the buffer write; this makes the lock coverage complete

Changes since v4:
- arctic_fan_write(): switch to wait_for_completion_timeout() (non-
  interruptible); eliminates the signal-interrupted write case of the
  late-ACK race that write_pending could not fully prevent
- arctic_fan_write(): guard pwm_duty[channel] commit with
  ack_status == 0 check; a device error ACK (status 0x01) no longer
  silently poisons the cached duty used in future OUT reports
- arctic_fan_probe()/remove(): replace devm_add_action_or_reset() +
  no-op remove() with explicit hid_device_io_stop/hid_hw_close/
  hid_hw_stop in remove(); devm_add_action_or_reset() was called after
  hdev->driver = NULL, causing a NULL deref in hid_hw_close() on unbind
- add reset_resume callback: device resets PWM to hardware defaults on
  power loss during suspend; driver now clears cached pwm_duty[] on
  reset-resume so stale pre-suspend values are not re-sent as if valid
- Documentation/hwmon/arctic_fan_controller.rst: document suspend/
  resume behaviour and the updated pwm[1-10] read semantics

Changes since v3:
- buf[]: upgrade from __aligned(8) to ____cacheline_aligned so the
  DMA buffer occupies its own cache line, preventing false sharing with
  adjacent fan_rpm[]/pwm_duty[] fields on non-coherent architectures
- arctic_fan_write(): add write_pending flag (protected by
  in_report_lock) so raw_event() delivers ACKs only while a write is
  in flight
- arctic_fan_write(): commit pwm_duty[channel] only after the device
  ACKs the command; a failed or timed-out write no longer leaves a
  stale value in the cached duty state
- arctic_fan_probe(): start IO (hid_device_io_start) before registering
  with hwmon; previously a sysfs write arriving between hwmon
  registration and io_start could send an OUT report whose ACK would be
  discarded by the HID core, causing a spurious timeout
- Documentation/hwmon/arctic_fan_controller.rst: document that cached
  PWM values start at 0 (hardware state unknown at probe) and that each
  OUT report carries all 10 channel values

Changes since v2:
- buf[]: add __aligned(8) for DMA safety
- ARCTIC_ACK_TIMEOUT_MS: restore 1000 ms; note observed max ~563 ms
- arctic_fan_parse_report(): replace hwmon_lock/hwmon_unlock with
  spin_lock_irqsave; hwmon_lock() may sleep and is unsafe when
  raw_event() runs in hardirq/softirq context
- arctic_fan_raw_event(): use spin_lock_irqsave for ACK path
- arctic_fan_write(): use spin_lock_irqsave for completion reinit
- arctic_fan_write(): clamp val to [0, 255] before u8 cast
- remove priv->hwmon_dev (no longer needed)

Changes since v1:
- Use hid_dbg() instead of module_param debug flag
- Move hid_device_id table adjacent to hid_driver struct
- Use get_unaligned_le16() for RPM parsing
- Remove impossible bounds/NULL checks; remove retry loop
- Add hid_is_usb() guard
- Do not update pwm_duty from IN reports (device is manual-only)
- Add completion/ACK mechanism for OUT report acknowledgment
- Add Documentation/hwmon/arctic_fan_controller.rst and MAINTAINERS

 Documentation/hwmon/arctic_fan_controller.rst |  56 +++
 Documentation/hwmon/index.rst                 |   1 +
 MAINTAINERS                                   |   7 +
 drivers/hwmon/Kconfig                         |  12 +
 drivers/hwmon/Makefile                        |   1 +
 drivers/hwmon/arctic_fan_controller.c         | 374 ++++++++++++++++++
 6 files changed, 451 insertions(+)
 create mode 100644 Documentation/hwmon/arctic_fan_controller.rst
 create mode 100644 drivers/hwmon/arctic_fan_controller.c

diff --git a/Documentation/hwmon/arctic_fan_controller.rst b/Documentation/hwmon/arctic_fan_controller.rst
new file mode 100644
index 0000000000..b5be88ae46
--- /dev/null
+++ b/Documentation/hwmon/arctic_fan_controller.rst
@@ -0,0 +1,56 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver arctic_fan_controller
+=====================================
+
+Supported devices:
+
+* ARCTIC Fan Controller (USB HID, VID 0x3904, PID 0xF001)
+
+Author: Aureo Serrano de Souza <aureo.serrano@arctic.de>
+
+Description
+-----------
+
+This driver provides hwmon support for the ARCTIC Fan Controller, a USB
+Custom HID device with 10 fan channels. The device sends IN reports about
+once per second containing current RPM values (bytes 11-30, 10 x uint16 LE).
+Fan speed control is manual-only: the device does not change PWM
+autonomously; it only applies a new duty cycle when it receives an OUT
+report from the host.
+
+After the device applies an OUT report, it sends back a 2-byte ACK IN
+report (Report ID 0x02, byte 1 = 0x00 on success) confirming the command
+was applied.
+
+Usage notes
+-----------
+
+Since it is a USB device, hotplug is supported. The device is autodetected.
+
+The device does not support GET_REPORT, so the driver cannot read back the
+current hardware PWM state at probe time. The cached PWM values (readable
+via pwm[1-10]) start at 0 and reflect only values that have been
+successfully written. Because each OUT report carries all 10 channel values,
+writing a single channel also sends the cached values for all other channels.
+Users should set all channels to the desired values before relying on the
+cached state.
+
+On system suspend, the device may lose power and reset its PWM channels to
+hardware defaults. The driver clears its cached duty values on resume so
+that reads reflect the unknown hardware state rather than stale pre-suspend
+values. Userspace is responsible for re-applying the desired duty cycles
+after resume.
+
+Sysfs entries
+-------------
+
+================ ==============================================================
+fan[1-10]_input  Fan speed in RPM (read-only). Updated from IN reports at ~1 Hz.
+pwm[1-10]        PWM duty cycle (0-255). Write: sends an OUT report setting the
+                 duty cycle (scaled from 0-255 to 0-100% for the device);
+                 the cached value is updated only after the device ACKs the
+                 command with a success status. Read: returns the last
+                 successfully written value; initialized to 0 at driver load
+                 and after resume (hardware state unknown).
+================ ==============================================================
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 595350bfc0..3cb9228f08 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -43,6 +43,7 @@ Hardware Monitoring Kernel Drivers
    amc6821
    aps-379
    aquacomputer_d5next
+   arctic_fan_controller
    asb100
    asc7621
    aspeed-g6-pwm-tach
diff --git a/MAINTAINERS b/MAINTAINERS
index 9fa9702d34..b9f5fe6d1b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2064,6 +2064,13 @@ S:	Maintained
 F:	drivers/net/arcnet/
 F:	include/uapi/linux/if_arcnet.h

+ARCTIC FAN CONTROLLER DRIVER
+M:	Aureo Serrano de Souza <aureo.serrano@arctic.de>
+L:	linux-hwmon@vger.kernel.org
+S:	Maintained
+F:	Documentation/hwmon/arctic_fan_controller.rst
+F:	drivers/hwmon/arctic_fan_controller.c
+
 ARM AND ARM64 SoC SUB-ARCHITECTURES (COMMON PARTS)
 M:	Arnd Bergmann <arnd@arndb.de>
 M:	Krzysztof Kozlowski <krzk@kernel.org>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 14e4cea48a..997bba56fe 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -388,6 +388,18 @@ config SENSORS_APPLESMC
 	  Say Y here if you have an applicable laptop and want to experience
 	  the awesome power of applesmc.

+config SENSORS_ARCTIC_FAN_CONTROLLER
+	tristate "ARCTIC Fan Controller"
+	depends on USB_HID
+	help
+	  If you say yes here you get support for the ARCTIC Fan Controller,
+	  a USB HID device (VID 0x3904, PID 0xF001) with 10 fan channels.
+	  The driver exposes fan speed (RPM) and PWM control via the hwmon
+	  sysfs interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called arctic_fan_controller.
+
 config SENSORS_ARM_SCMI
 	tristate "ARM SCMI Sensors"
 	depends on ARM_SCMI_PROTOCOL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 982ee2c6f9..efbd1cb818 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SENSORS_ADT7475)	+= adt7475.o
 obj-$(CONFIG_SENSORS_AHT10)	+= aht10.o
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AQUACOMPUTER_D5NEXT) += aquacomputer_d5next.o
+obj-$(CONFIG_SENSORS_ARCTIC_FAN_CONTROLLER)	+= arctic_fan_controller.o
 obj-$(CONFIG_SENSORS_ARM_SCMI)	+= scmi-hwmon.o
 obj-$(CONFIG_SENSORS_ARM_SCPI)	+= scpi-hwmon.o
 obj-$(CONFIG_SENSORS_AS370)	+= as370-hwmon.o
diff --git a/drivers/hwmon/arctic_fan_controller.c b/drivers/hwmon/arctic_fan_controller.c
new file mode 100644
index 0000000000..dbe84cd93c
--- /dev/null
+++ b/drivers/hwmon/arctic_fan_controller.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Linux hwmon driver for ARCTIC Fan Controller
+ *
+ * USB Custom HID device with 10 fan channels.
+ * Exposes fan RPM (input) and PWM (0-255) via hwmon. Device pushes IN reports
+ * at ~1 Hz; no GET_REPORT. OUT reports set PWM duty (bytes 1-10, 0-100%).
+ * PWM is manual-only: the device does not change duty autonomously, only
+ * when it receives an OUT report from the host.
+ */
+
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#define ARCTIC_VID			0x3904
+#define ARCTIC_PID			0xF001
+#define ARCTIC_NUM_FANS			10
+#define ARCTIC_OUTPUT_REPORT_ID		0x01
+#define ARCTIC_REPORT_LEN		32
+#define ARCTIC_RPM_OFFSET		11	/* bytes 11-30: 10 x uint16 LE */
+/* ACK report: device sends Report ID 0x02, 2 bytes (ID + status) after applying OUT report */
+#define ARCTIC_ACK_REPORT_ID		0x02
+#define ARCTIC_ACK_REPORT_LEN		2
+/*
+ * Time to wait for ACK report after send.
+ * Measured over 500 iterations: max ~563 ms. Keep 1 s as margin.
+ */
+#define ARCTIC_ACK_TIMEOUT_MS		1000
+
+struct arctic_fan_data {
+	struct hid_device *hdev;
+	struct device *hwmon_dev;	/* stored for explicit unregister in remove() */
+	spinlock_t in_report_lock;	/* protects fan_rpm, ack_status, write_pending, pwm_duty */
+	struct completion in_report_received; /* ACK (ID 0x02) received in raw_event */
+	int ack_status;			/* 0 = OK, negative errno on device error */
+	bool write_pending;		/* true while an OUT report ACK is in flight */
+	u32 fan_rpm[ARCTIC_NUM_FANS];
+	u8 pwm_duty[ARCTIC_NUM_FANS];	/* 0-255 matching sysfs range; converted to 0-100 on send */
+	/*
+	 * OUT report buffer passed to hid_hw_output_report(). Embedded in the
+	 * devm_kzalloc'd struct so it is heap-allocated and passes
+	 * usb_hcd_map_urb_for_dma(). Exclusively accessed by write(), which
+	 * the hwmon core serializes.
+	 */
+	__dma_from_device_group_begin();
+	u8 buf[ARCTIC_REPORT_LEN];
+	__dma_from_device_group_end();
+};
+
+/*
+ * Parse RPM values from the periodic status report (10 x uint16 LE at rpm_off).
+ * pwm_duty is not updated from the report: the device is manual-only, so the
+ * host cache is the authoritative source for PWM.
+ * Called from raw_event which may run in IRQ context; must not sleep.
+ */
+static void arctic_fan_parse_report(struct arctic_fan_data *priv, u8 *buf,
+				    int len, int rpm_off)
+{
+	unsigned long flags;
+	int i;
+
+	if (len < rpm_off + 20)
+		return;
+
+	spin_lock_irqsave(&priv->in_report_lock, flags);
+	for (i = 0; i < ARCTIC_NUM_FANS; i++)
+		priv->fan_rpm[i] = get_unaligned_le16(&buf[rpm_off + i * 2]);
+	spin_unlock_irqrestore(&priv->in_report_lock, flags);
+}
+
+/*
+ * raw_event: IN reports.
+ *
+ * Status report: Report ID 0x01, 32 bytes:
+ *   byte 0 = report ID, bytes 1-10 = PWM 0-100%, bytes 11-30 = 10 x RPM uint16 LE.
+ *   Device pushes these at ~1 Hz; no GET_REPORT.
+ *
+ * ACK report: Report ID 0x02, 2 bytes:
+ *   byte 0 = 0x02, byte 1 = status (0x00 = OK, 0x01 = ERROR).
+ *   Sent once after accepting and applying an OUT report (ID 0x01).
+ */
+static int arctic_fan_raw_event(struct hid_device *hdev,
+				struct hid_report *report, u8 *data, int size)
+{
+	struct arctic_fan_data *priv = hid_get_drvdata(hdev);
+	unsigned long flags;
+
+	hid_dbg(hdev, "arctic_fan: raw_event id=%u size=%d\n", report->id, size);
+
+	if (report->id == ARCTIC_ACK_REPORT_ID && size == ARCTIC_ACK_REPORT_LEN) {
+		spin_lock_irqsave(&priv->in_report_lock, flags);
+		/*
+		 * Only deliver if a write is in flight. This prevents a
+		 * late-arriving ACK from a timed-out write from erroneously
+		 * satisfying a subsequent write's completion wait.
+		 */
+		if (priv->write_pending) {
+			priv->ack_status = data[1] == 0x00 ? 0 : -EIO;
+			complete(&priv->in_report_received);
+		}
+		spin_unlock_irqrestore(&priv->in_report_lock, flags);
+		return 0;
+	}
+
+	if (report->id != ARCTIC_OUTPUT_REPORT_ID || size != ARCTIC_REPORT_LEN) {
+		hid_dbg(hdev, "arctic_fan: raw_event id=%u size=%d ignored\n",
+			report->id, size);
+		return 0;
+	}
+
+	arctic_fan_parse_report(priv, data, size, ARCTIC_RPM_OFFSET);
+	return 0;
+}
+
+static umode_t arctic_fan_is_visible(const void *data,
+				     enum hwmon_sensor_types type,
+				     u32 attr, int channel)
+{
+	if (type == hwmon_fan && attr == hwmon_fan_input)
+		return 0444;
+	if (type == hwmon_pwm && attr == hwmon_pwm_input)
+		return 0644;
+	return 0;
+}
+
+static int arctic_fan_read(struct device *dev, enum hwmon_sensor_types type,
+			   u32 attr, int channel, long *val)
+{
+	struct arctic_fan_data *priv = dev_get_drvdata(dev);
+	unsigned long flags;
+
+	if (type == hwmon_fan && attr == hwmon_fan_input) {
+		spin_lock_irqsave(&priv->in_report_lock, flags);
+		*val = priv->fan_rpm[channel];
+		spin_unlock_irqrestore(&priv->in_report_lock, flags);
+		return 0;
+	}
+	if (type == hwmon_pwm && attr == hwmon_pwm_input) {
+		spin_lock_irqsave(&priv->in_report_lock, flags);
+		*val = priv->pwm_duty[channel];
+		spin_unlock_irqrestore(&priv->in_report_lock, flags);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int arctic_fan_write(struct device *dev, enum hwmon_sensor_types type,
+			    u32 attr, int channel, long val)
+{
+	struct arctic_fan_data *priv = dev_get_drvdata(dev);
+	u8 new_duty = (u8)clamp_val(val, 0, 255);
+	unsigned long flags;
+	unsigned long t;
+	int i, ret;
+
+	/*
+	 * Build the buffer and arm write_pending under in_report_lock so that
+	 * reset_resume() cannot clear pwm_duty[] between the pwm_duty[] read
+	 * and the buffer write, and raw_event() cannot deliver a stale ACK
+	 * from a previous write into this write's completion.
+	 *
+	 * priv->buf is heap-allocated (embedded in the devm_kzalloc'd struct),
+	 * satisfying usb_hcd_map_urb_for_dma(). Exclusively accessed by
+	 * write() which the hwmon core serializes.
+	 *
+	 * pwm_duty[channel] is committed only after a positive device ACK so a
+	 * failed or timed-out write does not corrupt the cached state.
+	 *
+	 * Residual theoretical race: if write A times out (write_pending
+	 * cleared), write B sets write_pending = true, and a late ACK from
+	 * write A—delayed beyond ARCTIC_ACK_TIMEOUT_MS—arrives during write
+	 * B's pending window, it would falsely satisfy write B's completion.
+	 * This cannot be prevented in driver code without protocol support
+	 * (for example, a correlation ID echoed in the device ACK report).
+	 * In testing, observed ACK latency stayed below the 1 s timeout
+	 * (maximum ~563 ms over 500 iterations).
+	 *
+	 * The wait is non-interruptible so that a signal cannot cause write()
+	 * to return early while the OUT report is already in flight; an
+	 * interruptible early return would create the same late-ACK window
+	 * without even the timeout guard.
+	 * Serialized by the hwmon core: only one arctic_fan_write() at a time.
+	 * Use irqsave to match the IRQ context in which raw_event may run.
+	 */
+	spin_lock_irqsave(&priv->in_report_lock, flags);
+	priv->buf[0] = ARCTIC_OUTPUT_REPORT_ID;
+	for (i = 0; i < ARCTIC_NUM_FANS; i++) {
+		u8 d = i == channel ? new_duty : priv->pwm_duty[i];
+
+		priv->buf[1 + i] = DIV_ROUND_CLOSEST((unsigned int)d * 100, 255);
+	}
+	priv->ack_status = -ETIMEDOUT;
+	priv->write_pending = true;
+	reinit_completion(&priv->in_report_received);
+	spin_unlock_irqrestore(&priv->in_report_lock, flags);
+
+	ret = hid_hw_output_report(priv->hdev, priv->buf, ARCTIC_REPORT_LEN);
+	if (ret < 0) {
+		spin_lock_irqsave(&priv->in_report_lock, flags);
+		priv->write_pending = false;
+		spin_unlock_irqrestore(&priv->in_report_lock, flags);
+		return ret;
+	}
+
+	t = wait_for_completion_timeout(&priv->in_report_received,
+					msecs_to_jiffies(ARCTIC_ACK_TIMEOUT_MS));
+	spin_lock_irqsave(&priv->in_report_lock, flags);
+	priv->write_pending = false;
+	/* Commit inside the lock so reset_resume() cannot race with this write */
+	if (t && priv->ack_status == 0)
+		priv->pwm_duty[channel] = new_duty;
+	spin_unlock_irqrestore(&priv->in_report_lock, flags);
+
+	if (!t)
+		return -ETIMEDOUT;
+	return priv->ack_status; /* 0=OK, -EIO=device error */
+}
+
+static const struct hwmon_ops arctic_fan_ops = {
+	.is_visible = arctic_fan_is_visible,
+	.read = arctic_fan_read,
+	.write = arctic_fan_write,
+};
+
+static const struct hwmon_channel_info *arctic_fan_info[] = {
+	HWMON_CHANNEL_INFO(fan,
+			   HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT,
+			   HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT,
+			   HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT,
+			   HWMON_F_INPUT),
+	HWMON_CHANNEL_INFO(pwm,
+			   HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT,
+			   HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT,
+			   HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT,
+			   HWMON_PWM_INPUT),
+	NULL
+};
+
+static const struct hwmon_chip_info arctic_fan_chip_info = {
+	.ops = &arctic_fan_ops,
+	.info = arctic_fan_info,
+};
+
+static int arctic_fan_reset_resume(struct hid_device *hdev)
+{
+	struct arctic_fan_data *priv = hid_get_drvdata(hdev);
+	unsigned long flags;
+
+	/*
+	 * The device resets its PWM channels to hardware defaults on power
+	 * loss during suspend. Clear the cached duty values so they reflect
+	 * the unknown hardware state, consistent with probe-time behaviour
+	 * (the device has no GET_REPORT support). Hold in_report_lock so
+	 * this does not race with a concurrent pwm read or write callback.
+	 */
+	spin_lock_irqsave(&priv->in_report_lock, flags);
+	memset(priv->pwm_duty, 0, sizeof(priv->pwm_duty));
+	spin_unlock_irqrestore(&priv->in_report_lock, flags);
+	return 0;
+}
+
+static int arctic_fan_probe(struct hid_device *hdev,
+			    const struct hid_device_id *id)
+{
+	struct arctic_fan_data *priv;
+	int ret;
+
+	if (!hid_is_usb(hdev))
+		return -ENODEV;
+
+	ret = hid_parse(hdev);
+	if (ret)
+		return ret;
+
+	priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->hdev = hdev;
+	spin_lock_init(&priv->in_report_lock);
+	init_completion(&priv->in_report_received);
+	hid_set_drvdata(hdev, priv);
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DRIVER);
+	if (ret)
+		return ret;
+
+	ret = hid_hw_open(hdev);
+	if (ret)
+		goto out_stop;
+
+	/*
+	 * Start IO before registering with hwmon. If IO were started after
+	 * hwmon registration, a sysfs write arriving in that narrow window
+	 * would send an OUT report but the ACK could not be delivered (the HID
+	 * core discards events until io_started), causing a spurious timeout.
+	 */
+	hid_device_io_start(hdev);
+
+	/*
+	 * Use the non-devm variant and store the pointer so remove() can
+	 * call hwmon_device_unregister() before tearing down the HID
+	 * transport. devm_hwmon_device_register_with_info() would defer
+	 * unregistration until after remove() returns, leaving a window
+	 * where a concurrent sysfs write could call hid_hw_output_report()
+	 * on an already-stopped device (use-after-free).
+	 */
+	priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "arctic_fan",
+							  priv, &arctic_fan_chip_info,
+							  NULL);
+	if (IS_ERR(priv->hwmon_dev)) {
+		ret = PTR_ERR(priv->hwmon_dev);
+		goto out_close;
+	}
+
+	return 0;
+
+out_close:
+	hid_device_io_stop(hdev);
+	hid_hw_close(hdev);
+out_stop:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void arctic_fan_remove(struct hid_device *hdev)
+{
+	struct arctic_fan_data *priv = hid_get_drvdata(hdev);
+
+	/*
+	 * Unregister hwmon before stopping the HID transport. This removes
+	 * the sysfs files and waits for any in-progress write() callback to
+	 * return, so no hwmon op can call hid_hw_output_report() after
+	 * hid_hw_stop() frees the underlying USB resources.
+	 * Matches the pattern used by nzxt-smart2 and aquacomputer_d5next.
+	 *
+	 * The HID core clears hdev->io_started before invoking ->remove(),
+	 * so hid_device_io_stop() is not called here; doing so would emit
+	 * a spurious "io already stopped" warning.
+	 */
+	hwmon_device_unregister(priv->hwmon_dev);
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id arctic_fan_id_table[] = {
+	{ HID_USB_DEVICE(ARCTIC_VID, ARCTIC_PID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, arctic_fan_id_table);
+
+static struct hid_driver arctic_fan_driver = {
+	.name = "arctic_fan",
+	.id_table = arctic_fan_id_table,
+	.probe = arctic_fan_probe,
+	.remove = arctic_fan_remove,
+	.raw_event = arctic_fan_raw_event,
+	.reset_resume = arctic_fan_reset_resume,
+};
+
+module_hid_driver(arctic_fan_driver);
+
+MODULE_AUTHOR("Aureo Serrano de Souza <aureo.serrano@arctic.de>");
+MODULE_DESCRIPTION("HID hwmon driver for ARCTIC Fan Controller");
+MODULE_LICENSE("GPL");
--
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 10/11] MAINTAINERS: make clearer about what's expected for "P" field
From: Mauro Carvalho Chehab @ 2026-05-08  6:42 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Jonathan Corbet, Linux Doc Mailing List, linux-kernel,
	rust-for-linux
In-Reply-To: <CANiq72m_+CR1BikHHuAR2c-srGOtx0WPOF6wfDD9ASkbE5L8oQ@mail.gmail.com>

On Tue, 5 May 2026 20:02:03 +0200
Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> wrote:

> On Tue, May 5, 2026 at 3:32 PM Mauro Carvalho Chehab
> <mchehab+huawei@kernel.org> wrote:
> >
> > extenal site. Make it clearer.  
> 
> Typo.
> 
> > +          patches to the given subsystem. This is either an in-tree .rst file,  
> 
> Should this mention that the file should be within `Doc/`, due to the
> requirement of the current system?

Makes sense, I'll add the corresponding text there.

> 
> By the way, are we keeping the parser change to avoid breakage in case
> someone does not follow this? Or should this be
> 
>     ideally, an in-tree .rst file within Documentation/ for best rendering
> 
> or similar?

The parser won't break if P: violates the .rst rule. Yet, doing
something about that seems to be the right thing to do. Perhaps
the best would be to output a warning instead.

> 
> (Also, if the parser is changed, then I am ambivalent about changing
> the `P:` to GitHub, since the local file is available by definition,
> but GitHub may not... But it is fine either way!)
> 
> Thanks!
> 
> Cheers,
> Miguel



Thanks,
Mauro

^ permalink raw reply

* Re: [PATCH v2 04/11] docs: maintainers_include: clean most SPHINXDIRS=process warnings
From: Mauro Carvalho Chehab @ 2026-05-08  6:32 UTC (permalink / raw)
  To: Randy Dunlap
  Cc: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab,
	linux-kernel, rust-for-linux, Shuah Khan
In-Reply-To: <72626637-2ed7-40c6-b5fb-75664e59e377@infradead.org>

On Tue, 5 May 2026 12:53:01 -0700
Randy Dunlap <rdunlap@infradead.org> wrote:

> On 5/5/26 6:25 AM, Mauro Carvalho Chehab wrote:
> > building docs with SPHINXDIRS=process is too noisy, as it
> > generates lots of undefined refs. Fixing it is easy: just let
> > linkify generate html URLs for the broken links when SPHINXDIRS
> > is used.
> > 
> > Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>  
> 
> Is this specific to SPHINXDIRS=process?
> I don't see anything here checking for "process".
> 
> If this is process-specific, why?
> Or is it just for SPHINXDIRS="<subdir(s)>"?

It is specific with using SPHINXDIRS=<subdir> where <subdir>
contains one of the two meta-tags defined by the extension.
Yet, right now, just "process" subdir has them:

	$ git grep "\.\. maint" Documentation
	Documentation/process/maintainer-handbooks.rst:.. maintainers-profile-toc::
	Documentation/process/maintainers.rst:.. maintainers-include::

See, what this patch does in practice is to avoid a massive flood of
warnings if one uses:

	$ make SPHINXDIRS=process htmldocs

As ".. maintainers-include::" would create lots of ":doc:" references
to documents outside "process" subdir. With this patch, they're
converted into https links to docs.kernel.org, thus cleaning up the
warnings, while creating an output that will contain working cross
references.

Thanks,
Mauro

^ permalink raw reply

* Re: [PATCH] char: applicom: remove low-quality, unused driver
From: Arnd Bergmann @ 2026-05-08  6:30 UTC (permalink / raw)
  To: Ethan Nelson-Moore, linux-kernel, linux-doc
  Cc: Jonathan Corbet, Shuah Khan, Greg Kroah-Hartman, Jiri Slaby,
	Max Nikulin, Martin K. Petersen
In-Reply-To: <20260503035824.24078-1-enelsonmoore@gmail.com>

On Sun, May 3, 2026, at 05:58, Ethan Nelson-Moore wrote:
> The applicom driver supports PCI Profibus cards from Applicom, later
> acquired by Molex. It has severe coding style issues and has attracted
> a number of bug and security fixes over the years, despite the fact
> that no one appears to be using it. It was broken from at least the
> beginning of Git history (Linux 2.6.12-rc2 in April 2005) until October
> 2008, when a fatal bug was fixed in commit bc20589bf1c6 ("applicom.c:
> fix apparently-broken code in do_ac_read()"). In the commit message,
> the author commented that no one they knew was able to test the change.
> Since then, there have been no commits that indicate the driver is
> being used. Later PCI and PCI-Express Applicom Profibus cards only
> officially support Windows [1], and even the PCI-Express cards have
> been discontinued [2]. Given all these factors, remove the driver to
> reduce future maintenance workload.
>
> [1] 
> https://www.sarcitalia.it/file_upload/prodotti//PCIE1500S7_PFB_987651-3769_0876250001505823933.pdf
> [2] 
> https://us.rs-online.com/product/molex-woodhead-brad/112011-5026/70631928/
>
> Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>

I recently looked at this when another fix came in and came to
similar conclusions, but at the wasn't sure if any users remain:

https://lore.kernel.org/all/5c4965c8-dcec-4faf-bd87-19ca7665fedc@app.fastmail.com/

Let's go with your patch now. If users do remain, they should
be able to either just revert the removal locally, or work
on reintroducing an improved driver.

Acked-by: Arnd Bergmann <arnd@arndb.de>

^ permalink raw reply

* Re: [PATCH v2] cpufreq: elanfreq: Drop support for AMD Elan SC4*
From: Viresh Kumar @ 2026-05-08  5:47 UTC (permalink / raw)
  To: Sean Young
  Cc: Jonathan Corbet, Shuah Khan, Rafael J. Wysocki, Zhongqiu Han,
	linux-doc, linux-kernel, linux-pm
In-Reply-To: <20260507090107.10113-1-sean@mess.org>

On 07-05-26, 10:01, Sean Young wrote:
> Since commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"),
> the AMD Elan SC4* is no longer supported, so the cpu frequency
> driver is no longer needed.
> 
> Signed-off-by: Sean Young <sean@mess.org>
> ---
> Changes since v1:
>  - Also removes elanfreq= entry from kernel-parameters.txt
> 
>  .../admin-guide/kernel-parameters.txt         |   4 -
>  drivers/cpufreq/Kconfig.x86                   |  15 --
>  drivers/cpufreq/Makefile                      |   1 -
>  drivers/cpufreq/elanfreq.c                    | 226 ------------------
>  4 files changed, 246 deletions(-)
>  delete mode 100644 drivers/cpufreq/elanfreq.c

Acked-by: Viresh Kumar <viresh.kumar@linaro.org>

-- 
viresh

^ permalink raw reply

* Re: [PATCH v3 2/2] hwmon: add AMD Promontory 21 xHCI temperature sensor support
From: Jihong Min @ 2026-05-08  5:42 UTC (permalink / raw)
  To: Guenter Roeck, Jihong Min
  Cc: Greg Kroah-Hartman, Mathias Nyman, Jonathan Corbet, Shuah Khan,
	Mario Limonciello, Basavaraj Natikar, linux-usb, linux-hwmon,
	linux-doc, linux-pci, linux-kernel
In-Reply-To: <35c2436b-d172-4172-a684-a96c4a0dcabe@roeck-us.net>

I believe I have addressed the other review comments for v4, including the
remaining discussion from v2 2/2, your comments, and Mathias's suggestion to
move the PROM21-specific xHCI PCI handling into a separate glue driver.

I agree that "prom21_hwmon" is not a good name.

I think just "prom21" may also be too broad, because Promontory 21 is a
chipset/IP block with multiple I/O functions. This driver monitors the
temperature value exposed through the PROM21 xHCI PCI function's MMIO 
BAR, so
the xHCI part should probably be visible in the hwmon naming.

I am considering:

   - drivers/hwmon/prom21-xhci.c
   - CONFIG_SENSORS_PROM21_XHCI
   - hwmon name: prom21_xhci

while keeping the USB glue as xhci-pci-prom21.c.

Does that sound reasonable?

Sincerely,
Jihong Min

On 5/8/26 00:53, Guenter Roeck wrote:
> On Thu, May 07, 2026 at 12:31:59PM +0900, Jihong Min wrote:
>> PROM21 xHCI controllers expose an 8-bit temperature value through a
>> vendor-specific index/data register pair in the xHCI PCI MMIO BAR
>> region. Add an auxiliary hwmon driver for PROM21 controllers with PCI
>> ID 1022:43fd.
>>
>> PROM21 is an AMD chipset IP used in single-chip or daisy-chained
>> configurations to build AMD 6xx/8xx series chipsets.
>>
>> The vendor index register is at byte offset 0x3000 from the xHCI MMIO
>> BAR base and the vendor data register is at byte offset 0x3008. The
>> driver writes register selector 0x0001e520 to the index register, reads
>> the raw temperature value from the low 8 bits of the data register, and
>> restores the previous index before returning. Expose temp1_input and an
>> xHCI label through hwmon.
>>
>> Register the hwmon device under the parent PCI function so userspace
>> reports it as a PCI adapter, while the auxiliary driver still owns the
>> hwmon lifetime and unregisters it from the auxiliary remove path.
>>
>> No public AMD reference is available for this value. The conversion
>> formula is derived from observed temperature readings:
>>
>>    temp[C] = raw * 0.9066 - 78.624
>>
>> Testing showed that the temperature register does not return a valid
>> value while the xHCI PCI function is runtime suspended. By default, the
>> driver does not wake the parent PCI device from hwmon reads and returns
>> -EPERM while the device is suspended.
> Seriously ? Why would this be a permission issue ? Make it -ENODATA.
>
>> Document the supported device, register access, conversion formula,
>> module parameter, sysfs attributes, and sysfs lookup method.
>>
>> Assisted-by: Codex:gpt-5.5
>> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
>> ---
>>   Documentation/hwmon/index.rst        |   1 +
>>   Documentation/hwmon/prom21-hwmon.rst |  86 ++++++++
>>   drivers/hwmon/Kconfig                |  11 +
>>   drivers/hwmon/Makefile               |   1 +
>>   drivers/hwmon/prom21-hwmon.c         | 293 +++++++++++++++++++++++++++
>>   5 files changed, 392 insertions(+)
>>   create mode 100644 Documentation/hwmon/prom21-hwmon.rst
>>   create mode 100644 drivers/hwmon/prom21-hwmon.c
>>
>> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
>> index 8b655e5d6b68..41072977f0ef 100644
>> --- a/Documentation/hwmon/index.rst
>> +++ b/Documentation/hwmon/index.rst
>> @@ -216,6 +216,7 @@ Hardware Monitoring Kernel Drivers
>>      pmbus
>>      powerz
>>      powr1220
>> +   prom21-hwmon
>>      pt5161l
>>      pxe1610
>>      pwm-fan
>> diff --git a/Documentation/hwmon/prom21-hwmon.rst b/Documentation/hwmon/prom21-hwmon.rst
>> new file mode 100644
>> index 000000000000..0ba763e68ae9
>> --- /dev/null
>> +++ b/Documentation/hwmon/prom21-hwmon.rst
>> @@ -0,0 +1,86 @@
>> +.. SPDX-License-Identifier: GPL-2.0
>> +
>> +Kernel driver prom21-hwmon
>> +==========================
>> +
>> +Supported chips:
>> +
>> +  * AMD Promontory 21 (PROM21) xHCI
>> +
>> +    Prefix: 'prom21_hwmon'
> The "hwmon" in this name is redundant. Yes, I know, others like it too,
> but it is still redundant. I won't comment on it further, though.
>
>> +
>> +    PCI ID: 1022:43fd
>> +
>> +Author:
>> +
>> +  - Jihong Min <hurryman2212@gmail.com>
>> +
>> +Description
>> +-----------
>> +
>> +This driver exposes the temperature sensor in AMD PROM21 xHCI controllers.
>> +
>> +The driver binds to an auxiliary device created by the xHCI PCI driver for
>> +supported controllers. The sensor value is accessed through a vendor-specific
>> +index/data register pair in the controller's PCI MMIO BAR.
>> +
>> +PROM21 is an AMD chipset IP used in single-chip or daisy-chained configurations
>> +to build AMD 6xx/8xx series chipsets. Since the xHCI controllers are
>> +integrated in PROM21, this temperature can also be used as a monitor for a
>> +temperature close to the AMD chipset temperature.
>> +
>> +Register access
>> +---------------
>> +
>> +The temperature value is read through a vendor-specific index/data register
>> +pair in the xHCI PCI MMIO BAR. The driver uses the following byte offsets from
>> +the MMIO BAR base:
>> +
>> +======================= =====================================================
>> +0x3000			Vendor index register
>> +0x3008			Vendor data register
>> +======================= =====================================================
>> +
>> +The driver saves the current vendor index register value, writes the
>> +temperature selector ``0x0001e520`` to the vendor index register, reads the
>> +vendor data register, and restores the previous vendor index value before
>> +returning. The raw temperature value is the low 8 bits of the vendor data
>> +register value.
>> +
>> +No public AMD reference is available for the raw value. The temperature
>> +conversion formula is derived from observed PROM21 xHCI temperature readings:
>> +
>> +  temp[C] = raw * 0.9066 - 78.624
>> +
>> +Module parameters
>> +-----------------
>> +
>> +pm: bool
>> +  Allow runtime PM state changes for device memory access. This is disabled
>> +  by default. If disabled, the driver does not wake the xHCI PCI device from
>> +  a temperature read. It reads the temperature only when the device is active.
>> +  A read from a suspended device returns ``-EPERM``.
>> +
>> +Sysfs entries
>> +-------------
>> +
>> +======================= =====================================================
>> +temp1_input		Temperature in millidegrees Celsius
>> +temp1_label		"xHCI"
> This is pointless and not the idea behind having a "label" attribute.
> The driver name itself already associates the sensor with xhci.
> Please drop.
>
>> +======================= =====================================================
>> +
>> +The hwmon device name is ``prom21_hwmon``. The sysfs path depends on the hwmon
>> +device number assigned by the kernel. Userspace can locate the device by
>> +matching the ``name`` attribute:
>> +
>> +.. code-block:: sh
>> +
>> +   for hwmon in /sys/class/hwmon/hwmon*; do
>> +           [ "$(cat "$hwmon/name")" = "prom21_hwmon" ] || continue
>> +           cat "$hwmon/temp1_label"
>> +           cat "$hwmon/temp1_input"
>> +   done
>> +
>> +``temp1_input`` reports millidegrees Celsius, so a value of ``50113`` means
>> +50.113 degrees Celsius. If the raw register value is invalid, ``temp1_input``
>> +returns ``-ENODATA``.
>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
>> index 14e4cea48acc..06d81cc29fec 100644
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -940,6 +940,17 @@ config SENSORS_POWERZ
>>   	  This driver can also be built as a module. If so, the module
>>   	  will be called powerz.
>>   
>> +config SENSORS_PROM21
>> +	tristate "AMD Promontory 21 xHCI temperature sensor"
>> +	depends on USB_XHCI_PCI
>> +	select USB_XHCI_PCI_AUXDEV
>> +	help
>> +	  If you say yes here you get support for the AMD Promontory 21
>> +	  (PROM21) xHCI temperature sensor.
>> +
>> +	  This driver can also be built as a module. If so, the module
>> +	  will be called prom21-hwmon.
>> +
>>   config SENSORS_POWR1220
>>   	tristate "Lattice POWR1220 Power Monitoring"
>>   	depends on I2C
>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
>> index 4788996aa137..7693ed3b3f72 100644
>> --- a/drivers/hwmon/Makefile
>> +++ b/drivers/hwmon/Makefile
>> @@ -196,6 +196,7 @@ obj-$(CONFIG_SENSORS_PC87427)	+= pc87427.o
>>   obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
>>   obj-$(CONFIG_SENSORS_POWERZ)	+= powerz.o
>>   obj-$(CONFIG_SENSORS_POWR1220)  += powr1220.o
>> +obj-$(CONFIG_SENSORS_PROM21)	+= prom21-hwmon.o
>>   obj-$(CONFIG_SENSORS_PT5161L)	+= pt5161l.o
>>   obj-$(CONFIG_SENSORS_PWM_FAN)	+= pwm-fan.o
>>   obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON)	+= qnap-mcu-hwmon.o
>> diff --git a/drivers/hwmon/prom21-hwmon.c b/drivers/hwmon/prom21-hwmon.c
>> new file mode 100644
>> index 000000000000..1c137304d65d
>> --- /dev/null
>> +++ b/drivers/hwmon/prom21-hwmon.c
>> @@ -0,0 +1,293 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * AMD PROM21 xHCI Hwmon Implementation
>> + * (only temperature monitoring is supported)
>> + *
>> + * This can be effectively used as the alternative chipset temperature monitor.
>> + *
>> + * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
>> + */
>> +
>> +#include <linux/auxiliary_bus.h>
>> +#include <linux/device.h>
>> +#include <linux/err.h>
>> +#include <linux/errno.h>
>> +#include <linux/hwmon.h>
>> +#include <linux/io.h>
>> +#include <linux/math.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/pci.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/slab.h>
>> +#include <linux/usb.h>
>> +#include <linux/usb/hcd.h>
>> +
>> +#define PROM21_INDEX 0x3000
>> +#define PROM21_DATA 0x3008
>> +#define PROM21_TEMP_REG 0x0001e520
>> +
>> +#define PROM21_HWMON_NAME "prom21_hwmon"
>> +#define PROM21_TEMP_LABEL "xHCI"
>> +
>> +struct prom21_hwmon {
>> +	struct pci_dev *pdev;
>> +	struct device *hwmon_dev;
>> +	void __iomem *regs;
>> +	bool removing;
>> +	struct mutex lock; /* protects removing and the index/data registers */
> It is difficult to believe that auxiliary device management is so unstable
> that it needs all that complexity. This will require confirmation from
> someone who knows how this is supposed to work, and a detailed explanation
> in the driver explaining why it is necessary.
>
>> +};
>> +
>> +static bool pm;
>> +module_param(pm, bool, 0444);
>> +MODULE_PARM_DESC(pm, "Allow runtime PM state changes for device memory access");
> No. Either enable it or don't, but please don't add such module parameters.
> The pm complexity in the driver, as written, makes it all but impossible
> to determine what is going on.
>
>> +
>> +static void prom21_hwmon_invalidate(struct prom21_hwmon *hwmon)
>> +{
>> +	mutex_lock(&hwmon->lock);
>> +	hwmon->removing = true;
>> +	mutex_unlock(&hwmon->lock);
>> +}
>> +
>> +static int prom21_hwmon_pm_get(struct prom21_hwmon *hwmon, bool *pm_ref)
>> +{
>> +	struct device *dev = &hwmon->pdev->dev;
>> +	int ret;
>> +
>> +	*pm_ref = false;
>> +
>> +	/*
>> +	 * PROM21 temperature register access does not return a valid value while
>> +	 * the parent xHCI PCI function is suspended. By default, only read when
>> +	 * runtime PM reports the device as active, or when runtime PM is disabled
>> +	 * and the device is not marked as suspended. If pm=Y, allow runtime PM
>> +	 * state changes while accessing the temperature register.
>> +	 */
>> +	if (pm) {
>> +		ret = pm_runtime_resume_and_get(dev);
>> +		if (ret < 0)
>> +			return ret;
>> +
>> +		*pm_ref = true;
>> +		return 0;
>> +	}
>> +
>> +	ret = pm_runtime_get_if_active(dev);
>> +	if (ret > 0) {
>> +		*pm_ref = true;
>> +		return 0;
>> +	}
>> +
>> +	if (ret == -EINVAL && !pm_runtime_status_suspended(dev))
>> +		return 0;
>> +
>> +	if (!ret || pm_runtime_status_suspended(dev))
>> +		return -EPERM;
>> +
>> +	return ret;
>> +}
>> +
>> +/*
>> + * This is not a pure MMIO read. The PROM21 vendor data register is selected
>> + * by temporarily writing PROM21_TEMP_REG to the vendor index register. Keep
>> + * the sequence short and restore the previous index before returning.
>> + */
>> +static int prom21_hwmon_read_temp_raw_restore_index(struct prom21_hwmon *hwmon,
>> +						    u8 *raw)
>> +{
>> +	struct device *dev = &hwmon->pdev->dev;
>> +	bool pm_ref;
>> +	u32 index;
>> +	u32 data;
>> +	int ret;
>> +
>> +	/*
>> +	 * The xHCI PCI remove path destroys the auxiliary device before HCD
>> +	 * teardown. Keep runtime PM and MMIO inside the critical section so a
>> +	 * sysfs read cannot use the vendor register pair after remove starts.
>> +	 */
>> +	mutex_lock(&hwmon->lock);
>> +	if (hwmon->removing) {
>> +		mutex_unlock(&hwmon->lock);
>> +		return -ENODEV;
>> +	}
>> +
>> +	ret = prom21_hwmon_pm_get(hwmon, &pm_ref);
>> +	if (ret) {
>> +		mutex_unlock(&hwmon->lock);
>> +		return ret;
>> +	}
>> +
>> +	index = readl(hwmon->regs + PROM21_INDEX);
>> +	/* Select the PROM21 temperature register through the vendor index. */
>> +	writel(PROM21_TEMP_REG, hwmon->regs + PROM21_INDEX);
>> +	data = readl(hwmon->regs + PROM21_DATA);
>> +	/* Restore the previous vendor index register value. */
>> +	writel(index, hwmon->regs + PROM21_INDEX);
>> +	readl(hwmon->regs + PROM21_INDEX);
>> +
>> +	if (pm_ref) {
>> +		/*
>> +		 * Use autosuspend so repeated sysfs reads do not suspend the
>> +		 * controller immediately after each successful register access.
>> +		 */
>> +		pm_runtime_mark_last_busy(dev);
>> +		pm_runtime_put_autosuspend(dev);
>> +	}
>> +	mutex_unlock(&hwmon->lock);
>> +
>> +	*raw = data & 0xff;
>> +	if (!*raw || *raw == 0xff)
>> +		return -ENODATA;
>> +
>> +	return 0;
>> +}
>> +
>> +static long prom21_hwmon_raw_to_millicelsius(u8 raw)
>> +{
>> +	/*
>> +	 * No public AMD reference is available for this value.
>> +	 * The scale was derived from observed PROM21 xHCI temperature readings:
>> +	 *  temp[C] = raw * 0.9066 - 78.624
>> +	 */
>> +	return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;
>> +}
>> +
>> +static umode_t prom21_hwmon_is_visible(const void *drvdata,
>> +				       enum hwmon_sensor_types type, u32 attr,
>> +				       int channel)
>> +{
>> +	if (type != hwmon_temp || channel)
>> +		return 0;
>> +
>> +	switch (attr) {
>> +	case hwmon_temp_input:
>> +	case hwmon_temp_label:
>> +		return 0444;
>> +	default:
>> +		return 0;
>> +	}
>> +}
>> +
>> +static int prom21_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
>> +			     u32 attr, int channel, long *val)
>> +{
>> +	struct prom21_hwmon *hwmon = dev_get_drvdata(dev);
>> +	u8 raw;
>> +	int ret;
>> +
>> +	if (type != hwmon_temp || attr != hwmon_temp_input || channel)
>> +		return -EOPNOTSUPP;
>> +
>> +	ret = prom21_hwmon_read_temp_raw_restore_index(hwmon, &raw);
>> +	if (ret)
>> +		return ret;
>> +
>> +	*val = prom21_hwmon_raw_to_millicelsius(raw);
>> +	return 0;
>> +}
>> +
>> +static int prom21_hwmon_read_string(struct device *dev,
>> +				    enum hwmon_sensor_types type, u32 attr,
>> +				    int channel, const char **str)
>> +{
>> +	if (type != hwmon_temp || attr != hwmon_temp_label || channel)
>> +		return -EOPNOTSUPP;
>> +
>> +	*str = PROM21_TEMP_LABEL;
>> +	return 0;
>> +}
>> +
>> +static const struct hwmon_ops prom21_hwmon_ops = {
>> +	.is_visible = prom21_hwmon_is_visible,
>> +	.read = prom21_hwmon_read,
>> +	.read_string = prom21_hwmon_read_string,
>> +};
>> +
>> +static const struct hwmon_channel_info *const prom21_hwmon_info[] = {
>> +	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
>> +	NULL,
>> +};
>> +
>> +static const struct hwmon_chip_info prom21_hwmon_chip_info = {
>> +	.ops = &prom21_hwmon_ops,
>> +	.info = prom21_hwmon_info,
>> +};
>> +
>> +static int prom21_hwmon_probe(struct auxiliary_device *auxdev,
>> +			      const struct auxiliary_device_id *id)
>> +{
>> +	struct device *dev = &auxdev->dev;
>> +	struct device *parent = dev->parent;
>> +	struct prom21_hwmon *hwmon;
>> +	struct pci_dev *pdev;
>> +	struct usb_hcd *hcd;
>> +	int ret;
>> +
>> +	if (!parent || !dev_is_pci(parent))
>> +		return -ENODEV;
>> +
>> +	pdev = to_pci_dev(parent);
>> +	hcd = pci_get_drvdata(pdev);
>> +	if (!hcd)
>> +		return dev_err_probe(dev, -ENODEV,
>> +				     "xHCI HCD data unavailable\n");
>> +
>> +	if (!hcd->regs || hcd->rsrc_len < PROM21_DATA + sizeof(u32))
>> +		return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n");
>> +
>> +	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
>> +	if (!hwmon)
>> +		return -ENOMEM;
>> +
>> +	ret = devm_mutex_init(dev, &hwmon->lock);
>> +	if (ret)
>> +		return ret;
>> +
>> +	hwmon->pdev = pdev;
>> +	hwmon->regs = hcd->regs;
>> +	auxiliary_set_drvdata(auxdev, hwmon);
>> +
>> +	/*
>> +	 * Use the PCI function as the hwmon parent so user space reports it as
>> +	 * a PCI adapter. Lifetime is still owned by this auxiliary driver;
>> +	 * remove() unregisters the hwmon device before xhci-pci tears down the
>> +	 * HCD.
>> +	 */
>> +	hwmon->hwmon_dev =
>> +		hwmon_device_register_with_info(&pdev->dev, PROM21_HWMON_NAME,
>> +						hwmon, &prom21_hwmon_chip_info,
>> +						NULL);
>> +	if (IS_ERR(hwmon->hwmon_dev))
>> +		return PTR_ERR(hwmon->hwmon_dev);
>> +
>> +	return 0;
>> +}
>> +
>> +static void prom21_hwmon_remove(struct auxiliary_device *auxdev)
>> +{
>> +	struct prom21_hwmon *hwmon = auxiliary_get_drvdata(auxdev);
>> +
>> +	if (hwmon) {
>> +		prom21_hwmon_invalidate(hwmon);
>> +		hwmon_device_unregister(hwmon->hwmon_dev);
>> +	}
>> +}
>> +
>> +static const struct auxiliary_device_id prom21_hwmon_id_table[] = {
>> +	{ .name = "xhci_pci." PROM21_HWMON_NAME },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(auxiliary, prom21_hwmon_id_table);
>> +
>> +static struct auxiliary_driver prom21_hwmon_driver = {
>> +	.name = "prom21-hwmon",
>> +	.probe = prom21_hwmon_probe,
>> +	.remove = prom21_hwmon_remove,
>> +	.id_table = prom21_hwmon_id_table,
>> +};
>> +module_auxiliary_driver(prom21_hwmon_driver);
>> +
>> +MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
>> +MODULE_DESCRIPTION("AMD PROM21 xHCI hwmon driver");
>> +MODULE_LICENSE("GPL");

^ permalink raw reply

* 回复:[PATCH v13 net-next 03/11] net/nebula-matrix: add chip related definitions
From: Illusion Wang @ 2026-05-08  4:52 UTC (permalink / raw)
  To: Paolo Abeni, Dimon, Alvin, Sam, netdev
  Cc: andrew+netdev, corbet, kuba, linux-doc, lorenzo, horms,
	vadim.fedorenko, lukas.bulwahn, edumazet, enelsonmoore, skhan,
	hkallweit1, open list
In-Reply-To: <e41adaad-8937-4b5d-bdbf-d57d3efe2855@redhat.com>

[...]
>> +void nbl_write_all_regs(struct nbl_hw_mgt *hw_mgt)
>> +{
>> + struct nbl_common_info *common = hw_mgt->>common;
>> + u8 eth_mode = common->>eth_mode;
>> + const u32 *nbl_sec046_data;
>> + const u32 *nbl_sec071_data;
>> + u32 i;
>> +
>> + switch (eth_mode) {
>> + case 1:
>> +  nbl_sec046_data = nbl_sec046_1p_data;
>> +  nbl_sec071_data = nbl_sec071_1p_data;
>> +  break;
>> + case 2:
>> +  nbl_sec046_data = nbl_sec046_2p_data;
>> +  nbl_sec071_data = nbl_sec071_2p_data;
>> +  break;
>> + case 4:
>> +  nbl_sec046_data = nbl_sec046_4p_data;
>> +  nbl_sec071_data = nbl_sec071_4p_data;
>> +  break;
>> + default:
>> +  nbl_sec046_data = nbl_sec046_2p_data;
>> +  nbl_sec071_data = nbl_sec071_2p_data;
>> + }
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC006_SIZE; i++) {
>> +  if ((i + 1) % NBL_SEC_BLOCK_SIZE == 0)
>> +   nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
>> +
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC006_REGI(i), nbl_sec006_data[i]);
>> + }
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC007_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC007_REGI(i), nbl_sec007_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC008_SIZE; i++) {
>> +  if ((i + 1) % NBL_SEC_BLOCK_SIZE == 0)
>> +   nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
>> +
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC008_REGI(i), nbl_sec008_data[i]);
>> + }
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC009_SIZE; i++) {
>> +  if ((i + 1) % NBL_SEC_BLOCK_SIZE == 0)
>> +   nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
>> +
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC009_REGI(i), nbl_sec009_data[i]);
>> + }
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC010_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC010_REGI(i), nbl_sec010_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC011_SIZE; i++) {
>> +  if ((i + 1) % NBL_SEC_BLOCK_SIZE == 0)
>> +   nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
>> +
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC011_REGI(i), nbl_sec011_data[i]);
>> + }
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC012_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC012_REGI(i), nbl_sec012_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC013_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC013_REGI(i), nbl_sec013_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC014_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC014_REGI(i), nbl_sec014_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC022_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC022_REGI(i), nbl_sec022_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC023_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC023_REGI(i), nbl_sec023_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC024_SIZE; i++) {
>> +  if ((i + 1) % NBL_SEC_BLOCK_SIZE == 0)
>> +   nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
>> +
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC024_REGI(i), nbl_sec024_data[i]);
>> + }
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC025_SIZE; i++) {
>> +  if ((i + 1) % NBL_SEC_BLOCK_SIZE == 0)
>> +   nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
>> +
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC025_REGI(i), nbl_sec025_data[i]);
>> + }
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC026_SIZE; i++)
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC026_REGI(i), nbl_sec026_data[i]);
>> +
>> + nbl_flush_writes(hw_mgt);
>> + for (i = 0; i < NBL_SEC027_SIZE; i++) {
>> +  if ((i + 1) % NBL_SEC_BLOCK_SIZE == 0)
>> +   nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG);
>> +
>> +  nbl_hw_wr32(hw_mgt, NBL_SEC027_REGI(i), nbl_sec027_data[i]);

>Sashiko says:

>Could this loop read past the end of the nbl_sec009_data array?
>The macro NBL_SEC009_SIZE is defined as 2048, but the nbl_sec009_data array
>contains significantly fewer elements (around 754). This appears to cause
>sequential out-of-bounds reads into the .rodata section, writing unrelated
>memory to the device registers.
>Similar size mismatches exist for nbl_sec025_data (262 elements vs size
>1024)
>and nbl_sec022_data (506 elements vs size 256).
>Would it be safer to use ARRAY_SIZE() to bound these iterations?

But I printed out the results: 
ARRAY_SIZE(nbl_sec009_data) equals NBL_SEC009_SIZE,
ARRAY_SIZE(nbl_sec025_data) equals NBL_SEC025_SIZE,
and ARRAY_SIZE(nbl_sec022_data) equals NBL_SEC022_SIZE.

Is the AI making a mistake here?

^ permalink raw reply

* Re: [PATCH v9 2/6] iio: adc: ad4691: add initial driver for AD4691 family
From: Andy Shevchenko @ 2026-05-08  4:44 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Sabau, Radu bogdan, Lars-Peter Clausen, Hennerich, Michael,
	David Lechner, Sa, Nuno, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
	linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org
In-Reply-To: <20260507151549.61e4e8fb@jic23-huawei>

On Thu, May 07, 2026 at 03:15:49PM +0100, Jonathan Cameron wrote:
> On Thu, 7 May 2026 09:26:00 +0000
> "Sabau, Radu bogdan" <Radu.Sabau@analog.com> wrote:

...

> > > +	st->info = spi_get_device_match_data(spi);  
> > 
> > "Is it possible for st->info to be NULL here?
> > If the driver is manually bound to a device with a non-matching name using
> > the sysfs bind interface in combination with driver_override,
> > spi_get_device_match_data() could return NULL, which would cause a NULL
> > pointer dereference later in the probe sequence."
> > 
> > Agreed, will add the NULL check with -ENODEV immediately after
> > spi_get_device_match_data().
> 
> Andy, you seeing this one?  Looks like we are putting these checks back in again.
> Whilst anyone forcing a bind like this is onto a looser anyway we shouldn't
> crash due to a null dereference.

We should find a way how to disable that combination from the start. The driver
makes no sense to be instantiated from user space. Actually most of the drivers
nowadays should not be bound to the devices without driver data.


-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* [PATCH] ASoC: docs: Fix stale and misspelled references
From: Cássio Gabriel @ 2026-05-08  4:20 UTC (permalink / raw)
  To: Takashi Iwai, Mark Brown, Jonathan Corbet, Shuah Khan,
	Liam Girdwood, Jaroslav Kysela
  Cc: linux-sound, linux-doc, linux-kernel, Cássio Gabriel

While reading some docs, I have found some minor
misspelled words and some parts that could be improved.

The ASoC USB documentation refers to the USB DPCM backend link as DCPM
in two places. The ASoC platform documentation still points readers to
the old DPCM.txt name. The ALSA configuration guide also has two
references that omit their current subdirectory.

Fix a few stale and misspelled references in the ALSA and ASoC
documentation and update the references and fix a typo in the ASoC index.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
 Documentation/sound/alsa-configuration.rst | 4 ++--
 Documentation/sound/soc/index.rst          | 2 +-
 Documentation/sound/soc/platform.rst       | 2 +-
 Documentation/sound/soc/usb.rst            | 4 ++--
 4 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst
index f75f08763941..394cf87f340b 100644
--- a/Documentation/sound/alsa-configuration.rst
+++ b/Documentation/sound/alsa-configuration.rst
@@ -1097,7 +1097,7 @@ output (with ``--no-upload`` option) to kernel bugzilla or alsa-devel
 ML (see the section `Links and Addresses`_).
 
 ``power_save`` and ``power_save_controller`` options are for power-saving
-mode.  See powersave.rst for details.
+mode.  See Documentation/sound/designs/powersave.rst for details.
 
 Note 2: If you get click noises on output, try the module option
 ``position_fix=1`` or ``2``.  ``position_fix=1`` will use the SD_LPIB
@@ -1168,7 +1168,7 @@ line_outs_monitor
 enable_monitor
     Enable Analog Out on Channel 63/64 by default.
 
-See hdspm.rst for details.
+See Documentation/sound/cards/hdspm.rst for details.
 
 Module snd-ice1712
 ------------------
diff --git a/Documentation/sound/soc/index.rst b/Documentation/sound/soc/index.rst
index 8bed8f8f48da..22878d73f7a5 100644
--- a/Documentation/sound/soc/index.rst
+++ b/Documentation/sound/soc/index.rst
@@ -2,7 +2,7 @@
 ALSA SoC Layer
 ==============
 
-The documentation is spilt into the following sections:-
+The documentation is split into the following sections:-
 
 .. toctree::
    :maxdepth: 2
diff --git a/Documentation/sound/soc/platform.rst b/Documentation/sound/soc/platform.rst
index bd21d0a4dd9b..53caba6ce4ce 100644
--- a/Documentation/sound/soc/platform.rst
+++ b/Documentation/sound/soc/platform.rst
@@ -75,4 +75,4 @@ Each SoC DSP driver usually supplies the following features :-
 3. DMA IO to/from DSP buffers (if applicable)
 4. Definition of DSP front end (FE) PCM devices.
 
-Please see DPCM.txt for a description of item 4.
+Please see dpcm.rst for a description of item 4.
diff --git a/Documentation/sound/soc/usb.rst b/Documentation/sound/soc/usb.rst
index 94c12f9d9dd1..9cb860547f22 100644
--- a/Documentation/sound/soc/usb.rst
+++ b/Documentation/sound/soc/usb.rst
@@ -103,7 +103,7 @@ Returns 0 on success, and -EOPNOTSUPP on failure.
   - ``usbdev``: the usb device that was discovered
   - ``sdev``: capabilities of the device
 
-**snd_soc_usb_connect()** notifies the ASoC USB DCPM BE DAI link of a USB
+**snd_soc_usb_connect()** notifies the ASoC USB DPCM BE DAI link of a USB
 audio device detection.  This can be utilized in the BE DAI
 driver to keep track of available USB audio devices.  This is intended
 to be called by the USB offload driver residing in USB SND.
@@ -118,7 +118,7 @@ Returns 0 on success, negative error code on failure.
   - ``usbdev``: the usb device that was removed
   - ``sdev``: capabilities to free
 
-**snd_soc_usb_disconnect()** notifies the ASoC USB DCPM BE DAI link of a USB
+**snd_soc_usb_disconnect()** notifies the ASoC USB DPCM BE DAI link of a USB
 audio device removal.  This is intended to be called by the USB offload
 driver that resides in USB SND.
 

---
base-commit: b8dc547edf9e41474d8ce2dcf344e8e75b17781a
change-id: 20260508-asoc-doc-fixes-9efb3a85aa3b

Best regards,
--  
Cássio Gabriel <cassiogabrielcontato@gmail.com>


^ permalink raw reply related

* Re: [PATCH v5] docs/zh_CN: add module-signing Chinese translation
From: Alex Shi @ 2026-05-08  4:02 UTC (permalink / raw)
  To: Yan Zhu, alexs, si.yanteng, corbet
  Cc: dzm91, skhan, linux-doc, linux-kernel, lkp
In-Reply-To: <tencent_654B91A551D6E7DDF311CF580D42E6FB8F06@qq.com>

Applied, thanks!

On 2026/4/22 23:11, Yan Zhu wrote:
> Translate .../admin-guide/module-signing.rst into Chinese.
> 
> Update the translation through commit 0ad9a71933e7
> ("modsign: Enable ML-DSA module signing")
> 
> Reported-by: kernel test robot<lkp@intel.com>
> Closes:https://lore.kernel.org/oe-kbuild-all/202604182216.Qpd5KifK-lkp@intel.com/
> Signed-off-by: Yan Zhu<zhuyan2015@qq.com>
> ---
> v4->v5:
> Fix the format error of line 157.
> 
> v3->v4:
> Add patch change description.
> 
> v2->v3:
> Fix line 87 and 94 without tab indentation.
> 
> v1->v2:
> Fixed the issue of some lines exceeding 80 characters and alignment.
> ---
> ---
>   .../zh_CN/admin-guide/module-signing.rst      | 250 ++++++++++++++++++
>   1 file changed, 250 insertions(+)
>   create mode 100644 Documentation/translations/zh_CN/admin-guide/module-signing.rst
> 
> diff --git a/Documentation/translations/zh_CN/admin-guide/module-signing.rst b/Documentation/translations/zh_CN/admin-guide/module-signing.rst
> new file mode 100644
> index 000000000000..b5671224f102
> --- /dev/null
> +++ b/Documentation/translations/zh_CN/admin-guide/module-signing.rst
> @@ -0,0 +1,250 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +.. include:: ../disclaimer-zh_CN.rst
> +
> +:Original: Documentation/admin-guide/module-signing.rst
> +:翻译:
> + 朱岩 Yan Zhu<zhuyan2015@qq.com>
> +
> +
> +================
> +内核模块签名机制
> +================
> +
> +.. 目录
> +..
> +.. - 概述
> +.. - 配置模块签名
> +.. - 生成签名密钥
> +.. - 内核中的公钥
> +.. - 模块手动签名
> +.. - 已签名模块和剥离
> +.. - 加载已签名模块
> +.. - 无效签名和未签名模块
> +.. - 管理/保护私钥
> +
> +
> +概述
> +====
> +
> +内核模块签名机制在安装过程中对模块进行加密签名,然后在加载模块时检查签名。这
> +通过禁止加载未签名的模块或使用无效密钥签名的模块来提高内核安全性。模块签名通
> +过使恶意模块更难加载到内核中来增加安全性。模块签名检查在内核中完成,因此不需
> +要受信任的用户空间位。
> +
> +此机制使用 X.509 ITU-T 标准证书对涉及的公钥进行编码。签名本身不以任何工业标准
> +类型编码。内置机制目前仅支持 RSA、NIST P-384 ECDSA 和 NIST FIPS-204 ML-DSA
> +公钥签名标准(尽管它是可插拔的并允许使用其他标准)。对于 RSA 和 ECDSA,可以使
> +用的可能的哈希算法是大小为 256、384 和 512 的 SHA-2 和 SHA-3(算法由签名中的
> +数据选择);ML-DSA 会自行进行哈希运算,但允许与 SHA512 哈希算法结合用于签名属
> +性。
> +
> +配置模块签名
> +============
> +
> +通过进入内核配置的 :menuselection:`Enable Loadable Module Support` 菜单并打
> +开以下选项来启用模块签名机制::
> +
> +	CONFIG_MODULE_SIG	"Module signature verification"
> +
> +这有多个可用选项:
> +
> + (1) :menuselection:`Require modules to be validly signed`
> +     (``CONFIG_MODULE_SIG_FORCE``)
> +
> +     这指定了内核应如何处理其密钥未知或未签名的模块。
> +
> +     如果关闭(即"宽松模式"),则允许使用不可用密钥和未签名的模块,但内核将被
> +     标记为受污染,并且相关模块将被标记为受污染,显示字符'E'。
> +
> +     如果打开(即"限制模式"),只有具有有效签名且可由内核拥有的公钥验证的模块
> +     才会被加载。所有其他模块将生成错误。
> +
> +     无论此处的设置如何,如果模块的签名块无法解析,它将被直接拒绝。
> +
> +
> + (2) :menuselection:`Automatically sign all modules`
> +     (``CONFIG_MODULE_SIG_ALL``)
> +
> +     如果打开此选项,则在构建的 modules_install 阶段期间将自动签名模块。
> +     如果关闭,则必须使用以下命令手动签名模块::
> +
> +	scripts/sign-file
> +
> +
> + (3) :menuselection:`Which hash algorithm should modules be signed with?`
> +
> +     这提供了安装阶段将用于签名模块的哈希算法选择:
> +
> +	=============================== ==========================================
> +	``CONFIG_MODULE_SIG_SHA256``	:menuselection:`Sign modules with SHA-256`
> +	``CONFIG_MODULE_SIG_SHA384``	:menuselection:`Sign modules with SHA-384`
> +	``CONFIG_MODULE_SIG_SHA512``	:menuselection:`Sign modules with SHA-512`
> +	``CONFIG_MODULE_SIG_SHA3_256``	:menuselection:`Sign modules with SHA3-256`
> +	``CONFIG_MODULE_SIG_SHA3_384``	:menuselection:`Sign modules with SHA3-384`
> +	``CONFIG_MODULE_SIG_SHA3_512``	:menuselection:`Sign modules with SHA3-512`
> +	=============================== ==========================================
> +
> +     此处选择的算法也将被构建到内核中(而不是作为模块),以便使用该算法签名的
> +     模块可以在不导致循环依赖的情况下检查其签名。
> +
> +
> + (4) :menuselection:`File name or PKCS#11 URI of module signing key`
> +     (``CONFIG_MODULE_SIG_KEY``)
> +
> +     将此选项设置为除默认值 ``certs/signing_key.pem`` 之外的其他值将禁用签名
> +     密钥的自动生成,并允许使用您选择的密钥对内核模块进行签名。提供的字符串应
> +     标识包含私钥及其对应的 PEM 格式 X.509 证书的文件,或者在 OpenSSL
> +     ENGINE_pkcs11 功能正常的系统上,使用 RFC7512 定义的 PKCS#11 URI。在后一
> +     种情况下,PKCS#11 URI 应引用证书和私钥。
> +
> +     如果包含私钥的 PEM 文件已加密,或者 PKCS#11 令牌需要 PIN,可以通过
> +     ``KBUILD_SIGN_PIN`` 变量在构建时提供。
> +
> +
> + (5) :menuselection:`Additional X.509 keys for default system keyring`
> +     (``CONFIG_SYSTEM_TRUSTED_KEYS``)
> +
> +     此选项可设置为包含附加证书的 PEM 编码文件的文件名,这些证书将默认包含在
> +     系统密钥环中。
> +
> +请注意,启用模块签名会为内核构建过程添加对执行签名工具的 OpenSSL 开发包的依赖。
> +
> +
> +生成签名密钥
> +============
> +
> +生成和检查签名需要加密密钥对。私钥用于生成签名,相应的公钥用于检查签名。私钥
> +仅在构建期间需要,之后可以删除或安全存储。公钥被构建到内核中,以便在加载模块
> +时可以使用它来检查签名。
> +
> +在正常情况下,当 ``CONFIG_MODULE_SIG_KEY`` 保持默认值时,如果文件中不存在密
> +钥对,内核构建将使用 openssl 自动生成新的密钥对::
> +
> +	certs/signing_key.pem
> +
> +在构建 vmlinux 期间(公钥需要构建到 vmlinux 中)使用参数::
> +
> +	certs/x509.genkey
> +
> +文件(如果尚不存在也会生成)。
> +
> +可以在 RSA(``MODULE_SIG_KEY_TYPE_RSA``)、
> +ECDSA(``MODULE_SIG_KEY_TYPE_ECDSA``)和
> +ML-DSA(``MODULE_SIG_KEY_TYPE_MLDSA_*``)之间选择生成 RSA 4k、NIST P-384
> +密钥对或 ML-DSA 44、65 或 87 密钥对。
> +
> +强烈建议您提供自己的 x509.genkey 文件。
> +
> +最值得注意的是,在 x509.genkey 文件中,req_distinguished_name 部分应从默认值
> +更改::
> +
> +	[ req_distinguished_name ]
> +	#O = Unspecified company
> +	CN = Build time autogenerated kernel key
> +	#emailAddress =unspecified.user@unspecified.company
> +
> +生成的 RSA 密钥大小也可以通过以下方式设置::
> +
> +	[ req ]
> +	default_bits = 4096
> +
> +也可以使用位于 Linux 内核源代码树根节点中的 x509.genkey 密钥生成配置文件和
> +openssl 命令手动生成公钥/私钥文件。以下是生成公钥/私钥文件的示例::
> +
> +	openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \
> +	   -config x509.genkey -outform PEM -out kernel_key.pem \
> +	   -keyout kernel_key.pem
> +
> +然后可以将生成的 kernel_key.pem 文件的完整路径名指定在
> +``CONFIG_MODULE_SIG_KEY`` 选项中,并且将使用其中的证书和密钥而不是自动生成的
> +密钥对。
> +
> +
> +内核中的公钥
> +============
> +
> +内核包含一个可由 root 查看的公钥环。它们在名为 ".builtin_trusted_keys" 的密
> +钥环中,可以通过以下方式查看::
> +
> +	[root@deneb ~]# cat /proc/keys
> +	...
> +	223c7853 I------     1 perm 1f030000     0     0 keyring   .builtin_trusted_keys: 1
> +	302d2d52 I------     1 perm 1f010000     0     0 asymmetri Fedora kernel signing key: d69a84e6bce3d216b979e9505b3e3ef9a7118079: X509.RSA a7118079 []
> +
> +除了专门为模块签名生成的公钥外,还可以在 ``CONFIG_SYSTEM_TRUSTED_KEYS`` 配置
> +选项引用的 PEM 编码文件中提供其他受信任的证书。
> +
> +此外,架构代码可以从硬件存储中获取公钥并将其添加(例如从 UEFI 密钥数据库)。
> +
> +最后,可以通过以下方式添加其他公钥::
> +
> +	keyctl padd asymmetric "" [.builtin_trusted_keys-ID] <[key-file]
> +
> +例如::
> +
> +	keyctl padd asymmetric "" 0x223c7853 <my_public_key.x509
> +
> +但是,请注意,内核只允许将由已驻留在 ``.builtin_trusted_keys`` 中的密钥有效
> +签名的密钥添加到 ``.builtin_trusted_keys``。
> +
> +模块手动签名
> +============
> +
> +要手动对模块进行签名,请使用 Linux 内核源代码树中可用的 scripts/sign-file 工
> +具。该脚本需要 4 个参数:
> +
> +	1.  哈希算法(例如,sha256)
> +	2.  私钥文件名或 PKCS#11 URI
> +	3.  公钥文件名
> +	4.  要签名的内核模块
> +
> +以下是签名内核模块的示例::
> +
> +	scripts/sign-file sha512 kernel-signkey.priv \
> +		kernel-signkey.x509 module.ko
> +
> +使用的哈希算法不必与配置的算法匹配,但如果不同,应确保哈希算法要么内置在内核
> +中,要么可以在不需要自身的情况下加载。
> +
> +如果私钥需要密码或 PIN,可以在 $KBUILD_SIGN_PIN 环境变量中提供。
> +
> +
> +已签名模块和剥离
> +================
> +
> +已签名模块在末尾简单地附加了数字签名。模块文件末尾的字符串
> +``~Module signature appended~.`` 确认签名存在,但不能确认签名有效!
> +
> +已签名模块是脆弱的,因为签名在定义的ELF容器之外。因此,一旦计算并附加签名,就
> +不得剥离它们。请注意,整个模块都是签名的有效载荷,包括签名时存在的任何和所有
> +调试信息。
> +
> +
> +加载已签名模块
> +==============
> +
> +模块通过 insmod、modprobe、 ``init_module()`` 或 ``finit_module()`` 加载,
> +与未签名模块完全一样,因为在用户空间中不进行任何处理。
> +所有签名检查都在内核内完成。
> +
> +
> +无效签名和未签名模块
> +====================
> +
> +如果启用了 ``CONFIG_MODULE_SIG_FORCE`` 或在内核启动命令提供了
> +module.sig_enforce=1,内核将仅加载具有有效签名且具有公钥的模块。否则,它还将
> +加载未签名的模块。任何具有不匹配签名的模块将不被允许加载。
> +
> +任何具有不可解析签名的模块将被拒绝。
> +
> +
> +管理/保护私钥
> +==============
> +
> +由于私钥用于签名模块,病毒和恶意软件可以使用私钥签名模块并危害操作系统。私钥
> +必须被销毁或移动到安全位置,而不是保存在内核源代码树的根节点中。
> +
> +如果使用相同的私钥为多个内核配置签名模块,必须确保模块版本信息足以防止将模块
> +加载到不同的内核中。要么设置 ``CONFIG_MODVERSIONS=y``,要么通过更改
> +``EXTRAVERSION`` 或 ``CONFIG_LOCALVERSION`` 确保每个配置具有不同的内核发布字
> +符串。
> -- 2.43.0
> 


^ permalink raw reply

* Re: [PATCH] docs/zh_CN: restructure how-to.rst patch submission workflow
From: Alex Shi @ 2026-05-08  3:45 UTC (permalink / raw)
  To: Dongliang Mu, Alex Shi, Yanteng Si, Jonathan Corbet, Shuah Khan
  Cc: linux-doc, linux-kernel
In-Reply-To: <20260420115647.2718959-1-dzm91@hust.edu.cn>

Applied, Thanks!

On 2026/4/20 19:56, Dongliang Mu wrote:
> Split "导出补丁和制作封面" into separate "导出补丁" and
> "为补丁集制作封面" sections, and document the single-patch
> (git format-patch -1) and multi-patch (-N) flows side by side
> so new contributors do not have to infer one from the other.
> 
> Replace the invalid "git am --amend" invocations with "git commit
> --amend" in both the checkpatch fix-up and the iteration sections.
> 
> Expand the iteration section with a worked v2 example showing where the
> changelog goes relative to the --- separator, and describe how v3/v4
> changelogs stack newest-on-top.
> 
> Finally, add a note reminding submitters to carry Reviewed-by tags from
> reviewers into the next revision, placed below Signed-off-by.
> 
> Assisted-by:Claude:claude-opus-4-7
> Signed-off-by: Dongliang Mu<dzm91@hust.edu.cn>
> ---
>   Documentation/translations/zh_CN/how-to.rst | 105 ++++++++++++++------
>   1 file changed, 77 insertions(+), 28 deletions(-)
> 
> diff --git a/Documentation/translations/zh_CN/how-to.rst b/Documentation/translations/zh_CN/how-to.rst
> index 3dcf6754d1df..9ec2384e1e76 100644
> --- a/Documentation/translations/zh_CN/how-to.rst
> +++ b/Documentation/translations/zh_CN/how-to.rst
> @@ -269,13 +269,22 @@ Git 和邮箱配置
>   **请注意** 以上四行,缺少任何一行,您都将会在第一轮审阅后返工,如果您需要一个
>   更加明确的示例,请对 zh_CN 目录执行 git log。
>   
> -导出补丁和制作封面
> -------------------
> +导出补丁
> +--------
> +
> +这个时候,可以导出补丁,做发送邮件列表最后的准备了。对于单个补丁,
> +命令行执行::
> +
> +	git format-patch -1
> +
> +然后命令行会输出类似下面的内容::
> +
> +	0001-docs-zh_CN-add-xxxxxxxx.patch
>   
> -这个时候,可以导出补丁,做发送邮件列表最后的准备了。命令行执行::
> +如果您有多个补丁,命令行执行::
>   
>   	git format-patch -N
> -	# N 要替换为补丁数量,一般 N 大于等于 1
> +	# N 要替换为补丁数量,一般 N 大于 1
>   
>   然后命令行会输出类似下面的内容::
>   
> @@ -290,13 +299,12 @@ Git 和邮箱配置
>   
>   	./scripts/checkpatch.pl *.patch
>   
> -参考脚本输出,解决掉所有的 error 和 warning,通常情况下,只有下面这个
> +参考脚本输出,解决掉所有的 error 和 warning。通常情况下,只有下面这个
>   warning 不需要解决::
>   
>   	WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
>   
> -一个简单的解决方法是一次只检查一个补丁,然后打上该补丁,直接对译文进行修改,
> -然后执行以下命令为补丁追加更改::
> +对于单个补丁,解决方案很简单,只需要打上该补丁,直接对译文进行修改,为补丁追加后续更改::
>   
>   	git checkout docs-next
>   	git checkout -b test-trans-new
> @@ -304,15 +312,21 @@ warning 不需要解决::
>   	./scripts/checkpatch.pl 0001-xxxxx.patch
>   	# 直接修改您的翻译
>   	git add .
> -	git am --amend
> +	git commit --amend
>   	# 保存退出
> -	git am 0002-xxxxx.patch
> -	……
>   
> -重新导出再次检测,重复这个过程,直到处理完所有的补丁。
> +随后,重新导出补丁再次检测,重复这个过程,直到处理完所有 warning 和
> +error。
> +
> +如果您有多个补丁,请按补丁集中补丁顺序对每个补丁重复上述流程,一次只处理
> +一个,不要一次 git am 多个补丁。全部处理完毕后再重新导出并再次测试。
>   
> -最后,如果检测时没有需要处理的 warning 和 error,或者您只有一个补丁,请
> -跳过下面这个步骤,否则请重新导出补丁制作封面::
> +为补丁集制作封面
> +----------------
> +
> +对于单个补丁,请跳过本节。
> +
> +如果您有多个补丁,则需要为补丁集制作一份封面,即 0 号补丁::
>   
>   	git format-patch -N --cover-letter --thread=shallow
>   	# N 要替换为补丁数量,一般 N 大于 1
> @@ -329,18 +343,14 @@ warning 不需要解决::
>   	vim 0000-cover-letter.patch
>   
>   	...
> -	Subject: [PATCH 0/N] *** SUBJECT HERE *** #修改该字段,概括您的补丁集都做了哪些事情
> +	Subject: [PATCH 0/N] *** SUBJECT HERE *** # 修改该字段,概括您的补丁集都做了哪些事情
>   
> -	*** BLURB HERE ***			  #修改该字段,详细描述您的补丁集做了哪些事情
> +	*** BLURB HERE ***			  # 修改该字段,详细描述您的补丁集做了哪些事情
>   
>   	Yanteng Si (1):
>   	  docs/zh_CN: add xxxxx
>   	...
>   
> -如果您只有一个补丁,则无需制作封面(即 0 号补丁),只需执行::
> -
> -	git format-patch -1
> -
>   把补丁提交到邮件列表
>   ====================
>   
> @@ -392,28 +402,67 @@ reviewer 的评论,做到每条都有回复,每个回复都落实到位。
>   迭代补丁
>   --------
>   
> -建议您每回复一条评论,就修改一处翻译。然后重新生成补丁,相信您现在已经具
> -备了灵活使用 git am --amend 的能力。
> +建议您每回复一条评论,就修改一处翻译,然后重新生成补丁,相信您现在
> +已经具备了灵活使用 git am 与 git commit --amend 的能力。
>   
> -每次迭代一个补丁,不要一次多个::
> +对于单个补丁,每回复完评论后修改、追加::
>   
> -	git am <您要修改的补丁>
> +	git am 0001-xxxxx.patch
>   	# 直接对文件进行您的修改
>   	git add .
>   	git commit --amend
>   
> -当您将所有的评论落实到位后,导出第二版补丁,并修改封面::
> +当您将所有的评论落实到位后,导出第二版补丁::
>   
> -	git format-patch -N -v 2 --cover-letter --thread=shallow
> +	git format-patch -1 -v 2
> +
> +命令行会输出 v2-0001-xxxxx.patch。打开该文件,在 --- 分割线下方追加
> +changelog。注意,分割线以下的内容不会进入 git 提交历史,仅作为邮件中的
> +说明供 reviewer 检查::
> +
> +	Subject: [PATCH v2] docs/zh_CN: add xxxxxx translation
> +
> +	Translate .../xxx.rst into Chinese.
> +
> +	Signed-off-by: Yanteng Si<si.yanteng@linux.dev>
> +	---
> +	v1->v2:
> +	  - 修正第二节的错别字,Reviewer-A 提出的意见
> +	  - 根据 Reviewer-B 的建议调整段落顺序
>   
> -打开 0 号补丁,在 BLURB HERE 处编写相较于上个版本,您做了哪些改动。
> +	 Documentation/translations/zh_CN/xxx.rst | 100 ++++++
> +	 1 file changed, 100 insertions(+)
>   
> -然后执行::
> +后续迭代 v3、v4 …… 时,新的 changelog 放在最上面,旧的保留在下方,按
> +从新到旧的顺序叠加。例如 v3 补丁的 --- 下方::
>   
> -	git send-email v2* --to <maintainer email addr> --cc <others addr>
> +	---
> +	v2->v3:
> +	  - ...本次相较 v2 的改动...
> +	v1->v2:
> +	  - ...上一次相较 v1 的改动...
> +
> +然后发送::
> +
> +	git send-email v2-0001-*.patch --to <maintainer email addr> --cc <others addr>
> +
> +如果您有多个补丁,迭代时请按以下原则:每次只迭代一个补丁,不要一次多个,
> +每个补丁独立重复上述流程。所有评论落实到位后,导出 v2 时附带封面::
> +
> +	git format-patch -N -v 2 --cover-letter --thread=shallow
> +
> +打开 0 号补丁,在 BLURB HERE 处写明整组补丁相较 v1 的总体改动,格式
> +同上面的单个补丁 changelog 示例。如果某个补丁需要单独说明,可在该
> +补丁文件的 --- 分割线下方追加单个补丁的 changelog。最后执行::
> +
> +	git send-email v2-*.patch --to <maintainer email addr> --cc <others addr>
>   
>   这样,新的一版补丁就又发送到邮件列表等待审阅,之后就是重复这个过程。
>   
> +此外,如果审阅者或维护者在邮件回复中给出了 Reviewed-by tag,请在下
> +一版补丁的 commit 信息中加入该 tag,放在 Signed-off-by 行的下方,以
> +便维护者合入时保留您的审阅记录。
> +
>   审阅周期
>   --------
>   
> -- 2.43.0
> 


^ permalink raw reply

* Re: [PATCH v2] cpufreq: CPPC: add autonomous mode boot parameter support
From: Jie Zhan @ 2026-05-08  2:33 UTC (permalink / raw)
  To: Sumit Gupta, rafael, viresh.kumar, pierre.gondois,
	ionela.voinescu, zhenglifeng1, corbet, skhan, rdunlap,
	mario.limonciello, linux-pm, linux-doc, linux-kernel
  Cc: linux-tegra, treding, jonathanh, vsethi, ksitaraman, sanjayc,
	mochs, bbasu
In-Reply-To: <bc06c3ab-0286-4e1e-a615-e704b267c21a@nvidia.com>



On 5/5/2026 8:29 PM, Sumit Gupta wrote:
> Hi Jie,
> 
> 
> On 27/04/26 13:54, Jie Zhan wrote:
>> External email: Use caution opening links or attachments
>>
>>
>> Hi Sumit,
>>
>> In general, I would expect this parameter only toggles on auto_sel by
>> default.  IIUC, other CPPC configurations (min/max/desired perf, EPP,
>> enable) are optional and not closely related to this.
>>
>> Why including those stuff here?
>>
>>
>> Please see other questions inline.
>>
>> Thanks!
>> Jie
> 
> These together provide a known, predictable autonomous mode boot.
> min/max/desired seeding ensures HW has a known starting bound (BIOS
> may leave them unset).
> EPP=PERFORMANCE_PREF is needed as BIOS defaults often bias toward
> energy saving, and admins on many CPU systems shouldn't have to script
> per CPU sysfs writes at every boot to undo that.
Hi Sumit,

The min/max/desired perf might be a slightly different case, but the EPP
value should probably follow the default if there is?
Otherwise, users may complain that their BIOS defaults don't work and find
out it's driver's fault.

Alternatively, we may also make an assumption that we ignore BIOS EPP
config, but not sure if that's suitable?

(Perhaps let's just discuss in the bottom trunk because the main issue is
pretty much the same.)
>>
>> On 4/25/2026 4:18 AM, Sumit Gupta wrote:
>>> Add a kernel boot parameter 'cppc_cpufreq.auto_sel_mode' to enable
>>> CPPC autonomous performance selection on all CPUs at system startup.
>>> When autonomous mode is enabled, the hardware automatically adjusts
>>> CPU performance based on workload demands using Energy Performance
>>> Preference (EPP) hints.
>>>
>>> When auto_sel_mode=1:
>>> - Configure all CPUs for autonomous operation on first init
>>> - Set EPP to performance preference (0x0)
>>> - Use HW min/max_perf when available; otherwise initialize from caps
>>> - Clamp desired_perf to bounds before enabling autonomous mode
>>> - Hardware controls frequency instead of the OS governor
>>>
>>> The boot parameter is applied only during first policy initialization.
>>> Skip applying it on CPU hotplug to preserve runtime sysfs configuration.
>>>
>>> This patch depends on patch [2] ("cpufreq: Set policy->min and max
>>> as real QoS constraints") so that the policy->min/max set in
>>> cppc_cpufreq_cpu_init() are not overridden by cpufreq_set_policy()
>>> during init.
>>>
>>> Reviewed-by: Randy Dunlap <rdunlap@infradead.org> (Documentation)
>>> Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
>>> ---
>>> v[1] -> v2:
>>> - Call cppc_set_enable() unconditionally so CPPC is enabled for both
>>>    OS-driven and autonomous modes.
>> Why adding this in v2?
>> This looks like a separate issue since setting CPPC Enable reg doesn't seem
>> to be related with autonomous control.
> 
> In v2, moved it out of the auto_sel specific check.
> Agree that cppc_set_enable() is general CPPC enablement and can be split
> into a separate patch in v3 if preferred.
> 
Yeah it should be a separate patch I think.
>>> - Init min/max from caps instead of cppc_cpufreq_update_perf_limits()
>>>    as policy->min/max aren't yet populated.
>>>
>>> [1] https://lore.kernel.org/lkml/20260317151053.2361475-1-sumitg@nvidia.com/
>>> [2] https://lore.kernel.org/lkml/20260423084731.1090384-2-pierre.gondois@arm.com/
>>> ---
>>>   .../admin-guide/kernel-parameters.txt         | 13 +++
>>>   drivers/cpufreq/cppc_cpufreq.c                | 89 +++++++++++++++++--
>>>   2 files changed, 97 insertions(+), 5 deletions(-)
>>>
...
>>> +
>>> +             policy->cur = cppc_perf_to_khz(caps,
>>> +                                            cpu_data->perf_ctrls.desired_perf);
>>> +
>>> +             /* EPP is optional - some platforms may not support it */
>>> +             ret = cppc_set_epp(cpu, CPPC_EPP_PERFORMANCE_PREF);
>> Why setting this to PERFORMANCE by default?
>> A platform can have its own default EPP value.  This would override that.
> 
> The boot option targets performance oriented use cases on many CPU
> systems, avoiding sysfs scripting across all CPUs on every boot.
> The BIOS default EPP (often biased toward energy saving) would otherwise
> steer HW away from that goal. Admins can still re-tune EPP at runtime via
> the existing energy_performance_preference_val sysfs.
> 
Yeah, avoiding scripting makes sense for sure.
The thing is how do we do this nicely.

For now we need to consider follow either the BIOS default or the driver
default.
They seem to be exclusive since there isn't a clear way to support both at
the same time.

So, are we going to support the BIOS default case?
For example, users can config a default EPP in the BIOS setup menu.
>>> +             if (ret && ret != -EOPNOTSUPP)
>>> +                     pr_warn("Failed to set EPP for CPU%d (%d)\n", cpu, ret);
>>> +             else if (!ret)
>>> +                     cpu_data->perf_ctrls.energy_perf = CPPC_EPP_PERFORMANCE_PREF;
>>> +
...

^ permalink raw reply

* [PATCH net-next v3 8/8] selftests: drv-net: add netkit devmem tests
From: Bobby Eshleman @ 2026-05-08  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
  Cc: dw, sdf.kernel, mohsin.bashr, willemb, jiang.kun2, xu.xin16,
	wang.yaxin, netdev, linux-doc, linux-kernel, linux-rdma, bpf,
	linux-kselftest, Stanislav Fomichev, Mina Almasry, netdev,
	linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
	Bobby Eshleman
In-Reply-To: <20260507-tcp-dm-netkit-v3-0-52821445867c@meta.com>

From: Bobby Eshleman <bobbyeshleman@meta.com>

Add nk_devmem.py with four tests for TCP devmem through a netkit device:

These tests are just duplicates of the original devmem tests, with some
adjusted parameters such as telling ncdevmem to avoid device setup
(since it only has access to netkit, not a phys device).

Each test uses NetDrvContEnv with primary_rx_redirect=True to set up the
BPF redirect program on the primary netkit interface.

The NIC (HDS, RSS, queue lease) is configured once in main() before
ksft_run() and torn down in a finally block via cleanup_nic(), mirroring
the nk_qlease.py pattern. This avoids re-toggling NIC settings around
every test case.

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v4:
- Call configure_nic()/cleanup_nic() once around ksft_run() rather than
  relying on per-test configuration inside the run_* helpers.

Changes in v3:
- Reorder os.path expressions
- Drop @ksft_disruptive from check_nk_rx_hds to mirror the original
  check_rx_hds in devmem.py

Changes in v2:
- Add nk_devmem.py to TEST_PROGS in Makefile (Sashiko)
---
 tools/testing/selftests/drivers/net/hw/Makefile    |  1 +
 .../testing/selftests/drivers/net/hw/nk_devmem.py  | 55 ++++++++++++++++++++++
 2 files changed, 56 insertions(+)

diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 85ca4d1ecf9e..2f78c6aec397 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -34,6 +34,7 @@ TEST_PROGS = \
 	irq.py \
 	loopback.sh \
 	nic_timestamp.py \
+	nk_devmem.py \
 	nk_netns.py \
 	nk_qlease.py \
 	ntuple.py \
diff --git a/tools/testing/selftests/drivers/net/hw/nk_devmem.py b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
new file mode 100755
index 000000000000..0e36a0fa9688
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Test devmem TCP with netkit."""
+
+import os
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
+from lib.py import NetDrvContEnv
+from lib.py.devmem import (setup_test, require_devmem, configure_nic,
+                           cleanup_nic, run_rx, run_tx, run_tx_chunks,
+                           run_rx_hds)
+
+
+@ksft_disruptive
+def check_nk_rx(cfg) -> None:
+    """Run the devmem RX test through netkit."""
+    run_rx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx(cfg) -> None:
+    """Run the devmem TX test through netkit."""
+    run_tx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx_chunks(cfg) -> None:
+    """Run the devmem TX chunking test through netkit."""
+    run_tx_chunks(cfg)
+
+
+def check_nk_rx_hds(cfg) -> None:
+    """Run the HDS test through netkit."""
+    run_rx_hds(cfg)
+
+
+def main() -> None:
+    """Configure the NIC once, then run the netkit devmem test cases."""
+    with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg:
+        setup_test(cfg,
+                   os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                "ncdevmem"))
+
+        require_devmem(cfg)
+        configure_nic(cfg)
+        try:
+            ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks,
+                      check_nk_rx_hds], args=(cfg,))
+        finally:
+            cleanup_nic(cfg)
+
+    ksft_exit()
+
+
+if __name__ == "__main__":
+    main()

-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH net-next v3 7/8] selftests: drv-net: add primary_rx_redirect support to NetDrvContEnv
From: Bobby Eshleman @ 2026-05-08  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
  Cc: dw, sdf.kernel, mohsin.bashr, willemb, jiang.kun2, xu.xin16,
	wang.yaxin, netdev, linux-doc, linux-kernel, linux-rdma, bpf,
	linux-kselftest, Stanislav Fomichev, Mina Almasry, netdev,
	linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
	Bobby Eshleman
In-Reply-To: <20260507-tcp-dm-netkit-v3-0-52821445867c@meta.com>

From: Bobby Eshleman <bobbyeshleman@meta.com>

When sending from a namespace that has access to a netkit device with a
leased queue, the nk primary in the host namespace needs to redirect its
RX to the physical device. This patch adds that redirection bpf program
and teaches the harness to install it.

Add primary_rx_redirect=False parameter to NetDrvContEnv.__init__().
When enabled, _attach_primary_rx_redirect_bpf() attaches a new BPF TC
program (nk_primary_rx_redirect.bpf.c) to the primary (host-side) netkit
interface. The program redirects non-ICMPv6 IPv6 packets to the physical
NIC via bpf_redirect_neigh(), with the physical ifindex configured via
the .bss map. ICMPv6 is left on the host's netkit primary so IPv6
neighbor discovery still work locally.

Extract _find_bss_map_id() from _attach_bpf() into a reusable helper so
other BPF attachment methods can use it.

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- nk_primary_rx_redirect.bpf.c: add header includes to avoid hardcoding
  values
- update commit message explaining why ICMP is passed through
- env.py: re-use _tc_ensure_clsact() (had to add ifname paramater)
- env.py: gate the remote IPv6 host route install on primary_rx_redirect
  by moving it from _setup_ns() into _attach_primary_rx_redirect_bpf()
---
 .../drivers/net/hw/nk_primary_rx_redirect.bpf.c    | 39 +++++++++
 tools/testing/selftests/drivers/net/lib/py/env.py  | 93 +++++++++++++++++-----
 2 files changed, 114 insertions(+), 18 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
new file mode 100644
index 000000000000..46ff494b23de
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ipv6.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define ctx_ptr(field)		((void *)(long)(field))
+
+volatile __u32 phys_ifindex;
+
+SEC("tc/ingress")
+int nk_primary_rx_redirect(struct __sk_buff *skb)
+{
+	void *data_end = ctx_ptr(skb->data_end);
+	void *data = ctx_ptr(skb->data);
+	struct ethhdr *eth;
+	struct ipv6hdr *ip6h;
+
+	eth = data;
+	if ((void *)(eth + 1) > data_end)
+		return TC_ACT_OK;
+
+	if (eth->h_proto != bpf_htons(ETH_P_IPV6))
+		return TC_ACT_OK;
+
+	ip6h = data + sizeof(struct ethhdr);
+	if ((void *)(ip6h + 1) > data_end)
+		return TC_ACT_OK;
+
+	if (ip6h->nexthdr == IPPROTO_ICMPV6)
+		return TC_ACT_OK;
+
+	return bpf_redirect_neigh(phys_ifindex, NULL, 0, 0);
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 409b41922245..af8e1de8ed7b 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -336,15 +336,18 @@ class NetDrvContEnv(NetDrvEpEnv):
               +---------------+
     """
 
-    def __init__(self, src_path, rxqueues=1, **kwargs):
+    def __init__(self, src_path, rxqueues=1, primary_rx_redirect=False, **kwargs):
         self.netns = None
         self._nk_host_ifname = None
         self.nk_guest_ifname = None
         self._tc_clsact_added = False
         self._tc_attached = False
+        self._primary_rx_redirect_attached = False
+        self._primary_rx_redirect_clsact_added = False
         self._bpf_prog_pref = None
         self._bpf_prog_id = None
         self._init_ns_attached = False
+        self._remote_route_added = False
         self._old_fwd = None
         self._old_accept_ra = None
 
@@ -396,8 +399,18 @@ class NetDrvContEnv(NetDrvEpEnv):
 
         self._setup_ns()
         self._attach_bpf()
+        if primary_rx_redirect:
+            self._attach_primary_rx_redirect_bpf()
 
     def __del__(self):
+        if self._primary_rx_redirect_attached:
+            cmd(f"tc filter del dev {self._nk_host_ifname} ingress", fail=False)
+            self._primary_rx_redirect_attached = False
+
+        if self._primary_rx_redirect_clsact_added:
+            cmd(f"tc qdisc del dev {self._nk_host_ifname} clsact", fail=False)
+            self._primary_rx_redirect_clsact_added = False
+
         if self._tc_attached:
             cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
             self._tc_attached = False
@@ -406,6 +419,11 @@ class NetDrvContEnv(NetDrvEpEnv):
             cmd(f"tc qdisc del dev {self.ifname} clsact")
             self._tc_clsact_added = False
 
+        if self._remote_route_added:
+            cmd(f"ip -6 route del {self.nk_guest_ipv6}/128",
+                host=self.remote, fail=False)
+            self._remote_route_added = False
+
         if self._nk_host_ifname:
             cmd(f"ip link del dev {self._nk_host_ifname}")
             self._nk_host_ifname = None
@@ -459,13 +477,19 @@ class NetDrvContEnv(NetDrvEpEnv):
         ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self.nk_guest_ifname} nodad", ns=self.netns)
         ip(f"-6 route add default via fe80::1 dev {self.nk_guest_ifname}", ns=self.netns)
 
-    def _tc_ensure_clsact(self):
-        qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)
+    def _tc_ensure_clsact(self, ifname=None):
+        """Ensure a clsact qdisc exists on @ifname.
+
+        Returns True if this call added the qdisc, otherwise returns False.
+        """
+        if ifname is None:
+            ifname = self.ifname
+        qdisc = json.loads(cmd(f"tc -j qdisc show dev {ifname}").stdout)
         for q in qdisc:
             if q['kind'] == 'clsact':
-                return
-        cmd(f"tc qdisc add dev {self.ifname} clsact")
-        self._tc_clsact_added = True
+                return False
+        cmd(f"tc qdisc add dev {ifname} clsact")
+        return True
 
     def _get_bpf_prog_ids(self):
         filters = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout)
@@ -476,28 +500,28 @@ class NetDrvContEnv(NetDrvEpEnv):
                 return (bpf['pref'], bpf['options']['prog']['id'])
         raise Exception("Failed to get BPF prog ID")
 
+    def _find_bss_map_id(self, prog_id):
+        """Find the .bss map ID for a loaded BPF program."""
+        prog_info = bpftool(f"prog show id {prog_id}", json=True)
+        for map_id in prog_info.get("map_ids", []):
+            map_info = bpftool(f"map show id {map_id}", json=True)
+            if map_info.get("name", "").endswith("bss"):
+                return map_id
+        raise Exception(f"Failed to find .bss map for prog {prog_id}")
+
     def _attach_bpf(self):
         bpf_obj = self.test_dir / "nk_forward.bpf.o"
         if not bpf_obj.exists():
             raise KsftSkipEx("BPF prog not found")
 
-        self._tc_ensure_clsact()
+        if self._tc_ensure_clsact():
+            self._tc_clsact_added = True
         cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj}"
             " sec tc/ingress direct-action")
         self._tc_attached = True
 
         (self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids()
-        prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True)
-        map_ids = prog_info.get("map_ids", [])
-
-        bss_map_id = None
-        for map_id in map_ids:
-            map_info = bpftool(f"map show id {map_id}", json=True)
-            if map_info.get("name").endswith("bss"):
-                bss_map_id = map_id
-
-        if bss_map_id is None:
-            raise Exception("Failed to find .bss map")
+        bss_map_id = self._find_bss_map_id(self._bpf_prog_id)
 
         ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix)
         ipv6_bytes = ipv6_addr.packed
@@ -505,3 +529,36 @@ class NetDrvContEnv(NetDrvEpEnv):
         value = ipv6_bytes + ifindex_bytes
         value_hex = ' '.join(f'{b:02x}' for b in value)
         bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
+
+    def _attach_primary_rx_redirect_bpf(self):
+        """Attach BPF redirect program on the primary netkit ingress."""
+        bpf_obj = self.test_dir / "nk_primary_rx_redirect.bpf.o"
+        if not bpf_obj.exists():
+            raise KsftSkipEx("Primary RX redirect BPF prog not found")
+
+        if self._tc_ensure_clsact(self._nk_host_ifname):
+            self._primary_rx_redirect_clsact_added = True
+        cmd(f"tc filter add dev {self._nk_host_ifname} ingress"
+            f" bpf obj {bpf_obj} sec tc/ingress direct-action")
+        self._primary_rx_redirect_attached = True
+
+        ip(f"-6 route add {self.nk_guest_ipv6}/128 via {self.addr_v['6']}",
+           host=self.remote)
+        self._remote_route_added = True
+
+        filters = json.loads(
+            cmd(f"tc -j filter show dev {self._nk_host_ifname} ingress").stdout)
+        redirect_prog_id = None
+        for bpf in filters:
+            if 'options' not in bpf:
+                continue
+            if bpf['options']['bpf_name'].startswith('nk_primary_rx_redirect'):
+                redirect_prog_id = bpf['options']['prog']['id']
+                break
+        if redirect_prog_id is None:
+            raise Exception("Failed to get primary RX redirect BPF prog ID")
+
+        bss_map_id = self._find_bss_map_id(redirect_prog_id)
+        phys_ifindex_bytes = self.ifindex.to_bytes(4, byteorder='little')
+        value_hex = ' '.join(f'{b:02x}' for b in phys_ifindex_bytes)
+        bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")

-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH net-next v3 6/8] selftests: drv-net: refactor devmem command builders into lib module
From: Bobby Eshleman @ 2026-05-08  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
  Cc: dw, sdf.kernel, mohsin.bashr, willemb, jiang.kun2, xu.xin16,
	wang.yaxin, netdev, linux-doc, linux-kernel, linux-rdma, bpf,
	linux-kselftest, Stanislav Fomichev, Mina Almasry, netdev,
	linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
	Bobby Eshleman
In-Reply-To: <20260507-tcp-dm-netkit-v3-0-52821445867c@meta.com>

From: Bobby Eshleman <bobbyeshleman@meta.com>

Adding netkit-based devmem tests is a straight-forward copy of devmem
test commands plus some args for the nk cases, so this patch breaks out
these command builders into helpers used by both.

Though we tried to avoid libraries to avoid increasing the barrier of
entry/complexity (see selftests/drivers/net/README.md, section "Avoid
libraries and frameworks"), factoring out these functions seemed like
the lesser of two evils in this case of using the same commands, just
with slightly different args per environment.

I experimented with just having all of the tests in the same file to
avoid having helpers in a library file, but because ksft_run() is
limited to a single call per file, and the new tests will require
different environments (NetDrvContEnv/NetDrvEpEnv), it would have been
necessary to have each test set up its own environment instead of
sharing one for the entire ksft_run() run. This came at the cost of
ballooning the test time (from under 5s to 30s on my test system), so to
strike a balance these tests were placed in separate files so they could
keep a shared environment across a single ksft_run() run shared across
all tests using the same env type (introduced in subsequent patches).

The helpers work transparently with both plain and netkit environments
by inspecting cfg for netkit-specific attributes (netns, nk_queue,
etc...).

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v4:
- Make socat_send() always bind the source; drop its bind= parameter
  and the matching bind=not_ns at the run_rx call site.
- Drop socat_send()'s nodelay= arg; have buf_size>0 imply TCP_NODELAY
  since they are only meaningful together.
- configure_nic(): stash originals on cfg instead of using defer(); add
  paired cleanup_nic() helper. Drop the per-test configure_nic() calls
  from run_rx/run_tx/run_tx_chunks/run_rx_hds; the netkit test file
  invokes configure_nic/cleanup_nic once around ksft_run().
- make cfg.devmem_supported and cfg.devmem_probed public attrs (no '_')
  for sake of linting
- general cleanup of the code, linting fixes

Changes in v3:
- In setup_test, drop the unused cfg.listen_ns = getattr(cfg, 'netns',
  None) assignment.
- In run_rx, pass flow_steer=not_ns to ncdevmem_rx and bind=not_ns to
  socat_send to avoid changing functionality (we want just a straight
  refactor here)

Changes in v2:
- Move require_devmem() into individual test functions so KsftSkipEx goes up to
  ksft_run() (Sashiko)
- in ncdevmem_rx(), move -v 7 to take effect for both netns and
  non-netns when verify=True
---
 tools/testing/selftests/drivers/net/hw/devmem.py   |  77 ++------
 .../selftests/drivers/net/hw/lib/py/devmem.py      | 218 +++++++++++++++++++++
 2 files changed, 231 insertions(+), 64 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py
index ee863e90d1e0..dbc1e6a27b6a 100755
--- a/tools/testing/selftests/drivers/net/hw/devmem.py
+++ b/tools/testing/selftests/drivers/net/hw/devmem.py
@@ -2,91 +2,40 @@
 # SPDX-License-Identifier: GPL-2.0
 
 from os import path
-from lib.py import ksft_run, ksft_exit
-from lib.py import ksft_eq, KsftSkipEx
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
 from lib.py import NetDrvEpEnv
-from lib.py import bkg, cmd, rand_port, wait_port_listen
-from lib.py import ksft_disruptive
-
-
-def require_devmem(cfg):
-    if not hasattr(cfg, "_devmem_probed"):
-        probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
-        cfg._devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
-        cfg._devmem_probed = True
-
-    if not cfg._devmem_supported:
-        raise KsftSkipEx("Test requires devmem support")
+from lib.py.devmem import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds
 
 
 @ksft_disruptive
 def check_rx(cfg) -> None:
-    require_devmem(cfg)
-
-    port = rand_port()
-    socat = f"socat -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},bind={cfg.remote_baddr}:{port}"
-    listen_cmd = f"{cfg.bin_local} -l -f {cfg.ifname} -s {cfg.addr} -p {port} -c {cfg.remote_addr} -v 7"
-
-    with bkg(listen_cmd, exit_wait=True) as ncdevmem:
-        wait_port_listen(port)
-        cmd(f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | \
-            head -c 1K | {socat}", host=cfg.remote, shell=True)
-
-    ksft_eq(ncdevmem.ret, 0)
+    """Run the devmem RX test."""
+    run_rx(cfg)
 
 
 @ksft_disruptive
 def check_tx(cfg) -> None:
-    require_devmem(cfg)
-
-    port = rand_port()
-    listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
-    with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
-        wait_port_listen(port, host=cfg.remote)
-        cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port}", shell=True)
-
-    ksft_eq(socat.stdout.strip(), "hello\nworld")
+    """Run the devmem TX test."""
+    run_tx(cfg)
 
 
 @ksft_disruptive
 def check_tx_chunks(cfg) -> None:
-    require_devmem(cfg)
-
-    port = rand_port()
-    listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
-    with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
-        wait_port_listen(port, host=cfg.remote)
-        cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port} -z 3", shell=True)
-
-    ksft_eq(socat.stdout.strip(), "hello\nworld")
+    """Run the devmem TX chunking test."""
+    run_tx_chunks(cfg)
 
 
 def check_rx_hds(cfg) -> None:
-    """Test HDS splitting across payload sizes."""
-    require_devmem(cfg)
-
-    for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
-        port = rand_port()
-        listen_cmd = f"{cfg.bin_local} -L -l -f {cfg.ifname} -s {cfg.addr} -p {port}"
-
-        with bkg(listen_cmd, exit_wait=True) as ncdevmem:
-            wait_port_listen(port)
-            cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | " +
-                f"socat -b {size} -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},nodelay",
-                host=cfg.remote, shell=True)
-
-        ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
+    """Run the HDS test."""
+    run_rx_hds(cfg)
 
 
 def main() -> None:
+    """Run the devmem test cases."""
     with NetDrvEpEnv(__file__) as cfg:
-        cfg.bin_local = path.abspath(path.dirname(__file__) + "/ncdevmem")
-        cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
-
+        setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem"))
         ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds],
-                 args=(cfg, ))
+                 args=(cfg,))
     ksft_exit()
 
 
diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py b/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py
new file mode 100644
index 000000000000..d3e7a3645cba
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py
@@ -0,0 +1,218 @@
+# SPDX-License-Identifier: GPL-2.0
+"""Shared helpers for devmem TCP selftests."""
+
+import re
+
+from net.lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen,
+                        ksft_eq, KsftSkipEx, NetNSEnter, EthtoolFamily,
+                        NetdevFamily)
+
+
+def require_devmem(cfg):
+    """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported."""
+    if not hasattr(cfg, "devmem_probed"):
+        probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
+        cfg.devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
+        cfg.devmem_probed = True
+
+    if not cfg.devmem_supported:
+        raise KsftSkipEx("Test requires devmem support")
+
+
+def configure_nic(cfg):
+    """Channels, rings, RSS, queue lease for netkit devmem."""
+    cfg.require_ipver('6')
+    ethnl = EthtoolFamily()
+
+    channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+    channels = channels['combined-count']
+    if channels < 2:
+        raise KsftSkipEx(
+            'Test requires NETIF with at least 2 combined channels'
+        )
+
+    rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
+    cfg.orig_rx_rings = rings['rx']
+    cfg.orig_hds_thresh = rings.get('hds-thresh', 0)
+    cfg.orig_data_split = rings.get('tcp-data-split', 'unknown')
+
+    ethnl.rings_set({'header': {'dev-index': cfg.ifindex},
+                     'tcp-data-split': 'enabled',
+                     'hds-thresh': 0,
+                     'rx': min(64, cfg.orig_rx_rings)})
+
+    cfg.src_queue = channels - 1
+    ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+
+    with NetNSEnter(str(cfg.netns)):
+        netdevnl = NetdevFamily()
+        lease_result = netdevnl.queue_create({
+            "ifindex": cfg.nk_guest_ifindex,
+            "type": "rx",
+            "lease": {
+                "ifindex": cfg.ifindex,
+                "queue": {"id": cfg.src_queue, "type": "rx"},
+                "netns-id": 0,
+            },
+        })
+        cfg.nk_queue = lease_result['id']
+
+
+def cleanup_nic(cfg):
+    """Undo configure_nic() by restoring RSS and ring settings."""
+    ethtool(f"-X {cfg.ifname} default")
+    EthtoolFamily().rings_set({'header': {'dev-index': cfg.ifindex},
+                               'tcp-data-split': cfg.orig_data_split,
+                               'hds-thresh': cfg.orig_hds_thresh,
+                               'rx': cfg.orig_rx_rings})
+
+
+def set_flow_rule(cfg, port):
+    """Install a flow rule steering to src_queue and return the flow rule ID."""
+    output = ethtool(
+        f"-N {cfg.ifname} flow-type tcp6 dst-port {port}"
+        f" action {cfg.src_queue}"
+    ).stdout
+    return int(re.search(r'ID (\d+)', output).group(1))
+
+
+def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False):
+    """Build the ncdevmem RX listener command."""
+    if hasattr(cfg, 'netns'):
+        flow_rule_id = set_flow_rule(cfg, port)
+        defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+
+        ifname = cfg.nk_guest_ifname
+        addr = cfg.nk_guest_ipv6
+        extras = [f"-t {cfg.nk_queue}", "-q 1", "-n"]
+    else:
+        ifname = cfg.ifname
+        addr = cfg.addr
+        extras = []
+        if flow_steer:
+            extras.append(f"-c {cfg.remote_addr}")
+
+    if verify:
+        extras.append("-v 7")
+    if fail_on_linear:
+        extras.append("-L")
+
+    parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}",
+             f"-p {port}", *extras]
+    return " ".join(parts)
+
+
+def ncdevmem_tx(cfg, port, chunk_size=0):
+    """Build the ncdevmem TX send command."""
+    if hasattr(cfg, 'netns'):
+        ifname = cfg.nk_guest_ifname
+        addr = cfg.remote_addr_v['6']
+        extras = ["-t 0", "-q 1", "-n"]
+    else:
+        ifname = cfg.ifname
+        addr = cfg.remote_addr
+        extras = []
+
+    if chunk_size:
+        extras.append(f"-z {chunk_size}")
+
+    parts = [cfg.bin_local, f"-f {ifname}", f"-s {addr}",
+             f"-p {port}", *extras]
+    return " ".join(parts)
+
+
+def socat_send(cfg, port, buf_size=0):
+    """Socat command for sending to the devmem listener.
+
+    When buf_size > 0, force one TCP segment per write of exactly that size by
+    setting socat's buffer (-b) and disabling Nagle (TCP_NODELAY).
+    """
+    proto = f"TCP{cfg.addr_ipver}"
+
+    if hasattr(cfg, 'netns'):
+        addr = f"[{cfg.nk_guest_ipv6}]"
+    else:
+        addr = cfg.baddr
+
+    suffix = f",bind={cfg.remote_baddr}:{port}"
+
+    buf = ""
+    if buf_size:
+        buf = f"-b {buf_size}"
+        suffix += ",nodelay"
+
+    return f"socat {buf} -u - {proto}:{addr}:{port}{suffix}"
+
+
+def socat_listen(cfg, port):
+    """Socat listen command for TX tests."""
+    return f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
+
+
+def setup_test(cfg, bin_local):
+    """Stash the local ncdevmem path on cfg and deploy it to the remote."""
+    cfg.bin_local = bin_local
+    cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+
+
+def run_rx(cfg):
+    """Run the devmem RX test."""
+    require_devmem(cfg)
+    port = rand_port()
+    socat = socat_send(cfg, port)
+    data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | head -c 1K"
+                 f" | {socat}")
+    netns = getattr(cfg, "netns", None)
+
+    listen_cmd = ncdevmem_rx(cfg, port, flow_steer=not hasattr(cfg, 'netns'))
+    with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
+        wait_port_listen(port, proto="tcp", ns=netns)
+        cmd(data_pipe, host=cfg.remote, shell=True)
+    ksft_eq(ncdevmem.ret, 0)
+
+
+def run_tx(cfg):
+    """Run the devmem TX test."""
+    require_devmem(cfg)
+    netns = getattr(cfg, "netns", None)
+    port = rand_port()
+    tx_cmd = ncdevmem_tx(cfg, port)
+    listen_cmd = socat_listen(cfg, port)
+
+    with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+        wait_port_listen(port, host=cfg.remote)
+        cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
+    ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_tx_chunks(cfg):
+    """Run the devmem TX chunking test."""
+    require_devmem(cfg)
+    netns = getattr(cfg, "netns", None)
+    port = rand_port()
+    tx_cmd = ncdevmem_tx(cfg, port, chunk_size=3)
+    listen_cmd = socat_listen(cfg, port)
+
+    with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+        wait_port_listen(port, host=cfg.remote)
+        cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
+    ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_rx_hds(cfg):
+    """Run the HDS test by running devmem RX across a segment size sweep."""
+    require_devmem(cfg)
+    netns = getattr(cfg, "netns", None)
+
+    for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
+        port = rand_port()
+
+        listen_cmd = ncdevmem_rx(cfg, port, verify=False,
+                                 fail_on_linear=True)
+        socat = socat_send(cfg, port, buf_size=size)
+
+        with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
+            wait_port_listen(port, proto="tcp", ns=netns)
+            cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | "
+                f"{socat}", host=cfg.remote, shell=True)
+        ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")

-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH net-next v3 5/8] selftests: drv-net: make attr _nk_guest_ifname public
From: Bobby Eshleman @ 2026-05-08  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
  Cc: dw, sdf.kernel, mohsin.bashr, willemb, jiang.kun2, xu.xin16,
	wang.yaxin, netdev, linux-doc, linux-kernel, linux-rdma, bpf,
	linux-kselftest, Stanislav Fomichev, Mina Almasry, netdev,
	linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
	Bobby Eshleman
In-Reply-To: <20260507-tcp-dm-netkit-v3-0-52821445867c@meta.com>

From: Bobby Eshleman <bobbyeshleman@meta.com>

Subsequent patches will use the _nk_guest_ifname as a public attr for
setting up devmem. Rename to nk_guest_ifname to avoid angering the
linter about the '_' prefix being used for a non-private attr.

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
 tools/testing/selftests/drivers/net/hw/nk_qlease.py |  8 ++++----
 tools/testing/selftests/drivers/net/lib/py/env.py   | 16 ++++++++--------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/hw/nk_qlease.py b/tools/testing/selftests/drivers/net/hw/nk_qlease.py
index aa83dc321328..139a91ebd229 100755
--- a/tools/testing/selftests/drivers/net/hw/nk_qlease.py
+++ b/tools/testing/selftests/drivers/net/hw/nk_qlease.py
@@ -71,7 +71,7 @@ def test_iou_zcrx(cfg) -> None:
     flow_rule_id = set_flow_rule(cfg)
     defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
 
-    rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
+    rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg.nk_guest_ifname} -q {cfg.nk_queue}"
     tx_cmd = f"{cfg.bin_remote} -c -h {cfg.nk_guest_ipv6} -p {cfg.port} -l 12840"
     with bkg(rx_cmd, exit_wait=True):
         wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
@@ -128,7 +128,7 @@ def test_attach_xdp_with_mp(cfg) -> None:
 
     netdevnl = NetdevFamily()
 
-    rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
+    rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg.nk_guest_ifname} -q {cfg.nk_queue}"
     with bkg(rx_cmd):
         wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
 
@@ -178,7 +178,7 @@ def test_destroy(cfg) -> None:
     ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
     defer(ethtool, f"-X {cfg.ifname} default")
 
-    rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
+    rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg.nk_guest_ifname} -q {cfg.nk_queue}"
     rx_proc = cmd(rx_cmd, background=True)
     wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
 
@@ -196,7 +196,7 @@ def test_destroy(cfg) -> None:
     ip(f"link del dev {cfg._nk_host_ifname}")
     kill_timer.join()
     cfg._nk_host_ifname = None
-    cfg._nk_guest_ifname = None
+    cfg.nk_guest_ifname = None
 
     queue_info = netdevnl.queue_get(
         {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 24ce122abd9c..409b41922245 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -339,7 +339,7 @@ class NetDrvContEnv(NetDrvEpEnv):
     def __init__(self, src_path, rxqueues=1, **kwargs):
         self.netns = None
         self._nk_host_ifname = None
-        self._nk_guest_ifname = None
+        self.nk_guest_ifname = None
         self._tc_clsact_added = False
         self._tc_attached = False
         self._bpf_prog_pref = None
@@ -390,7 +390,7 @@ class NetDrvContEnv(NetDrvEpEnv):
 
         netkit_links.sort(key=lambda x: x['ifindex'])
         self._nk_host_ifname = netkit_links[1]['ifname']
-        self._nk_guest_ifname = netkit_links[0]['ifname']
+        self.nk_guest_ifname = netkit_links[0]['ifname']
         self.nk_host_ifindex = netkit_links[1]['ifindex']
         self.nk_guest_ifindex = netkit_links[0]['ifindex']
 
@@ -409,7 +409,7 @@ class NetDrvContEnv(NetDrvEpEnv):
         if self._nk_host_ifname:
             cmd(f"ip link del dev {self._nk_host_ifname}")
             self._nk_host_ifname = None
-            self._nk_guest_ifname = None
+            self.nk_guest_ifname = None
 
         if self._init_ns_attached:
             cmd("ip netns del init", fail=False)
@@ -448,16 +448,16 @@ class NetDrvContEnv(NetDrvEpEnv):
         cmd("ip netns attach init 1")
         self._init_ns_attached = True
         ip("netns set init 0", ns=self.netns)
-        ip(f"link set dev {self._nk_guest_ifname} netns {self.netns.name}")
+        ip(f"link set dev {self.nk_guest_ifname} netns {self.netns.name}")
         ip(f"link set dev {self._nk_host_ifname} up")
         ip(f"-6 addr add fe80::1/64 dev {self._nk_host_ifname} nodad")
         ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self._nk_host_ifname}")
 
         ip("link set lo up", ns=self.netns)
-        ip(f"link set dev {self._nk_guest_ifname} up", ns=self.netns)
-        ip(f"-6 addr add fe80::2/64 dev {self._nk_guest_ifname}", ns=self.netns)
-        ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns)
-        ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns)
+        ip(f"link set dev {self.nk_guest_ifname} up", ns=self.netns)
+        ip(f"-6 addr add fe80::2/64 dev {self.nk_guest_ifname}", ns=self.netns)
+        ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self.nk_guest_ifname} nodad", ns=self.netns)
+        ip(f"-6 route add default via fe80::1 dev {self.nk_guest_ifname}", ns=self.netns)
 
     def _tc_ensure_clsact(self):
         qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)

-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH net-next v3 4/8] selftests: drv-net: ncdevmem: add -n flag to skip NIC configuration
From: Bobby Eshleman @ 2026-05-08  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
  Cc: dw, sdf.kernel, mohsin.bashr, willemb, jiang.kun2, xu.xin16,
	wang.yaxin, netdev, linux-doc, linux-kernel, linux-rdma, bpf,
	linux-kselftest, Stanislav Fomichev, Mina Almasry, netdev,
	linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
	Bobby Eshleman
In-Reply-To: <20260507-tcp-dm-netkit-v3-0-52821445867c@meta.com>

From: Bobby Eshleman <bobbyeshleman@meta.com>

Add a -n (skip_config) flag that causes ncdevmem to skip NIC
configuration when operating as an RX server. When -n is passed,
ncdevmem skips configuring header split, RSS, and flow steering, as well
as their teardown on exit.

This allows ksft tests to pre-configure the NIC in the host namespace
before launching ncdevmem in the guest namespace. This is needed for
netkit devmem tests where the test harness namespace has direct access
to the NIC and the ncdevmem namespace does not.

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
 tools/testing/selftests/drivers/net/hw/ncdevmem.c | 58 +++++++++++++----------
 1 file changed, 34 insertions(+), 24 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
index e098d6534c3c..d96e8a3b5a65 100644
--- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
+++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
@@ -93,6 +93,7 @@ static char *port;
 static size_t do_validation;
 static int start_queue = -1;
 static int num_queues = -1;
+static int skip_config;
 static char *ifname;
 static unsigned int ifindex;
 static unsigned int dmabuf_id;
@@ -828,7 +829,7 @@ static struct netdev_queue_id *create_queues(void)
 
 static int do_server(struct memory_buffer *mem)
 {
-	struct ethtool_rings_get_rsp *ring_config;
+	struct ethtool_rings_get_rsp *ring_config = NULL;
 	char ctrl_data[sizeof(int) * 20000];
 	size_t non_page_aligned_frags = 0;
 	struct sockaddr_in6 client_addr;
@@ -851,27 +852,29 @@ static int do_server(struct memory_buffer *mem)
 		return -1;
 	}
 
-	ring_config = get_ring_config();
-	if (!ring_config) {
-		pr_err("Failed to get current ring configuration");
-		return -1;
-	}
+	if (!skip_config) {
+		ring_config = get_ring_config();
+		if (!ring_config) {
+			pr_err("Failed to get current ring configuration");
+			return -1;
+		}
 
-	if (configure_headersplit(ring_config, 1)) {
-		pr_err("Failed to enable TCP header split");
-		goto err_free_ring_config;
-	}
+		if (configure_headersplit(ring_config, 1)) {
+			pr_err("Failed to enable TCP header split");
+			goto err_free_ring_config;
+		}
 
-	/* Configure RSS to divert all traffic from our devmem queues */
-	if (configure_rss()) {
-		pr_err("Failed to configure rss");
-		goto err_reset_headersplit;
-	}
+		/* Configure RSS to divert all traffic from our devmem queues */
+		if (configure_rss()) {
+			pr_err("Failed to configure rss");
+			goto err_reset_headersplit;
+		}
 
-	/* Flow steer our devmem flows to start_queue */
-	if (configure_flow_steering(&server_sin)) {
-		pr_err("Failed to configure flow steering");
-		goto err_reset_rss;
+		/* Flow steer our devmem flows to start_queue */
+		if (configure_flow_steering(&server_sin)) {
+			pr_err("Failed to configure flow steering");
+			goto err_reset_rss;
+		}
 	}
 
 	if (bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys)) {
@@ -1052,13 +1055,17 @@ static int do_server(struct memory_buffer *mem)
 err_unbind:
 	ynl_sock_destroy(ys);
 err_reset_flow_steering:
-	reset_flow_steering();
+	if (!skip_config)
+		reset_flow_steering();
 err_reset_rss:
-	reset_rss();
+	if (!skip_config)
+		reset_rss();
 err_reset_headersplit:
-	restore_ring_config(ring_config);
+	if (!skip_config)
+		restore_ring_config(ring_config);
 err_free_ring_config:
-	ethtool_rings_get_rsp_free(ring_config);
+	if (!skip_config)
+		ethtool_rings_get_rsp_free(ring_config);
 	return err;
 }
 
@@ -1404,7 +1411,7 @@ int main(int argc, char *argv[])
 	int is_server = 0, opt;
 	int ret, err = 1;
 
-	while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:")) != -1) {
+	while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:n")) != -1) {
 		switch (opt) {
 		case 'L':
 			fail_on_linear = true;
@@ -1436,6 +1443,9 @@ int main(int argc, char *argv[])
 		case 'z':
 			max_chunk = atoi(optarg);
 			break;
+		case 'n':
+			skip_config = 1;
+			break;
 		case '?':
 			fprintf(stderr, "unknown option: %c\n", optopt);
 			break;

-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH net-next v3 3/8] net: devmem: support TX over NETMEM_TX_NO_DMA devices
From: Bobby Eshleman @ 2026-05-08  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
  Cc: dw, sdf.kernel, mohsin.bashr, willemb, jiang.kun2, xu.xin16,
	wang.yaxin, netdev, linux-doc, linux-kernel, linux-rdma, bpf,
	linux-kselftest, Stanislav Fomichev, Mina Almasry, netdev,
	linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
	Bobby Eshleman
In-Reply-To: <20260507-tcp-dm-netkit-v3-0-52821445867c@meta.com>

From: Bobby Eshleman <bobbyeshleman@meta.com>

When a netkit virtual device leases queues from a physical NIC, devmem
TX bindings created on the netkit device must still result in the dmabuf
being mapped for dma by the physical device. This patch accomplishes
this by teaching the bind handler to search for the underlying
DMA-capable device by looking it up via leased rx queues. The function
netdev_find_netmem_tx_dev(), used for finding the underlying DMA-capable
device, can be extended to support other non-netkit NETMEM_TX_NO_DMA
devices in the future if needed.

Additionally, this patch extends validate_xmit_unreadable_skb() to
support the netkit case, where the skb is validated twice: once on the
netkit guest device and again on the physical NIC after BPF redirect or
ip forwarding.

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- Fix validate_xmit_unreadable_skb() bug for non-devmem
  unreadable niovs (should not be dropped)
- Major simplification of validate_xmit_unreadable_skb()
- Fix prematurely released lock in bind-tx handler (Jakub)

Changes in v2:
- In validate_xmit_unreadable_skb() to check netmem_tx mode before
  inspecting frags (Jakub)
- Lock bind_dev around netdev_queue_get_dma_dev() when bind_dev !=
  netdev to fix lockdep (Sashiko)
---
 net/core/dev.c         |  3 +++
 net/core/devmem.c      |  6 +++--
 net/core/devmem.h      |  9 ++++++--
 net/core/netdev-genl.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 72 insertions(+), 9 deletions(-)

diff --git a/net/core/dev.c b/net/core/dev.c
index fbe4c328a367..268417c9ef22 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3999,6 +3999,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
 	if (dev->netmem_tx == NETMEM_TX_NONE)
 		goto out_free;
 
+	if (dev->netmem_tx == NETMEM_TX_NO_DMA)
+		goto out;
+
 	shinfo = skb_shinfo(skb);
 
 	if (shinfo->nr_frags > 0) {
diff --git a/net/core/devmem.c b/net/core/devmem.c
index cde4c89bc146..644c286b778f 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -181,7 +181,7 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
 }
 
 struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev,
+net_devmem_bind_dmabuf(struct net_device *dev, struct net_device *vdev,
 		       struct device *dma_dev,
 		       enum dma_data_direction direction,
 		       unsigned int dmabuf_fd, struct netdev_nl_sock *priv,
@@ -212,6 +212,7 @@ net_devmem_bind_dmabuf(struct net_device *dev,
 	}
 
 	binding->dev = dev;
+	binding->vdev = vdev;
 	xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
 
 	err = percpu_ref_init(&binding->ref,
@@ -397,7 +398,8 @@ struct net_devmem_dmabuf_binding *net_devmem_get_binding(struct sock *sk,
 	 */
 	dst_dev = dst_dev_rcu(dst);
 	if (unlikely(!dst_dev) ||
-	    unlikely(dst_dev != READ_ONCE(binding->dev))) {
+	    unlikely(dst_dev != READ_ONCE(binding->dev) &&
+		     dst_dev != READ_ONCE(binding->vdev))) {
 		err = -ENODEV;
 		goto out_unlock;
 	}
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 1c5c18581fcb..f399632b3c4b 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -19,7 +19,12 @@ struct net_devmem_dmabuf_binding {
 	struct dma_buf *dmabuf;
 	struct dma_buf_attachment *attachment;
 	struct sg_table *sgt;
+	/* Physical NIC that does the actual DMA for this binding. */
 	struct net_device *dev;
+	/* Virtual device (e.g. netkit) the user called bind-tx on. Must be
+	 * NETMEM_TX_NO_DMA.
+	 */
+	struct net_device *vdev;
 	struct gen_pool *chunk_pool;
 	/* Protect dev */
 	struct mutex lock;
@@ -84,7 +89,7 @@ struct dmabuf_genpool_chunk_owner {
 
 void __net_devmem_dmabuf_binding_free(struct work_struct *wq);
 struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev,
+net_devmem_bind_dmabuf(struct net_device *dev, struct net_device *vdev,
 		       struct device *dma_dev,
 		       enum dma_data_direction direction,
 		       unsigned int dmabuf_fd, struct netdev_nl_sock *priv,
@@ -165,7 +170,7 @@ static inline void net_devmem_put_net_iov(struct net_iov *niov)
 }
 
 static inline struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev,
+net_devmem_bind_dmabuf(struct net_device *dev, struct net_device *vdev,
 		       struct device *dma_dev,
 		       enum dma_data_direction direction,
 		       unsigned int dmabuf_fd,
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 4d2c49371cdb..b4d48f3672a5 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1077,7 +1077,7 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
 		goto err_rxq_bitmap;
 	}
 
-	binding = net_devmem_bind_dmabuf(netdev, dma_dev, DMA_FROM_DEVICE,
+	binding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE,
 					 dmabuf_fd, priv, info->extack);
 	if (IS_ERR(binding)) {
 		err = PTR_ERR(binding);
@@ -1119,9 +1119,43 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
 	return err;
 }
 
+/* Find the DMA-capable device for a netmem TX binding.
+ *
+ * For NETMEM_TX_DMA devices, return the device itself.
+ * For NETMEM_TX_NO_DMA devices, walk leased RX queues to find the underlying
+ * physical device and return it.
+ */
+static struct net_device *
+netdev_find_netmem_tx_dev(struct net_device *dev)
+{
+	struct netdev_rx_queue *lease_rxq;
+	struct net_device *phys_dev;
+	int i;
+
+	if (dev->netmem_tx == NETMEM_TX_DMA)
+		return dev;
+
+	if (dev->netmem_tx != NETMEM_TX_NO_DMA)
+		return NULL;
+
+	for (i = 0; i < dev->real_num_rx_queues; i++) {
+		lease_rxq = READ_ONCE(__netif_get_rx_queue(dev, i)->lease);
+		if (!lease_rxq)
+			continue;
+
+		phys_dev = lease_rxq->dev;
+		if (netif_device_present(phys_dev) &&
+		    phys_dev->netmem_tx == NETMEM_TX_DMA)
+			return phys_dev;
+	}
+
+	return NULL;
+}
+
 int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
 {
 	struct net_devmem_dmabuf_binding *binding;
+	struct net_device *bind_dev;
 	struct netdev_nl_sock *priv;
 	struct net_device *netdev;
 	struct device *dma_dev;
@@ -1171,22 +1205,41 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
 		goto err_unlock_netdev;
 	}
 
-	dma_dev = netdev_queue_get_dma_dev(netdev, 0, NETDEV_QUEUE_TYPE_TX);
-	binding = net_devmem_bind_dmabuf(netdev, dma_dev, DMA_TO_DEVICE,
-					 dmabuf_fd, priv, info->extack);
+	bind_dev = netdev_find_netmem_tx_dev(netdev);
+	if (!bind_dev) {
+		err = -EOPNOTSUPP;
+		NL_SET_ERR_MSG(info->extack,
+			       "No DMA-capable device found for netmem TX");
+		goto err_unlock_netdev;
+	}
+
+	if (bind_dev != netdev)
+		netdev_lock(bind_dev);
+
+	dma_dev = netdev_queue_get_dma_dev(bind_dev, 0, NETDEV_QUEUE_TYPE_TX);
+
+	binding = net_devmem_bind_dmabuf(bind_dev,
+					 bind_dev != netdev ? netdev : NULL,
+					 dma_dev, DMA_TO_DEVICE, dmabuf_fd,
+					 priv, info->extack);
 	if (IS_ERR(binding)) {
 		err = PTR_ERR(binding);
-		goto err_unlock_netdev;
+		goto err_unlock_bind_dev;
 	}
 
 	nla_put_u32(rsp, NETDEV_A_DMABUF_ID, binding->id);
 	genlmsg_end(rsp, hdr);
 
+	if (bind_dev != netdev)
+		netdev_unlock(bind_dev);
 	netdev_unlock(netdev);
 	mutex_unlock(&priv->lock);
 
 	return genlmsg_reply(rsp, info);
 
+err_unlock_bind_dev:
+	if (bind_dev != netdev)
+		netdev_unlock(bind_dev);
 err_unlock_netdev:
 	netdev_unlock(netdev);
 err_unlock_sock:

-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH net-next v3 2/8] net: netkit: declare NETMEM_TX_NO_DMA mode
From: Bobby Eshleman @ 2026-05-08  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
	Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
	Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
	Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
	kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
  Cc: dw, sdf.kernel, mohsin.bashr, willemb, jiang.kun2, xu.xin16,
	wang.yaxin, netdev, linux-doc, linux-kernel, linux-rdma, bpf,
	linux-kselftest, Stanislav Fomichev, Mina Almasry, netdev,
	linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
	Bobby Eshleman
In-Reply-To: <20260507-tcp-dm-netkit-v3-0-52821445867c@meta.com>

From: Bobby Eshleman <bobbyeshleman@meta.com>

Some virtual devices like netkit (or ifb) never DMA and never touch frag
contents, they just forward the skb to another device. They are unable
to forward unreadable skbs, however, because they fail to pass TX
validation checks on dev->netmem_tx. The existing two-state
NETMEM_TX_NONE / NETMEM_TX_DMA doesn't give the TX validator enough
information to differentiate devices that will attempt DMA on the
unreadable skb from those that will simply route it untouched.

Add a third mode to the enum so drivers can indicate 1) if they have
netmem TX support, and 2) if they do, whether they are DMA-capable:

NETMEM_TX_NO_DMA - pass-through, device never DMAs

Widen dev->netmem_tx from a 1-bit field to 2 bits to fit the new value,
and declare netkit as NETMEM_TX_NO_DMA. Devmem TX support over these
devices comes in a follow-up patch.

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- net_cachelines/net_device.rst: align the netmem_tx row's type column
  with the rest of the table by using "unsigned_long:2" instead of
  "unsigned long:2"
- Split this into a distinct patch (Jakub)
---
 Documentation/networking/net_cachelines/net_device.rst | 2 +-
 Documentation/networking/netmem.rst                    | 3 +++
 Documentation/translations/zh_CN/networking/netmem.rst | 3 +++
 drivers/net/netkit.c                                   | 1 +
 include/linux/netdevice.h                              | 7 ++++---
 5 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst
index 1c19bb7705df..7b3392553fd6 100644
--- a/Documentation/networking/net_cachelines/net_device.rst
+++ b/Documentation/networking/net_cachelines/net_device.rst
@@ -10,7 +10,7 @@ Type                                Name                        fastpath_tx_acce
 =================================== =========================== =================== =================== ===================================================================================
 unsigned_long:32                    priv_flags                  read_mostly                             __dev_queue_xmit(tx)
 unsigned_long:1                     lltx                        read_mostly                             HARD_TX_LOCK,HARD_TX_TRYLOCK,HARD_TX_UNLOCK(tx)
-unsigned long:1                     netmem_tx:1;                read_mostly
+unsigned_long:2                     netmem_tx:2;                read_mostly
 char                                name[16]
 struct netdev_name_node*            name_node
 struct dev_ifalias*                 ifalias
diff --git a/Documentation/networking/netmem.rst b/Documentation/networking/netmem.rst
index 5ccadba4f373..217869d1108d 100644
--- a/Documentation/networking/netmem.rst
+++ b/Documentation/networking/netmem.rst
@@ -99,3 +99,6 @@ Driver TX Requirements
    appropriate mode:
 
    - `NETMEM_TX_DMA`: for physical devices that perform DMA.
+
+   - `NETMEM_TX_NO_DMA`: for virtual or passthrough devices that do
+     not DMA, but still support handling of netmem-backed skbs.
diff --git a/Documentation/translations/zh_CN/networking/netmem.rst b/Documentation/translations/zh_CN/networking/netmem.rst
index 9c84423b7528..320f3eacf51b 100644
--- a/Documentation/translations/zh_CN/networking/netmem.rst
+++ b/Documentation/translations/zh_CN/networking/netmem.rst
@@ -92,3 +92,6 @@ dma-mapping API 去处理。
 2. 驱动程序应将 `netdev->netmem_tx` 设置为适当的模式:
 
    - `NETMEM_TX_DMA`:适用于执行 DMA 的物理设备。
+
+   - `NETMEM_TX_NO_DMA`:适用于不执行 DMA 的虚拟或透传设备,但仍支持
+     处理 netmem 支持的 skb。
diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
index 5e2eecc3165d..0ad6a806d7d5 100644
--- a/drivers/net/netkit.c
+++ b/drivers/net/netkit.c
@@ -466,6 +466,7 @@ static void netkit_setup(struct net_device *dev)
 	dev->priv_flags |= IFF_NO_QUEUE;
 	dev->priv_flags |= IFF_DISABLE_NETPOLL;
 	dev->lltx = true;
+	dev->netmem_tx = NETMEM_TX_NO_DMA;
 
 	dev->netdev_ops     = &netkit_netdev_ops;
 	dev->ethtool_ops    = &netkit_ethtool_ops;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 580bccb118a0..11d68e75eb4f 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1791,6 +1791,7 @@ enum netdev_stat_type {
 enum netmem_tx_mode {
 	NETMEM_TX_NONE,		/* no netmem TX support */
 	NETMEM_TX_DMA,		/* DMA-capable netmem TX (real HW) */
+	NETMEM_TX_NO_DMA,	/* no DMA, e.g. passthrough for virtual devs */
 };
 
 enum netdev_reg_state {
@@ -1814,8 +1815,8 @@ enum netdev_reg_state {
  *	@lltx:		device supports lockless Tx. Deprecated for real HW
  *			drivers. Mainly used by logical interfaces, such as
  *			bonding and tunnels
- *	@netmem_tx:	device netmem TX mode (NETMEM_TX_NONE or
- *			NETMEM_TX_DMA).
+ *	@netmem_tx:	device netmem TX mode (NETMEM_TX_NONE, NETMEM_TX_DMA,
+ *			or NETMEM_TX_NO_DMA).
  *
  *	@name:	This is the first field of the "visible" part of this structure
  *		(i.e. as seen by users in the "Space.c" file).  It is the name
@@ -2138,7 +2139,7 @@ struct net_device {
 	struct_group(priv_flags_fast,
 		unsigned long		priv_flags:32;
 		unsigned long		lltx:1;
-		unsigned long		netmem_tx:1;
+		unsigned long		netmem_tx:2;
 	);
 	const struct net_device_ops *netdev_ops;
 	const struct header_ops *header_ops;

-- 
2.53.0-Meta


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox