Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH v7 00/42] guest_memfd: In-place conversion support
From: Ackerley Tng @ 2026-06-04 21:14 UTC (permalink / raw)
  To: Sean Christopherson
  Cc: Ackerley Tng via B4 Relay, aik, andrew.jones, binbin.wu, brauner,
	chao.p.peng, david, ira.weiny, jmattson, jthoughton, michael.roth,
	oupton, pankaj.gupta, qperret, rick.p.edgecombe, rientjes,
	shivankg, steven.price, tabba, willy, wyihan, yan.y.zhao,
	forkloop, pratyush, suzuki.poulose, aneesh.kumar, liam,
	Paolo Bonzini, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
	Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
	Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
	Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka,
	kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco
In-Reply-To: <aiHeDZEPkAcWcSkn@google.com>

Sean Christopherson <seanjc@google.com> writes:

> On Wed, Jun 03, 2026, Ackerley Tng wrote:
>> Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org>
>> writes:
>>
>> > This is v7 of guest_memfd in-place conversion support.
>> >
>>
>> Here's the outstanding items after going over everyone's comments
>> including Sashiko's:
>>
>> + KVM: TDX: Make source page optional for KVM_TDX_INIT_MEM_REGION
>>     + Need to move page clearing into __kvm_gmem_get_pfn to resolve
>>       leak where populate can put initialized kernel memory into TDX
>>       guest
>>     + See suggested fix at [1]
>
> That fix works for me.  The initial guest image will typically be a tiny subset
> of guest memory, so unnecessarily zeroing a few pages isn't a performance concern.
>

In regular usage moving the zeroing in [1] doesn't change anything,
since the same zeroing would have first happened when the host faults
the pages to put the initial image. When populating, there's no more
zeroing since it was zeroed.

[1] covers the case where the host doesn't write anything to the pages
and directly tries to populate the pages to the guest.

>> + KVM: guest_memfd: Only prepare folios for private pages,
>>     + s/non-CoCo/CoCo in commit message "INIT_SHARED is about to be
>>       supported for non-CoCo VMs in a later patch in this series
>>     + Use Suggested-by: Michael Roth <michael.roth@amd.com>
>> + KVM: selftests: Test that shared/private status is consistent across
>>   processes
>>     + Improve test reliability using pthread_mutex
>>     + I have a fixup patch offline.
>> 	
>> I would like feedback on these:
>> 	
>> + KVM: selftests: Test conversion with elevated page refcount
>>     + Askar pointed out that soon vmsplice may not pin pages. Should I
>>       pin pages through CONFIG_GUP_TEST like in [2]? I prefer not to
>>       take a dependency on CONFIG_GUP_TEST.
>
> I'm not exactly excited about taking a dependency on CONFIG_GUP_TEST either, but
> it probably is the least awful choice.  E.g. KVM also pins pages is certain flows,
> but we're _also_ actively working to remove the need to pin.
>
> Hmm, maybe IORING_REGISTER_PBUF_RING?  AFAICT, it's almost literally a "pin user
> memory" syscall.
>

Hmm that takes a dependency on io_uring, which isn't always compiled
in. Between CONFIG_IO_URING and CONFIG_GUP_TEST, I'd rather
CONFIG_GUP_TEST.

>> + KVM: selftests: Add script to exercise private_mem_conversions_test
>>     + Would like to know what people think of a wrapper script before
>>       I address Sashiko's comments.
>
> NAK to a wrapper script.  This sounds like a perfect fit for Vipin's selftest
> runner (which I'm like 4 months overdue for reviewing, testing, and merging).
> If the runner _can't_ do what you want, then I'd rather improve the runner.
>
> [*] https://lore.kernel.org/all/20260331194202.1722082-1-vipinsh@google.com
>

Good to know we have this!

Thanks, I'll work on a v8 to clean up the above.

>>
>> [1] https://lore.kernel.org/all/CAEvNRgEVC=fFuKVgZYvWyZD7t_zvUZihFG8hrACjvtkD5cwugw@mail.gmail.com/
>> [2] https://lore.kernel.org/all/baa8838f623102931e755cf34c86314b305af49c.1747264138.git.ackerleytng@google.com/
>>
>> >
>> > [...snip...]
>> >

^ permalink raw reply

* Re: [PATCH v3] killswitch: add per-function short-circuit mitigation primitive
From: Justin Suess @ 2026-06-04 21:18 UTC (permalink / raw)
  To: Sasha Levin
  Cc: Song Liu, linux-kernel, linux-doc, linux-kselftest, bpf,
	live-patching, Greg Kroah-Hartman, Andrew Morton, Jonathan Corbet,
	Mathieu Desnoyers, Joshua Peisach, Florian Weimer, Breno Leitao,
	Anthony Iliopoulos, Michal Hocko, Jiri Olsa
In-Reply-To: <agsVDqdALBoHEHlv@laps>

On Mon, May 18, 2026 at 09:33:02AM -0400, Sasha Levin wrote:
> On Sun, May 17, 2026 at 11:37:36PM -0700, Song Liu wrote:
> > On Sun, May 17, 2026 at 6:49 AM Sasha Levin <sashal@kernel.org> wrote:
> > > * fail_function (CONFIG_FUNCTION_ERROR_INJECTION) is disabled in
> > >   most production kernels. Even where enabled, it only works on
> > >   functions pre-annotated with ALLOW_ERROR_INJECTION() in source -
> > >   no help for a freshly-disclosed CVE. The debugfs UI is blocked by
> > >   lockdown=integrity and the override is probabilistic.
> > > 
> > > * BPF override (bpf_override_return) honors the same
> > >   ALLOW_ERROR_INJECTION() whitelist, and BPF itself is off in many
> > >   production kernels. Even where on, the operator interface is
> > >   "load a verified BPF program," not a one-line write.
> > 
> > If it is OK for killswitch to attach to any kernel functions, do we still
> > need ALLOW_ERROR_INJECTION() for fail_function and BPF
> > override? Shall we instead also allow fail_function and BPF override
> > to attach to any kernel functions?
> 
> I don't think so. ALLOW_ERROR_INJECTION is not a security mechanism, it's an
> integrity/safety mechanism for both bpf and fault injection.
> 
> It protects against a "developer or CI script doing legitimate fault injection
> accidentally panics the box" scenario, not an "attacker gets in" one.
>
At that point why not just make this entire killswitch mechanism an expanded
version of the bpf_override_return helper that doesn't care about ALLOW_ERROR_INJECTION?

Then killswitch mitigations are just BPF programs.

This could be paired with a userspace tool for building and
loading the killswitch programs conveniently.

You can make the helper function only succeed if (CONFIG_KILLSWITCH=y
CONFIG_BPF_KPROBE_OVERRIDE=y etc.) and taint the kernel on the first call.

BPF has the crash_kexec kfunc already that can take down the kernel.
Thus it's not crazy in my opinion to add a helper with a similar intentional
intentional footgun in another kfunc/helper.

We can automatically benefit from BPF signing mechanisms to prevent
unauthorized loading of programs. If killswitch is enabled, users can
restrict unauthorized use of it by restricting the loading of all BPF
programs to those signed w/ the key.

Thanks,
Justin
> -- 
> Thanks,
> Sasha

^ permalink raw reply

* Re: [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
From: Nathan Chancellor @ 2026-06-04 21:48 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Yunseong Kim, Ingo Molnar, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
	Andrey Konovalov, Andrew Morton, Nick Desaulniers, Bill Wendling,
	Justin Stitt, Nicolas Schier, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	linux-kernel, kasan-dev, llvm, linux-kbuild, rust-for-linux,
	workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260604084519.GA3126523@noisy.programming.kicks-ass.net>

On Thu, Jun 04, 2026 at 10:45:19AM +0200, Peter Zijlstra wrote:
> On Wed, Jun 03, 2026 at 07:43:29PM +0200, Yunseong Kim wrote:
> > Add CFLAGS_KCOV_DATAFLOW and RUSTFLAGS_KCOV_DATAFLOW exports to
> > scripts/Makefile.kcov, containing:
> >   -fsanitize-coverage=dataflow-args,dataflow-ret -g
> >   (with optional -fno-inline via CONFIG_KCOV_DATAFLOW_NO_INLINE)
> > 
> > scripts/Makefile.lib applies these flags when a module's Makefile sets:
> >   KCOV_DATAFLOW_file.o := y   (per-file)
> >   KCOV_DATAFLOW := y          (per-directory)
> > 
> > Also supports CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL for global enablement.
> > The flags are only applied to kernel objects (same guard as basic KCOV).
> > 
> > Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> > ---
> >  scripts/Makefile.kcov | 6 ++++++
> >  scripts/Makefile.lib  | 7 +++++++
> >  2 files changed, 13 insertions(+)
> > 
> > diff --git a/scripts/Makefile.kcov b/scripts/Makefile.kcov
> > index 78305a84ba9d..101173fe194b 100644
> > --- a/scripts/Makefile.kcov
> > +++ b/scripts/Makefile.kcov
> > @@ -2,10 +2,16 @@
> >  kcov-flags-y					+= -fsanitize-coverage=trace-pc
> >  kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS)	+= -fsanitize-coverage=trace-cmp
> >  
> > +# KCOV dataflow: trace function args and return values
> > +kcov-dataflow-flags-y := -fsanitize-coverage=dataflow-args,dataflow-ret -g
> > +kcov-dataflow-flags-$(CONFIG_KCOV_DATAFLOW_NO_INLINE) += -fno-inline
> 
> https://clang.llvm.org/docs/ClangCommandLineReference.html
> 
> Has no mention of -fno-inline, furthermore, what are the exact
> semantics? Does it inhibit __always_inline?
> 

Based on clang/test/CodeGen/always-inline.c [1], I believe the semantics
are the same as GCC's '-fno-inline' [2], which avoids inlining except
for always_inline functions.

[1]: https://github.com/llvm/llvm-project/blob/1d13b74cf086629d5cdae5f44ef4a62cebcaf3ff/clang/test/CodeGen/always-inline.c
[2]: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fno-inline

-- 
Cheers,
Nathan

^ permalink raw reply

* Re: [PATCH net-next v3 05/13] net: ethernet: oa_tc6: Export standard defined registers
From: Andrew Lunn @ 2026-06-04 22:13 UTC (permalink / raw)
  To: ciprian.regus
  Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
	linux-doc, devicetree
In-Reply-To: <20260604-adin1140-driver-v3-5-5debdb3173c4@analog.com>

On Thu, Jun 04, 2026 at 07:32:50PM +0300, Ciprian Regus via B4 Relay wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
> 
> Move defines for standard Open Alliance TC6 register addresses and
> subfields in the oa_tc6's header and add entries for the PHYID and
> CONFIG2. As such, other ethernet drivers that rely on oa_tc6 can use
> them directly.
> 
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>

Reviewed-by: Andrew Lunn <andrew@lunn.ch>

    Andrew

^ permalink raw reply

* Re: [PATCH net-next v3 06/13] net: ethernet: oa_tc6: Add the OA_TC6_ prefix to standard registers
From: Andrew Lunn @ 2026-06-04 22:14 UTC (permalink / raw)
  To: ciprian.regus
  Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
	linux-doc, devicetree
In-Reply-To: <20260604-adin1140-driver-v3-6-5debdb3173c4@analog.com>

On Thu, Jun 04, 2026 at 07:32:51PM +0300, Ciprian Regus via B4 Relay wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
> 
> The OA TC6 standard registers are currently exported in a header file.
> Add the OA_TC6_ prefix to the register address and subfield mask macros
> to avoid future naming conflicts.
> 
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>

Reviewed-by: Andrew Lunn <andrew@lunn.ch>

    Andrew

^ permalink raw reply

* Re: [PATCH net-next v3 07/13] net: ethernet: oa_tc6: Add read_mms/write_mms register access functions
From: Andrew Lunn @ 2026-06-04 22:19 UTC (permalink / raw)
  To: ciprian.regus
  Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
	linux-doc, devicetree
In-Reply-To: <20260604-adin1140-driver-v3-7-5debdb3173c4@analog.com>

> +/**
> + * oa_tc6_read_register_mms - function for reading a MAC-PHY register in a
> + * memory map other than 0.

Is MMS 0 somehow special? Does it not work if passed to this function?

I'm not suggesting all the current calls are replaced with this, just
that the documentation might be inaccurate?

> + * @tc6: oa_tc6 struct.
> + * @mms: Memory map selector for the register.
> + * @address: register address of the MAC-PHY to be read.
> + * @value: value read from the @address register address of the MAC-PHY.
> + *
> + * Return: 0 on success otherwise failed.

Isn't the "otherwise" actually an error code?

	Andrew

^ permalink raw reply

* Re: [PATCH net-next v3 07/13] net: ethernet: oa_tc6: Add read_mms/write_mms register access functions
From: Andrew Lunn @ 2026-06-04 22:21 UTC (permalink / raw)
  To: ciprian.regus
  Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
	linux-doc, devicetree
In-Reply-To: <20260604-adin1140-driver-v3-7-5debdb3173c4@analog.com>

> +#define OA_TC6_REG_ADDR_MASK		GENMASK(15, 0)

> +int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u32 address,
> +			     u32 *value)

I think address should be a u16? That way the compiler will catch
anybody trying to pass an address which is too big.

    Andrew

---
pw-bot: cr

^ permalink raw reply

* Re: [PATCH net-next v3 08/13] net: ethernet: oa_tc6: Use the read_mms/write_mms functions for C45
From: Andrew Lunn @ 2026-06-04 22:23 UTC (permalink / raw)
  To: ciprian.regus
  Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
	linux-doc, devicetree
In-Reply-To: <20260604-adin1140-driver-v3-8-5debdb3173c4@analog.com>

On Thu, Jun 04, 2026 at 07:32:53PM +0300, Ciprian Regus via B4 Relay wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
> 
> Accessing PHY MMD devices requires control transactions to registers in
> a memory map other than 0. Replace the current formatting of the
> register addresses with the oa_tc6_{read,write}_register_mms()
> functions. While we're here, introduce the mms variable to store the
> memory map returned by oa_tc6_get_phy_c45_mms() instead of ret, in order
> to improve the code readability.
> 
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>

Reviewed-by: Andrew Lunn <andrew@lunn.ch>

    Andrew

^ permalink raw reply

* Re: [PATCH net-next v3 09/13] net: ethernet: oa_tc6: Add new register address defines
From: Andrew Lunn @ 2026-06-04 22:31 UTC (permalink / raw)
  To: ciprian.regus
  Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
	linux-doc, devicetree
In-Reply-To: <20260604-adin1140-driver-v3-9-5debdb3173c4@analog.com>

> +/* Vendor specific memory map. */
> +#define OA_TC6_VEND_MMS1			1

I'm not sure that is the best name. Table 6 calls it MAC. 10-15 are
listed as vendor specific. Ideally we want to avoid confusion between
MAC registers and other vendor registers. So maybe OA_TC6_MAC_MMS1?
That would fit the pattern:

/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the
 * OPEN Alliance specification.
 */
#define OA_TC6_PHY_C45_PCS_MMS2                 2       /* MMD 3 */
#define OA_TC6_PHY_C45_PMA_PMD_MMS3             3       /* MMD 1 */
#define OA_TC6_PHY_C45_VS_PLCA_MMS4             4       /* MMD 31 */
#define OA_TC6_PHY_C45_AUTO_NEG_MMS5            5       /* MMD 7 */
#define OA_TC6_PHY_C45_POWER_UNIT_MMS6          6       /* MMD 13 */

I personally would generalise the comment here, and add it to this
list, so keeping everything together.

    Andrew

^ permalink raw reply

* Re: [PATCH net-next v3 13/13] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
From: Andrew Lunn @ 2026-06-04 22:36 UTC (permalink / raw)
  To: ciprian.regus
  Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
	linux-doc, devicetree
In-Reply-To: <20260604-adin1140-driver-v3-13-5debdb3173c4@analog.com>

> +	ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_VEND_MMS1,
> +					ADIN1140_MAC_P1_LOOP_ADDR_REG, 0x0);

Looking at this, don't you think it is a bit odd writing a _MAC_
register to a _VEND_ MMS? Having consistent naming will help spot dumb
typos where you mix up registers and MMS's. It is an error i've made
in the past, and now try to make more obvious by using consistent
naming schemes.

    Andrew


^ permalink raw reply

* [PATCH v1] arm64: errata: Workaround NVIDIA Olympus device store/load ordering erratum
From: Shanker Donthineni @ 2026-06-04 23:12 UTC (permalink / raw)
  To: Catalin Marinas, Will Deacon, linux-arm-kernel
  Cc: Mark Rutland, linux-kernel, linux-doc, Shanker Donthineni,
	Vikram Sethi, Jason Sequeira

On systems with NVIDIA Olympus cores, a Device-nGnR* load can be
observed by a peripheral before an older, non-overlapping Device-nGnR*
store to the same peripheral. This breaks the program-order guarantee
that software expects for Device-nGnR* accesses and can leave a
peripheral in an incorrect state, as a load is observed before an
earlier store takes effect.

The erratum can occur only when all of the following apply:

  - A PE executes a Device-nGnR* store followed by a younger
    Device-nGnR* load.
  - The store is not a store-release.
  - The accesses target the same peripheral and do not overlap in bytes.
  - There is at most one intervening Device-nGnR* store in program
    order, and there are no intervening Device-nGnR* loads.
  - There is no DSB, and no DMB that orders loads, between the store and
    the load.
  - Specific micro-architectural and timing conditions occur.

Two ways to restore ordering: insert a barrier (any DSB, or a DMB that
orders loads) between the store and the load, or make the store a
store-release. A load-acquire on the load side would not help, because
acquire semantics do not prevent a load from being observed ahead of an
older store; only the store side (release or a barrier) closes the
window.

Promote the raw MMIO store helpers (__raw_writeb/w/l/q) from plain str*
to stlr* (Store-Release), which removes the "store is not a
store-release" condition for every device write the kernel issues.
Because writel() and writel_relaxed() are both built on __raw_writel()
in asm-generic/io.h, patching the raw variants covers both the
non-relaxed and relaxed APIs without touching the higher layers. Note
that writel()'s own barrier sits before the store, so it does not order
the store against a subsequent readl(); the store-release promotion is
what provides that ordering.

Like ARM64_ERRATUM_832075 on the load side, the change is gated on a new
ARM64_WORKAROUND_DEVICE_STORE_RELEASE capability and only activated on
parts that match MIDR_NVIDIA_OLYMPUS, so unaffected CPUs continue to use
the plain str* sequence.

Co-developed-by: Vikram Sethi <vsethi@nvidia.com>
Signed-off-by: Vikram Sethi <vsethi@nvidia.com>
Signed-off-by: Shanker Donthineni <sdonthineni@nvidia.com>
---
 Documentation/arch/arm64/silicon-errata.rst |  2 ++
 arch/arm64/Kconfig                          | 23 ++++++++++++++++++++
 arch/arm64/include/asm/io.h                 | 24 ++++++++++++++-------
 arch/arm64/kernel/cpu_errata.c              |  8 +++++++
 arch/arm64/tools/cpucaps                    |  1 +
 5 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index 211119ce7adc..899bed3908bb 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -256,6 +256,8 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | NVIDIA         | Carmel Core     | N/A             | NVIDIA_CARMEL_CNP_ERRATUM   |
 +----------------+-----------------+-----------------+-----------------------------+
+| NVIDIA         | Olympus core    | T410-OLY-1027   | NVIDIA_OLYMPUS_1027_ERRATUM |
++----------------+-----------------+-----------------+-----------------------------+
 | NVIDIA         | T241 GICv3/4.x  | T241-FABRIC-4   | N/A                         |
 +----------------+-----------------+-----------------+-----------------------------+
 | NVIDIA         | T241 MPAM       | T241-MPAM-1     | N/A                         |
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index fe60738e5943..a6bac84b05a1 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -564,6 +564,29 @@ config ARM64_ERRATUM_832075
 
 	  If unsure, say Y.
 
+config NVIDIA_OLYMPUS_1027_ERRATUM
+	bool "NVIDIA Olympus: device store/load ordering erratum"
+	default y
+	help
+	  This option adds an alternative code sequence to work around an
+	  NVIDIA Olympus core erratum where a Device-nGnR* store can be
+	  observed by a peripheral after a younger Device-nGnR* load to the
+	  same peripheral. This breaks the program order that drivers rely
+	  on for MMIO and can leave a device in an incorrect state.
+
+	  The workaround promotes the raw MMIO store helpers
+	  (__raw_writeb/w/l/q) to Store-Release (STLR), which restores the
+	  required ordering. Because writel() and writel_relaxed() are built
+	  on __raw_writel(), both are covered without changes to the higher
+	  layers.
+
+	  The fix is applied through the alternatives framework, so enabling
+	  this option does not by itself activate the workaround: it is
+	  patched in only when an affected CPU is detected, and is a no-op on
+	  unaffected CPUs.
+
+	  If unsure, say Y.
+
 config ARM64_ERRATUM_834220
 	bool "Cortex-A57: 834220: Stage 2 translation fault might be incorrectly reported in presence of a Stage 1 fault (rare)"
 	depends on KVM
diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h
index 8cbd1e96fd50..b6d7966e9c19 100644
--- a/arch/arm64/include/asm/io.h
+++ b/arch/arm64/include/asm/io.h
@@ -25,29 +25,37 @@
 #define __raw_writeb __raw_writeb
 static __always_inline void __raw_writeb(u8 val, volatile void __iomem *addr)
 {
-	volatile u8 __iomem *ptr = addr;
-	asm volatile("strb %w0, %1" : : "rZ" (val), "Qo" (*ptr));
+	asm volatile(ALTERNATIVE("strb %w0, [%1]",
+				 "stlrb %w0, [%1]",
+				 ARM64_WORKAROUND_DEVICE_STORE_RELEASE)
+		     : : "rZ" (val), "r" (addr));
 }
 
 #define __raw_writew __raw_writew
 static __always_inline void __raw_writew(u16 val, volatile void __iomem *addr)
 {
-	volatile u16 __iomem *ptr = addr;
-	asm volatile("strh %w0, %1" : : "rZ" (val), "Qo" (*ptr));
+	asm volatile(ALTERNATIVE("strh %w0, [%1]",
+				 "stlrh %w0, [%1]",
+				 ARM64_WORKAROUND_DEVICE_STORE_RELEASE)
+		     : : "rZ" (val), "r" (addr));
 }
 
 #define __raw_writel __raw_writel
 static __always_inline void __raw_writel(u32 val, volatile void __iomem *addr)
 {
-	volatile u32 __iomem *ptr = addr;
-	asm volatile("str %w0, %1" : : "rZ" (val), "Qo" (*ptr));
+	asm volatile(ALTERNATIVE("str %w0, [%1]",
+				 "stlr %w0, [%1]",
+				 ARM64_WORKAROUND_DEVICE_STORE_RELEASE)
+		     : : "rZ" (val), "r" (addr));
 }
 
 #define __raw_writeq __raw_writeq
 static __always_inline void __raw_writeq(u64 val, volatile void __iomem *addr)
 {
-	volatile u64 __iomem *ptr = addr;
-	asm volatile("str %x0, %1" : : "rZ" (val), "Qo" (*ptr));
+	asm volatile(ALTERNATIVE("str %x0, [%1]",
+				 "stlr %x0, [%1]",
+				 ARM64_WORKAROUND_DEVICE_STORE_RELEASE)
+		     : : "rZ" (val), "r" (addr));
 }
 
 #define __raw_readb __raw_readb
diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
index 5377e4c2eba2..958d7f16bfeb 100644
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
@@ -809,6 +809,14 @@ const struct arm64_cpu_capabilities arm64_errata[] = {
 		ERRATA_MIDR_ALL_VERSIONS(MIDR_NVIDIA_CARMEL),
 	},
 #endif
+#ifdef CONFIG_NVIDIA_OLYMPUS_1027_ERRATUM
+	{
+		/* NVIDIA Olympus core */
+		.desc = "NVIDIA Olympus device load/store ordering erratum",
+		.capability = ARM64_WORKAROUND_DEVICE_STORE_RELEASE,
+		ERRATA_MIDR_ALL_VERSIONS(MIDR_NVIDIA_OLYMPUS),
+	},
+#endif
 #ifdef CONFIG_ARM64_WORKAROUND_TRBE_OVERWRITE_FILL_MODE
 	{
 		/*
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 811c2479e82d..d367257bf770 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -120,6 +120,7 @@ WORKAROUND_CAVIUM_TX2_219_PRFM
 WORKAROUND_CAVIUM_TX2_219_TVM
 WORKAROUND_CLEAN_CACHE
 WORKAROUND_DEVICE_LOAD_ACQUIRE
+WORKAROUND_DEVICE_STORE_RELEASE
 WORKAROUND_NVIDIA_CARMEL_CNP
 WORKAROUND_PMUV3_IMPDEF_TRAPS
 WORKAROUND_QCOM_FALKOR_E1003
-- 
2.43.0


^ permalink raw reply related

* htmldocs: Documentation/admin-guide/mm/damon/hugepage.rst: WARNING: document isn't included in any toctree [toc.not_included]
From: kernel test robot @ 2026-06-04 23:37 UTC (permalink / raw)
  To: Asier Gutierrez; +Cc: oe-kbuild-all, 0day robot, linux-doc

tree:   https://github.com/intel-lab-lkp/linux/commits/gutierrez-asier-huawei-partners-com/mm-damon-Introduce-DAMOS_QUOTA_HUGEPAGE-auto-tuning/20260604-230803
head:   263912f5b34dfc2412f2e8b7ea92f6a278ebcfe7
commit: 263912f5b34dfc2412f2e8b7ea92f6a278ebcfe7 Documentation/admin-guide/mm/damon: add DAMON-based Hugepage Management
date:   8 hours ago
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project f43d6834093b19baf79beda8c0337ab020ac5f17)
docutils: docutils (Docutils 0.21.2, Python 3.13.5, on linux)
reproduce: (https://download.01.org/0day-ci/archive/20260605/202606050129.sxUrDe40-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202606050129.sxUrDe40-lkp@intel.com/

All warnings (new ones prefixed by >>):

   Documentation/userspace-api/landlock:550: ./include/uapi/linux/landlock.h:45: ERROR: Unknown target name: "network flags". [docutils]
   Documentation/userspace-api/landlock:550: ./include/uapi/linux/landlock.h:50: ERROR: Unknown target name: "scope flags". [docutils]
   Documentation/userspace-api/landlock:550: ./include/uapi/linux/landlock.h:24: ERROR: Unknown target name: "filesystem flags". [docutils]
   Documentation/userspace-api/landlock:559: ./include/uapi/linux/landlock.h:168: ERROR: Unknown target name: "filesystem flags". [docutils]
   Documentation/userspace-api/landlock:559: ./include/uapi/linux/landlock.h:191: ERROR: Unknown target name: "network flags". [docutils]
>> Documentation/admin-guide/mm/damon/hugepage.rst: WARNING: document isn't included in any toctree [toc.not_included]
   Documentation/networking/skbuff:36: ./include/linux/skbuff.h:181: WARNING: Failed to create a cross reference. A title or caption not found: 'crc' [ref.ref]

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH v2 2/6] alloc_tag: add ioctl filters to /proc/allocinfo
From: Abhishek Bapat @ 2026-06-04 23:53 UTC (permalink / raw)
  To: Hao Ge
  Cc: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Shuah Khan,
	Jonathan Corbet, linux-doc, linux-kernel, linux-mm, Sourav Panda
In-Reply-To: <aba73999-dc29-45c1-b6cd-54a7c1b824fa@linux.dev>

On Sun, May 24, 2026 at 8:00 PM Hao Ge <hao.ge@linux.dev> wrote:
>
> Hi Abhishek
>
>
> On 2026/5/23 01:45, Abhishek Bapat wrote:
> > Extend the capability of the IOCTL mechanism to filter allocations based
> > on tag's module name, function name, file name and line number.
> >
> > Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
> > ---
> >   include/uapi/linux/alloc_tag.h | 26 ++++++++++++++-
> >   lib/alloc_tag.c                | 58 ++++++++++++++++++++++++++++++++--
> >   2 files changed, 80 insertions(+), 4 deletions(-)
> >
> > diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h
> > index e9a5b55fcc7a..0cc9db5298c6 100644
> > --- a/include/uapi/linux/alloc_tag.h
> > +++ b/include/uapi/linux/alloc_tag.h
> > @@ -34,8 +34,32 @@ struct allocinfo_tag_data {
> >       struct allocinfo_counter counter;
> >   };
> >
> > +enum {
> > +     ALLOCINFO_FILTER_MODNAME,
> > +     ALLOCINFO_FILTER_FUNCTION,
> > +     ALLOCINFO_FILTER_FILENAME,
> > +     ALLOCINFO_FILTER_LINENO,
> > +     __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO
> > +};
> > +
> > +#define ALLOCINFO_FILTER_MASK_MODNAME                (1 << ALLOCINFO_FILTER_MODNAME)
> > +#define ALLOCINFO_FILTER_MASK_FUNCTION               (1 << ALLOCINFO_FILTER_FUNCTION)
> > +#define ALLOCINFO_FILTER_MASK_FILENAME               (1 << ALLOCINFO_FILTER_FILENAME)
> > +#define ALLOCINFO_FILTER_MASK_LINENO         (1 << ALLOCINFO_FILTER_LINENO)
> > +
> > +#define ALLOCINFO_FILTER_MASKS \
> > +     ((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1)
> > +
> > +struct allocinfo_filter {
> > +     __u64 mask; /* bitmask of the filter fields used */
> > +     struct allocinfo_tag fields;
> > +};
> > +
> >   struct allocinfo_get_at {
> > -     __u64 pos;      /* input */
> > +     /* inputs */
> > +     __u64 pos;
> > +     struct allocinfo_filter filter;
> > +     /* output */
> >       struct allocinfo_tag_data data;
> >   };
> >
> > diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
> > index 3598735b6c93..56c394ef721f 100644
> > --- a/lib/alloc_tag.c
> > +++ b/lib/alloc_tag.c
> > @@ -48,6 +48,7 @@ int alloc_tag_ref_offs;
> >   struct allocinfo_private {
> >       struct codetag_iterator iter;
> >       bool print_header;
> > +     struct allocinfo_filter filter;
> >       /* ioctl uses a separate iterator not to interfere with reads */
> >       struct codetag_iterator ioctl_iter;
> >       bool positioned; /* seq_open_private() sets to 0 */
> > @@ -167,6 +168,11 @@ static void allocinfo_copy_str(char *dest, const char *src)
> >       strscpy(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);
> >   }
> >
> > +static int allocinfo_cmp_str(const char *str, const char *template)
> > +{
> > +     return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE);
> > +}
> > +
> >   static void allocinfo_to_params(struct codetag *ct,
> >                               struct allocinfo_tag_data *data)
> >   {
> > @@ -198,27 +204,71 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
> >       return 0;
> >   }
> >
> > +static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter)
> > +{
> > +     if (!filter || !filter->mask)
> > +             return true;
> > +
> > +     if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) {
> > +             if (!ct->modname)
> > +                     return false;
> > +             if (allocinfo_cmp_str(ct->modname, filter->fields.modname))
> > +                     return false;
> > +     }
> > +
>
> Apologies -- I previously suggested the "!ct->modname ->
>
> return false" approach, but I realized we broke the ability to
>
> filter for built-in allocations.  allocinfo_to_params() returns
>
> modname="" for those, so a user would naturally try to filter
>
> by modname="" -- except it never matches because ct->modname is
>
> NULL, not an empty string.
>
> Maybe something like this instead?
>
> if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) {
>
>      if (ct->modname) {
>
>          if (allocinfo_cmp_str(ct->modname, filter->fields.modname))
>
>              return false;
>
>          } else if (filter->fields.modname[0] != '\0') {
>
>              return false;
>
>          }
>
> }
>
> That way modname="" matches built-in tags, which lines up with
>
> what the API actually returns.
>
> Thanks
>
> Hao
>

Ah good catch, will use the following logic that's a bit cleaner:
if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) {
                /* user wants to filter by modname but ct->modname is NULL */
                if (!ct->modname) {
                        /* validate if user was attempting to filter
for built-in allocations */
                        if (filter->fields.modname[0] != '\0')
                                return false;
                }
                if (allocinfo_cmp_str(ct->modname, filter->fields.modname))
                        return false;
        }

> > +     if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) &&
> > +         ct->function && (allocinfo_cmp_str(ct->function, filter->fields.function)))
> > +             return false;
> > +
> > +     if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) &&
> > +         ct->filename && (allocinfo_cmp_str(ct->filename, filter->fields.filename)))
> > +             return false;
> > +
> > +     if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) &&
> > +         ct->lineno != filter->fields.lineno)
> > +             return false;
> > +
> > +     return true;
> > +}
> > +
> >   static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
> >   {
> >       struct allocinfo_private *priv;
> >       struct codetag *ct;
> > -     __u64 pos;
> >       struct allocinfo_get_at params = {0};
> > +     __u64 skip_count;
> >
> >       if (copy_from_user(&params, arg, sizeof(params)))
> >               return -EFAULT;
> >
> > +     if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)
> > +             return -EINVAL;
> > +
> >       priv = (struct allocinfo_private *)m->private;
> > -     pos = params.pos;
> > +
> > +     skip_count = params.pos;
> >
> >       mutex_lock(&priv->ioctl_lock);
> >       codetag_lock_module_list(alloc_tag_cttype, true);
> >
> > +     if (params.filter.mask)
> > +             priv->filter = params.filter;
> > +     else
> > +             priv->filter.mask = 0;
> > +
> >       /* Find the codetag */
> >       priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
> >       ct = codetag_next_ct(&priv->ioctl_iter);
> > -     while (ct && pos--)
> > +
> > +     while (ct) {
> > +             if (matches_filter(ct, &priv->filter)) {
> > +                     if (skip_count == 0)
> > +                             break;
> > +                     skip_count--;
> > +             }
> >               ct = codetag_next_ct(&priv->ioctl_iter);
> > +     }
> > +
> >       if (ct) {
> >               allocinfo_to_params(ct, &params.data);
> >               priv->positioned = true;
> > @@ -254,6 +304,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
> >       }
> >
> >       ct = codetag_next_ct(&priv->ioctl_iter);
> > +     while (ct && !matches_filter(ct, &priv->filter))
> > +             ct = codetag_next_ct(&priv->ioctl_iter);
> >       if (ct)
> >               allocinfo_to_params(ct, &params);
> >

^ permalink raw reply

* Re: [PATCH v2 3/7] seg6: add End.M.GTP6.E behavior
From: Andrea Mayer @ 2026-06-05  1:20 UTC (permalink / raw)
  To: Yuya Kusakabe
  Cc: David S. Miller, Eric Dumazet, David Ahern, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Justin Iurman, Shuah Khan,
	Jonathan Corbet, Shuah Khan, linux-kernel, netdev,
	linux-kselftest, linux-doc, stefano.salsano, ahabdels,
	Andrea Mayer, andrea
In-Reply-To: <20260505-seg6-mobile-v2-3-9e8022bdfdb6@gmail.com>

On Tue, 05 May 2026 01:30:13 +0900
Yuya Kusakabe <yuya.kusakabe@gmail.com> wrote:

Hi Yuya,

I do not repeat below the points from my cover letter and patch 1-2 replies
(drop reasons, OIF/VRF removal, C helper, coding style, etc.).

> Add the End.M.GTP6.E behavior (RFC 9433 Section 6.5), the IPv6 dual
> of End.M.GTP4.E.  An End.M.GTP6.E SID always sits in the penultimate
> position of an SR Policy (RFC 9433 Section 6.5 Notes); when it
> becomes the active SID (segments_left == 1) the kernel pops the
> IPv6/SRH outer, recovers TEID and QFI from the 40-bit
> Args.Mob.Session field encoded in the locator-relative slice of the
> SID, and re-encapsulates the inner T-PDU in IPv6/UDP/GTP-U toward
> the next segment held in SRH[0].
> 
> The flow info, traffic class and hop limit are propagated from the
> inbound IPv6 outer to the new outer (RFC 6040).
> 
> When net.netfilter.nf_hooks_lwtunnel=1, the inner T-PDU traverses
> NF_INET_PRE_ROUTING between the SRv6 strip and the GTP-U push,
> mirroring End.DX4 / End.DX6.
> 
> Configuration:
> 
>   ip -6 route add 2001:db8:e::/64 \
>       encap seg6local action End.M.GTP6.E src 2001:db8:2::1 \
>       dev <dev>

SEG6_LOCAL_MOBILE_SRC_ADDR (the "src" attribute) is copied verbatim into
the outer IPv6 source address. In patch 2 (End.M.GTP4.E) the same
attribute is used as a template from which bits are extracted to form
the IPv4 source address, and may be entirely unused depending on
v4_mask_len.
This UAPI overload needs revision.

>
> Link: https://www.rfc-editor.org/rfc/rfc9433.html#section-6.5
> Link: https://www.rfc-editor.org/rfc/rfc6040
> Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
> ---
>  include/uapi/linux/seg6_local.h                    |   2 +
>  net/ipv6/seg6_local.c                              | 312 ++++++++++++++++
>  tools/testing/selftests/net/Makefile               |   1 +
>  .../selftests/net/srv6_end_m_gtp6_e_test.sh        | 402 +++++++++++++++++++++
>  4 files changed, 717 insertions(+)
> 
> diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
> index b42cb526bb81..8e46ede2980d 100644
> --- a/include/uapi/linux/seg6_local.h
> +++ b/include/uapi/linux/seg6_local.h
> @@ -75,6 +75,8 @@ enum {
>  	SEG6_LOCAL_ACTION_END_MAP	= 17,
>  	/* SRv6 to IPv4/GTP-U encap (RFC 9433 Section 6.6) */
>  	SEG6_LOCAL_ACTION_END_M_GTP4_E	= 18,
> +	/* SRv6 to IPv6/GTP-U encap (RFC 9433 Section 6.5) */
> +	SEG6_LOCAL_ACTION_END_M_GTP6_E	= 19,
>  
>  	__SEG6_LOCAL_ACTION_MAX,
>  };
> diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
> index 4051fe89e6d1..4e5d138c3657 100644
> --- a/net/ipv6/seg6_local.c
> +++ b/net/ipv6/seg6_local.c

> + [snip]

> +static int input_action_end_m_gtp6_e_finish(struct net *net,
> +					    struct sock *sk,
> +					    struct sk_buff *skb)
> +{
> +	enum skb_drop_reason reason = SKB_DROP_REASON_SEG6_MOBILE_NOMEM;
> +	struct seg6_mobile_gtp6_e_cb cb = *SEG6_MOBILE_GTP6_E_CB(skb);
> +	struct dst_entry *orig_dst = skb_dst(skb);
> +	const struct seg6_mobile_info *minfo;
> +	struct seg6_local_lwt *slwt;
> +	struct ipv6hdr *new_ip6h;
> +	struct udphdr *uh;
> +
> +	slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
> +	minfo = &slwt->mobile_info;
> +

Same dst/lwtstate issue as patch 2.

> +	/* Reject GSO packets that would not fit the egress IPv6/UDP/GTP-U
> +	 * path after our outer headers are added; the GSO segmenter cannot
> +	 * adjust mss across SRv6 -> GTP-U conversion.  Skip the check
> +	 * entirely when no MTU is known on the current dst.
> +	 */
> +	if (skb_is_gso(skb)) {
> +		unsigned int ovhd = sizeof(*new_ip6h) + sizeof(*uh) +
> +				    sizeof(struct gtp1_header_long) +
> +				    sizeof(struct seg6_mobile_pdu_session_ext);
> +		unsigned int mtu = dst_mtu(skb_dst(skb));
> +
> +		if (mtu && (mtu <= ovhd ||
> +			    !skb_gso_validate_network_len(skb, mtu - ovhd))) {
> +			reason = SKB_DROP_REASON_SEG6_MOBILE_MTU_EXCEEDED;
> +			goto drop;
> +		}
> +	}
> +
> +	/* Reserve worst-case headroom for the entire outer chain we are about
> +	 * to push: IPv6 + UDP + GTP-U long header + PDU Session extension.
> +	 * Subsequent skb_cow_head() calls inside seg6_mobile_push_gtpu() then
> +	 * become no-ops.
> +	 */
> +	if (skb_cow_head(skb,
> +			 sizeof(*new_ip6h) + sizeof(*uh) +
> +			 sizeof(struct gtp1_header_long) +
> +			 sizeof(struct seg6_mobile_pdu_session_ext)))

Same ovhd scoping point as patch 2.

> +		goto drop;
> +

Same missing iptunnel_handle_offloads() as patch 2.

> +	if (seg6_mobile_push_gtpu(skb, cb.teid, cb.qfi, cb.pdu_type,
> +				  cb.pdu_type_set))
> +		goto drop;
> +
> +	uh = skb_push(skb, sizeof(*uh));
> +	skb_reset_transport_header(skb);
> +	uh->source = htons(GTP1U_PORT);
> +	uh->dest = htons(GTP1U_PORT);
> +	uh->len = htons(skb->len);
> +

Same fixed source port question as patch 2.

> +	new_ip6h = skb_push(skb, sizeof(*new_ip6h));
> +	skb_reset_network_header(skb);
> +	memset(new_ip6h, 0, sizeof(*new_ip6h));
> +	ip6_flow_hdr(new_ip6h, cb.tclass, cb.flowlabel);
> +	new_ip6h->payload_len = htons(skb->len - sizeof(*new_ip6h));
> +	new_ip6h->nexthdr = IPPROTO_UDP;
> +	new_ip6h->hop_limit = cb.hop_limit;
> +	new_ip6h->saddr = minfo->src_addr;
> +	new_ip6h->daddr = cb.next_sid;
> +
> +	/* RFC 8200 requires UDP/IPv6 checksums.  Initialise the
> +	 * pseudo-header sum and let the stack/NIC complete it via
> +	 * CHECKSUM_PARTIAL so we do not pay a per-packet linear sum and
> +	 * we cooperate with offload.
> +	 */
> +	skb->ip_summed = CHECKSUM_PARTIAL;
> +	skb->csum_start = (unsigned char *)uh - skb->head;
> +	skb->csum_offset = offsetof(struct udphdr, check);
> +	uh->check = ~csum_ipv6_magic(&new_ip6h->saddr, &new_ip6h->daddr,
> +				     skb->len - sizeof(*new_ip6h),
> +				     IPPROTO_UDP, 0);
> +

udp6_set_csum() already handles the CHECKSUM_PARTIAL + pseudo-header seed
setup and also covers the GSO case. Using it would avoid open-coding this
sequence.

> +	skb->protocol = htons(ETH_P_IPV6);
> +	nf_reset_ct(skb);
> +	skb_dst_drop(skb);
> +
> +	seg6_lookup_any_nexthop(skb, &cb.next_sid, 0, false, slwt->oif);
> +	return dst_input(skb);
> +
> +drop:
> +	kfree_skb_reason(skb, reason);
> +	return -EINVAL;
> +}

seg6_lookup_any_nexthop() already calls skb_dst_drop() internally. The
explicit call above is redundant.

> + [snip]

> +static int input_action_end_m_gtp6_e(struct sk_buff *skb,
> +				     struct seg6_local_lwt *slwt)
> +{
> +	enum skb_drop_reason reason = SKB_DROP_REASON_SEG6_MOBILE_BAD_SID;
> +	const struct seg6_mobile_info *minfo = &slwt->mobile_info;
> +	struct seg6_mobile_gtp6_e_cb *cb;
> +	struct in6_addr next_sid;
> +	struct ipv6_sr_hdr *srh;
> +	u8 hop_limit, tclass, qfi;
> +	unsigned int outer_len;
> +	struct ipv6hdr *ip6h;
> +	int inner_nfproto;
> +	__be32 flowlabel;
> +	__be16 frag_off;
> +	u64 args_mob;
> +	u32 teid;
> +	int off;
> +	u8 nh;
> +

Same reverse Christmas tree issue as patch 2.

> + [snip]

> +	/* RFC 6040 outer-to-outer propagation: copy DSCP+ECN (tclass) and
> +	 * the flow label from the SRv6 outer to the new IPv6 outer.  Use
> +	 * ip6_flowlabel() (not ip6_flowinfo()) so the tclass byte is
> +	 * supplied exactly once via the @tclass argument of ip6_flow_hdr().
> +	 */
> +	flowlabel = ip6_flowlabel(ip6h);
> +	tclass = ipv6_get_dsfield(ip6h);
> +	hop_limit = ip6h->hop_limit;
> +

Same RFC 6040 question as patch 2 (here also flow label).

> +	/* RFC 9433 Section 6.5 upper-layer S02 mandates "Pop the IPv6
> +	 * header and all its extension headers".  ipv6_skip_exthdr()
> +	 * walks every extension header (HBH/Routing/Dest-Opts/Fragment)
> +	 * so HBH-before-SRH and DOpts-after-SRH are handled too.  The
> +	 * terminal next-header value also selects NFPROTO_IPV4 /
> +	 * NFPROTO_IPV6 for the NF_INET_PRE_ROUTING hook below.
> +	 */
> +	nh = ip6h->nexthdr;
> +	off = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nh, &frag_off);
> +	if (off < 0) {
> +		reason = SKB_DROP_REASON_SEG6_MOBILE_BAD_INNER;
> +		goto drop;
> +	}
> +	outer_len = off;
> +

Same BAD_INNER misuse as patch 2.

Same frag_off check missing after ipv6_skip_exthdr() as patch 2.

> + [snip]

> +	/* For inner IP traffic that may traverse NF_INET_PRE_ROUTING below,
> +	 * pull the full inner IP header into the linear area so a netfilter
> +	 * hook reading skb_transport_header() does not access stale data.
> +	 * Non-IP inner is forwarded as-is via the GTP-U T-PDU payload.
> +	 */
> +	if (!pskb_may_pull(skb, outer_len + ((inner_nfproto == NFPROTO_IPV4) ?
> +					     sizeof(struct iphdr) :
> +					     (inner_nfproto == NFPROTO_IPV6) ?
> +					     sizeof(struct ipv6hdr) : 0))) {
> +		reason = SKB_DROP_REASON_SEG6_MOBILE_BAD_INNER;
> +		goto drop;
> +	}
> +

Same repeated ternary as patch 2.

> + [snip]

>  static struct seg6_action_desc seg6_action_table[] = {
>  	{
> @@ -2153,6 +2431,17 @@ static struct seg6_action_desc seg6_action_table[] = {
>  			.build_state = seg6_mobile_v4_validate,
>  		},
>  	},
> +	{
> +		.action		= SEG6_LOCAL_ACTION_END_M_GTP6_E,
> +		.attrs		= SEG6_F_ATTR(SEG6_LOCAL_MOBILE_SRC_ADDR),
> +		.optattrs	= SEG6_F_LOCAL_COUNTERS |
> +				  SEG6_F_ATTR(SEG6_LOCAL_MOBILE_PDU_TYPE) |
> +				  SEG6_F_ATTR(SEG6_LOCAL_OIF),
> +		.input		= input_action_end_m_gtp6_e,
> +		.slwt_ops	= {
> +			.build_state = seg6_mobile_gtp6_e_validate,
> +		},
> +	},

> + [snip]

> +/* End.M.GTP6.E SID layout (RFC 9433 Section 6.5):
> + *
> + *   | locator (route prefix)  | Args.Mob.Session (40) | pad |
> + *
> + * The locator length is the route's IPv6 destination prefix length.
> + * Reject route additions whose prefix leaves no room for the 40-bit
> + * Args.Mob.Session field at setup time so the operator gets a clear
> + * error from `ip route add` instead of silent per-packet drops.
> + */
> +static int seg6_mobile_gtp6_e_validate(struct seg6_local_lwt *slwt,
> +				       const void *cfg,
> +				       struct netlink_ext_ack *extack)
> +{
> +	const struct fib6_config *fib6_cfg = cfg;
> +
> +	if ((unsigned int)fib6_cfg->fc_dst_len + SEG6_MOBILE_ARGS_MOB_LEN > 128) {

Nit: fc_dst_len is int in struct fib6_config (IPv6 prefix length, range
0..128); the (unsigned int) cast is not needed.

> + [snip]

Thanks,

Ciao,
Andrea

P.S. I am temporarily writing from another address due to a mail
delivery issue at my @uniroma2.it address. Please always Cc my default
andrea.mayer@uniroma2.it address on replies.

^ permalink raw reply

* Re: [PATCH v2 0/4] dt-bindings: remove redundant .txt redirect stubs
From: patchwork-bot+netdevbpf @ 2026-06-05  1:30 UTC (permalink / raw)
  To: Akash Sukhavasi
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, robh,
	krzk+dt, conor+dt, mchehab, olteanv, horms, corbet, skhan,
	dmitry.torokhov, thierry.reding, jonathanh, lee, netdev,
	devicetree, linux-kernel, linux-media, linux-doc, linux-input,
	linux-tegra
In-Reply-To: <20260603-b4-remove-redirect-stubs-v2-0-c8c19876ab64@gmail.com>

Hello:

This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Wed, 03 Jun 2026 15:42:17 -0500 you wrote:
> Several .txt files under Documentation/devicetree/bindings/ contain
> only a redirect notice pointing to a .yaml schema with the same base
> filename in the same directory. These stubs were useful during the
> .txt to .yaml transition but are now redundant, since the .yaml is
> discoverable by name. Meanwhile, other documentation still references
> some of these stubs, forcing readers through an unnecessary extra hop
> to reach the actual schema.
> 
> [...]

Here is the summary with links:
  - [v2,1/4] dt-bindings: net: remove obsolete mdio.txt
    https://git.kernel.org/netdev/net-next/c/ed2b921b542b
  - [v2,2/4] dt-bindings: media: remove obsolete rc.txt
    (no matching commit)
  - [v2,3/4] dt-bindings: net: dsa: remove obsolete dsa.txt
    https://git.kernel.org/netdev/net-next/c/0fbbc19482bd
  - [v2,4/4] dt-bindings: input: remove obsolete matrix-keymap.txt
    (no matching commit)

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* 回复:[PATCH v17 net-next 05/11] net/nebula-matrix: add channel layer
From: Illusion Wang @ 2026-06-05  2:00 UTC (permalink / raw)
  To: Paolo Abeni, Dimon, Alvin, Sam, netdev
  Cc: andrew+netdev, corbet, kuba, horms, linux-doc, vadim.fedorenko,
	lukas.bulwahn, edumazet, enelsonmoore, skhan, hkallweit1,
	open list
In-Reply-To: <c5750d8a-5b37-4269-819b-1c3c04be0ddf@redhat.com>

> Sashiko reports a number of issues WRT the atomic status updates:
> 
> https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260601093149.25905-1-illusion.wang%40nebula-matrix.com

> It's indeed quite complex to get things right this way. I'm wondering if
> you should use a spin_lock instead to protect the relevant state?

> Also a few process related notes:
> - to make effective progresses you should proactively reply on the ML WRT
> the sashiko comments discussion if and why they are (not) relevant.
> - is 'illusion wang' (all lower case) your real name? we can't accept
> pseudonims as SoB tag.

Thank you for your feedback.
Yes, I've reviewed the Sashiko report. The atomic status update race is real:
nbl_chan_recv_ack_msg writes status and need_waked while nbl_chan_send_msg
may read/clear them, with no ordering guarantee between the two atomics.
The spinlock fix addresses exactly what Sashiko flagged.
I'll update the patchset accordingly.
ps: "illusion wang" is indeed my real name.

^ permalink raw reply

* [PATCH 0/3] *** Add support for Silergy SQ24860 ***
From: Ziming Zhu @ 2026-06-05  2:04 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon, devicetree, linux-kernel, linux-doc,
	Ziming Zhu

From: Ziming Zhu <ziming.zhu@silergycorp.com>

Add support for the Silergy SQ24860 eFuse.

SQ24860 is a PMBus device that reports input voltage, output voltage,
auxiliary voltage, input current, input power, and temperature
telemetry.
It also supports average and peak history reporting.

The series adds the devicetree binding, PMBus hwmon driver, and hwmon
documentation.

Tested on a board with SQ24860 at I2C address 0x40. Verified voltage,
current, power, temperature, samples, and history reset hwmon
attributes.

Ziming Zhu (3):
  dt-bindings: hwmon: pmbus: Add bindings for Silergy SQ24860
  hwmon: pmbus: Add support for Silergy SQ24860
  hwmon: Add documentation for SQ24860

 .../bindings/hwmon/pmbus/silergy,sq24860.yaml |  74 +++
 Documentation/hwmon/index.rst                 |   1 +
 Documentation/hwmon/sq24860.rst               |  96 ++++
 drivers/hwmon/pmbus/Kconfig                   |  19 +
 drivers/hwmon/pmbus/Makefile                  |   1 +
 drivers/hwmon/pmbus/sq24860.c                 | 423 ++++++++++++++++++
 6 files changed, 614 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml
 create mode 100644 Documentation/hwmon/sq24860.rst
 create mode 100644 drivers/hwmon/pmbus/sq24860.c

-- 
2.25.1


^ permalink raw reply

* [PATCH 3/3] hwmon: Add documentation for SQ24860
From: Ziming Zhu @ 2026-06-05  2:04 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon, devicetree, linux-kernel, linux-doc,
	Ziming Zhu
In-Reply-To: <20260605020450.89638-1-zmzhu0630@163.com>

From: Ziming Zhu <ziming.zhu@silergycorp.com>

Document the supported sysfs attributes for the Silergy SQ24860 PMBus
hwmon driver.

Signed-off-by: Ziming Zhu <ziming.zhu@silergycorp.com>
---
 Documentation/hwmon/index.rst   |  1 +
 Documentation/hwmon/sq24860.rst | 96 +++++++++++++++++++++++++++++++++
 2 files changed, 97 insertions(+)
 create mode 100644 Documentation/hwmon/sq24860.rst

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 8b655e5d6b68..1888905cd234 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -242,6 +242,7 @@ Hardware Monitoring Kernel Drivers
    smsc47m192
    smsc47m1
    sparx5-temp
+   sq24860
    spd5118
    stpddc60
    surface_fan
diff --git a/Documentation/hwmon/sq24860.rst b/Documentation/hwmon/sq24860.rst
new file mode 100644
index 000000000000..7029f612e1b9
--- /dev/null
+++ b/Documentation/hwmon/sq24860.rst
@@ -0,0 +1,96 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver sq24860
+=====================
+
+Supported chips:
+
+  * Silergy SQ24860
+
+    Prefix: 'sq24860'
+
+Author:
+
+	Ziming Zhu <ziming.zhu@silergycorp.com>
+
+Description
+------------
+
+This driver implements support for the Silergy SQ24860 eFuse. The device is an
+integrated circuit protection and power management device with a PMBus
+interface.
+
+The device supports direct format for reading input voltage, output voltage,
+auxiliary voltage, input current, input power, and temperature.
+
+The current and power measurement scale depends on the resistor connected
+between the IMON pin and ground. The resistor value can be configured with the
+``sy,rimon-micro-ohms`` device tree property. See
+``Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml`` for details.
+
+Due to the specificities of the chip, all history reset attributes are tied
+together. Resetting the history of one sensor resets the history of all sensors.
+
+Sysfs entries
+-------------
+
+The following attributes are supported. Limits are read-write; all other
+attributes are read-only.
+
+======================= ======================================================
+in1_label               "vin"
+in1_input               Measured input voltage.
+in1_average             Average measured input voltage.
+in1_min                 Minimum input voltage limit.
+in1_lcrit               Critical low input voltage limit.
+in1_max                 Maximum input voltage limit.
+in1_crit                Critical high input voltage limit.
+in1_min_alarm           Input voltage low warning alarm.
+in1_lcrit_alarm         Input voltage low fault alarm.
+in1_max_alarm           Input voltage high warning alarm.
+in1_crit_alarm          Input voltage high fault alarm.
+in1_highest             Historical maximum input voltage.
+in1_lowest              Historical minimum input voltage.
+in1_reset_history       Write any value to reset history.
+
+in2_label               "vmon"
+in2_input               Measured auxiliary input voltage.
+
+in3_label               "vout1"
+in3_input               Measured output voltage.
+in3_average             Average measured output voltage.
+in3_min                 Minimum output voltage limit.
+in3_min_alarm           Output voltage low alarm.
+in3_lowest              Historical minimum output voltage.
+in3_reset_history       Write any value to reset history.
+
+curr1_label             "iin"
+curr1_input             Measured input current.
+curr1_average           Average measured input current.
+curr1_max               Maximum input current warning limit.
+curr1_crit              Critical input over-current fault limit.
+curr1_max_alarm         Input current warning alarm.
+curr1_crit_alarm        Input over-current fault alarm.
+curr1_highest           Historical maximum input current.
+curr1_reset_history     Write any value to reset history.
+
+power1_label            "pin"
+power1_input            Measured input power.
+power1_average          Average measured input power.
+power1_max              Maximum input power warning limit.
+power1_alarm            Input power warning alarm.
+power1_input_highest    Historical maximum input power.
+power1_reset_history    Write any value to reset history.
+
+temp1_input             Measured temperature.
+temp1_average           Average measured temperature.
+temp1_max               Maximum temperature warning limit.
+temp1_crit              Critical temperature fault limit.
+temp1_max_alarm         Temperature warning alarm.
+temp1_crit_alarm        Temperature fault alarm.
+temp1_highest           Historical maximum temperature.
+temp1_reset_history     Write any value to reset history.
+
+samples                 Number of samples used for average values.
+======================= ======================================================
+
-- 
2.25.1


^ permalink raw reply related

* [PATCH 2/3] hwmon: pmbus: Add support for Silergy SQ24860
From: Ziming Zhu @ 2026-06-05  2:04 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon, devicetree, linux-kernel, linux-doc,
	Ziming Zhu
In-Reply-To: <20260605020450.89638-1-zmzhu0630@163.com>

From: Ziming Zhu <ziming.zhu@silergycorp.com>

Add PMBus hwmon support for the Silergy SQ24860 eFuse.

The driver reports input voltage, output voltage, auxiliary voltage,
input current, input power, and temperature. It also exposes peak,
average, and minimum history attributes, sample count configuration,
and maps the manufacturer-specific VIREF register to the generic input
over-current fault limit attribute.

The IMON resistor value is read from the silergy,rimon-micro-ohms device
property and used to configure the input current calibration gain.

Signed-off-by: Ziming Zhu <ziming.zhu@silergycorp.com>
---
 drivers/hwmon/pmbus/Kconfig   |  19 ++
 drivers/hwmon/pmbus/Makefile  |   1 +
 drivers/hwmon/pmbus/sq24860.c | 423 ++++++++++++++++++++++++++++++++++
 3 files changed, 443 insertions(+)
 create mode 100644 drivers/hwmon/pmbus/sq24860.c

diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 8f4bff375ecb..a905b5af137c 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -612,6 +612,25 @@ config SENSORS_STEF48H28
 	  This driver can also be built as a module. If so, the module will
 	  be called stef48h28.
 
+config SENSORS_SQ24860
+	tristate "Silergy SQ24860"
+	help
+	  If you say yes here you get hardware monitoring support for Silergy
+	  SQ24860 eFuse.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called sq24860.
+
+config SENSORS_SQ24860_REGULATOR
+	bool "Regulator support for SQ24860"
+	depends on SENSORS_SQ24860 && REGULATOR
+	default SENSORS_SQ24860
+	help
+	  If you say yes here you get regulator support for Silergy SQ24860.
+	  The regulator is registered through the PMBus regulator framework and
+	  can be used to control the output exposed by the device.
+	  This option is only useful if regulator framework support is needed.
+
 config SENSORS_STPDDC60
 	tristate "ST STPDDC60"
 	help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 7129b62bc00f..86bc93c6c091 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_PM6764TR)	+= pm6764tr.o
 obj-$(CONFIG_SENSORS_PXE1610)	+= pxe1610.o
 obj-$(CONFIG_SENSORS_Q54SJ108A2)	+= q54sj108a2.o
 obj-$(CONFIG_SENSORS_STEF48H28)	+= stef48h28.o
+obj-$(CONFIG_SENSORS_SQ24860)	+= sq24860.o
 obj-$(CONFIG_SENSORS_STPDDC60)	+= stpddc60.o
 obj-$(CONFIG_SENSORS_TDA38640)	+= tda38640.o
 obj-$(CONFIG_SENSORS_TPS25990)	+= tps25990.o
diff --git a/drivers/hwmon/pmbus/sq24860.c b/drivers/hwmon/pmbus/sq24860.c
new file mode 100644
index 000000000000..1c3b2bf2a026
--- /dev/null
+++ b/drivers/hwmon/pmbus/sq24860.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2024.
+// Author: Ziming Zhu <ziming.zhu@silergycorp.com>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "pmbus.h"
+
+#define SQ24860_IIN_CAL_GAIN		0x38
+#define SQ24860_READ_VAUX		0xd0
+#define SQ24860_READ_VIN_MIN		0xd1
+#define SQ24860_READ_VIN_PEAK		0xd2
+#define SQ24860_READ_IIN_PEAK		0xd4
+#define SQ24860_READ_PIN_PEAK		0xd5
+#define SQ24860_READ_TEMP_AVG		0xd6
+#define SQ24860_READ_TEMP_PEAK		0xd7
+#define SQ24860_READ_VOUT_MIN		0xda
+#define SQ24860_READ_VIN_AVG		0xdc
+#define SQ24860_READ_VOUT_AVG		0xdd
+#define SQ24860_READ_IIN_AVG		0xde
+#define SQ24860_READ_PIN_AVG		0xdf
+#define SQ24860_VIREF			0xe0
+#define SQ24860_PK_MIN_AVG		0xea
+#define PK_MIN_AVG_RST_PEAK		BIT(7)
+#define PK_MIN_AVG_RST_AVG		BIT(6)
+#define PK_MIN_AVG_RST_MIN		BIT(5)
+#define PK_MIN_AVG_AVG_CNT		GENMASK(2, 0)
+#define SQ24860_MFR_WRITE_PROTECT	0xf8
+#define SQ24860_UNLOCKED		BIT(7)
+
+#define SQ24860_8B_SHIFT		2
+#define SQ24860_IIN_OCF_NUM		1000000
+#define SQ24860_IIN_OCF_DIV		129278
+#define SQ24860_IIN_OCF_OFF		165
+
+#define PK_MIN_AVG_RST_MASK		(PK_MIN_AVG_RST_PEAK | \
+					 PK_MIN_AVG_RST_AVG  | \
+					 PK_MIN_AVG_RST_MIN)
+
+/*
+ * Arbitrary default Rimon value: 1.6kOhm
+ */
+#define SQ24860_DEFAULT_RIMON		1600000000
+#define SQ24860_DEFAULT_GIMON		18180
+
+#define SQ24860_VAUX_DIV		20
+
+static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon, u32 gimon)
+{
+	u64 temp = (u64)6400 * 1000000000 * 1000;
+	u16 word = temp / ((u64)rimon * gimon);
+	int ret = i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN,
+						word);
+	return ret;
+}
+
+static int sq24860_mfr_write_protect_set(struct i2c_client *client,
+					  u8 protect)
+{
+	u8 val;
+
+	switch (protect) {
+	case 0:
+		val = 0xa2;
+		break;
+	case PB_WP_ALL:
+		val = 0x0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT,
+				     val);
+}
+
+static int sq24860_mfr_write_protect_get(struct i2c_client *client)
+{
+	int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT);
+
+	if (ret < 0)
+		return ret;
+
+	return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL;
+}
+
+static int sq24860_read_word_data(struct i2c_client *client,
+				   int page, int phase, int reg)
+{
+	int ret;
+
+	switch (reg) {
+	case PMBUS_VIRT_READ_VIN_MAX:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_VIN_PEAK);
+		break;
+
+	case PMBUS_VIRT_READ_VIN_MIN:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_VIN_MIN);
+		break;
+
+	case PMBUS_VIRT_READ_VIN_AVG:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_VIN_AVG);
+		break;
+
+	case PMBUS_VIRT_READ_VOUT_MIN:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_VOUT_MIN);
+		break;
+
+	case PMBUS_VIRT_READ_VOUT_AVG:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_VOUT_AVG);
+		break;
+
+	case PMBUS_VIRT_READ_IIN_AVG:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_IIN_AVG);
+		break;
+
+	case PMBUS_VIRT_READ_IIN_MAX:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_IIN_PEAK);
+		break;
+
+	case PMBUS_VIRT_READ_TEMP_AVG:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_TEMP_AVG);
+		break;
+
+	case PMBUS_VIRT_READ_TEMP_MAX:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_TEMP_PEAK);
+		break;
+
+	case PMBUS_VIRT_READ_PIN_AVG:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_PIN_AVG);
+		break;
+
+	case PMBUS_VIRT_READ_PIN_MAX:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_PIN_PEAK);
+		break;
+
+	case PMBUS_VIRT_READ_VMON:
+		ret = pmbus_read_word_data(client, page, phase,
+					   SQ24860_READ_VAUX);
+		if (ret < 0)
+			break;
+		ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV);
+		break;
+
+	case PMBUS_VIN_UV_WARN_LIMIT:
+	case PMBUS_VIN_UV_FAULT_LIMIT:
+	case PMBUS_VIN_OV_WARN_LIMIT:
+	case PMBUS_VIN_OV_FAULT_LIMIT:
+	case PMBUS_VOUT_UV_WARN_LIMIT:
+	case PMBUS_IIN_OC_WARN_LIMIT:
+	case PMBUS_OT_WARN_LIMIT:
+	case PMBUS_OT_FAULT_LIMIT:
+	case PMBUS_PIN_OP_WARN_LIMIT:
+		/*
+		 * These registers provide an 8 bits value instead of a
+		 * 10bits one. Just shifting twice the register value is
+		 * enough to make the sensor type conversion work, even
+		 * if the datasheet provides different m, b and R for
+		 * those.
+		 */
+		ret = pmbus_read_word_data(client, page, phase, reg);
+		if (ret < 0)
+			break;
+		ret <<= SQ24860_8B_SHIFT;
+		break;
+
+	case PMBUS_IIN_OC_FAULT_LIMIT:
+		/*
+		 * VIREF directly sets the over-current limit at which the eFuse
+		 * will turn the FET off and trigger a fault. Expose it through
+		 * this generic property instead of a manufacturer specific one.
+		 */
+		ret = pmbus_read_byte_data(client, page, SQ24860_VIREF);
+		if (ret < 0)
+			break;
+		ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM,
+					SQ24860_IIN_OCF_DIV);
+		ret += SQ24860_IIN_OCF_OFF;
+		break;
+
+	case PMBUS_VIRT_SAMPLES:
+		ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG);
+		if (ret < 0)
+			break;
+		ret = 1 << FIELD_GET(PK_MIN_AVG_AVG_CNT, ret);
+		break;
+
+	case PMBUS_VIRT_RESET_TEMP_HISTORY:
+	case PMBUS_VIRT_RESET_VIN_HISTORY:
+	case PMBUS_VIRT_RESET_IIN_HISTORY:
+	case PMBUS_VIRT_RESET_PIN_HISTORY:
+	case PMBUS_VIRT_RESET_VOUT_HISTORY:
+		ret = 0;
+		break;
+
+	default:
+		ret = -ENODATA;
+		break;
+	}
+
+	return ret;
+}
+
+static int sq24860_write_word_data(struct i2c_client *client,
+				    int page, int reg, u16 value)
+{
+	int ret;
+
+	switch (reg) {
+	case PMBUS_VIN_UV_WARN_LIMIT:
+	case PMBUS_VIN_UV_FAULT_LIMIT:
+	case PMBUS_VIN_OV_WARN_LIMIT:
+	case PMBUS_VIN_OV_FAULT_LIMIT:
+	case PMBUS_VOUT_UV_WARN_LIMIT:
+	case PMBUS_IIN_OC_WARN_LIMIT:
+	case PMBUS_OT_WARN_LIMIT:
+	case PMBUS_OT_FAULT_LIMIT:
+	case PMBUS_PIN_OP_WARN_LIMIT:
+		value >>= SQ24860_8B_SHIFT;
+		value = clamp_val(value, 0, 0xff);
+		ret = pmbus_write_word_data(client, page, reg, value);
+		break;
+
+	case PMBUS_IIN_OC_FAULT_LIMIT:
+		value -= SQ24860_IIN_OCF_OFF;
+		value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV,
+					  SQ24860_IIN_OCF_NUM);
+		value = clamp_val(value, 0, 0x3f);
+		ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value);
+		break;
+
+	case PMBUS_VIRT_SAMPLES:
+		value = clamp_val(value, 1, 1 << PK_MIN_AVG_AVG_CNT);
+		value = ilog2(value);
+		ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG,
+					     PK_MIN_AVG_AVG_CNT,
+					     FIELD_PREP(PK_MIN_AVG_AVG_CNT, value));
+		break;
+
+	case PMBUS_VIRT_RESET_TEMP_HISTORY:
+	case PMBUS_VIRT_RESET_VIN_HISTORY:
+	case PMBUS_VIRT_RESET_IIN_HISTORY:
+	case PMBUS_VIRT_RESET_PIN_HISTORY:
+	case PMBUS_VIRT_RESET_VOUT_HISTORY:
+		/*
+		 * SQ24860 has history resets based on MIN/AVG/PEAK instead of per
+		 * sensor type. Exposing this quirk in hwmon is not desirable so
+		 * reset MIN, AVG and PEAK together. Even is there effectively only
+		 * one reset, which resets everything, expose the 5 entries so
+		 * userspace is not required map a sensor type to another to trigger
+		 * a reset
+		 */
+		ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG,
+					     PK_MIN_AVG_RST_MASK,
+					     PK_MIN_AVG_RST_MASK);
+		break;
+
+	default:
+		ret = -ENODATA;
+		break;
+	}
+
+	return ret;
+}
+
+static int sq24860_read_byte_data(struct i2c_client *client,
+				   int page, int reg)
+{
+	int ret;
+
+	switch (reg) {
+	case PMBUS_WRITE_PROTECT:
+		ret = sq24860_mfr_write_protect_get(client);
+		break;
+
+	default:
+		ret = -ENODATA;
+		break;
+	}
+
+	return ret;
+}
+
+static int sq24860_write_byte_data(struct i2c_client *client,
+				    int page, int reg, u8 byte)
+{
+	int ret;
+
+	switch (reg) {
+	case PMBUS_WRITE_PROTECT:
+		ret = sq24860_mfr_write_protect_set(client, byte);
+		break;
+
+	default:
+		ret = -ENODATA;
+		break;
+	}
+
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR)
+static const struct regulator_desc sq24860_reg_desc[] = {
+	PMBUS_REGULATOR_ONE_NODE("vout"),
+};
+#endif
+
+static const struct pmbus_driver_info sq24860_base_info = {
+	.pages = 1,
+	.format[PSC_VOLTAGE_IN] = direct,
+	.m[PSC_VOLTAGE_IN] = 64,
+	.b[PSC_VOLTAGE_IN] = 0,
+	.R[PSC_VOLTAGE_IN] = 0,
+	.format[PSC_VOLTAGE_OUT] = direct,
+	.m[PSC_VOLTAGE_OUT] = 64,
+	.b[PSC_VOLTAGE_OUT] = 0,
+	.R[PSC_VOLTAGE_OUT] = 0,
+	.format[PSC_TEMPERATURE] = direct,
+	.m[PSC_TEMPERATURE] = 1,
+	.b[PSC_TEMPERATURE] = 0,
+	.R[PSC_TEMPERATURE] = 0,
+	/*
+	 * Current and Power measurement depends on the ohm value
+	 * of Rimon. m is multiplied by 1000 below to have an integer
+	 * and -3 is added to R to compensate.
+	 */
+	.format[PSC_CURRENT_IN] = direct,
+	.m[PSC_CURRENT_IN] = 16,
+	.b[PSC_CURRENT_IN] = 0,
+	.R[PSC_CURRENT_IN] = 0,
+	.format[PSC_POWER] = direct,
+	.m[PSC_POWER] = 2,
+	.b[PSC_POWER] = 0,
+	.R[PSC_POWER] = 0,
+	.func[0] = (PMBUS_HAVE_VIN |
+		    PMBUS_HAVE_VOUT |
+		    PMBUS_HAVE_VMON |
+		    PMBUS_HAVE_IIN |
+		    PMBUS_HAVE_PIN |
+		    PMBUS_HAVE_TEMP |
+		    PMBUS_HAVE_STATUS_VOUT |
+		    PMBUS_HAVE_STATUS_IOUT |
+		    PMBUS_HAVE_STATUS_INPUT |
+		    PMBUS_HAVE_STATUS_TEMP |
+		    PMBUS_HAVE_SAMPLES),
+	.read_word_data = sq24860_read_word_data,
+	.write_word_data = sq24860_write_word_data,
+	.read_byte_data = sq24860_read_byte_data,
+	.write_byte_data = sq24860_write_byte_data,
+
+#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR)
+	.reg_desc = sq24860_reg_desc,
+	.num_regulators = ARRAY_SIZE(sq24860_reg_desc),
+#endif
+};
+
+static const struct i2c_device_id sq24860_i2c_id[] = {
+	{ "sq24860" },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id);
+
+static const struct of_device_id sq24860_of_match[] = {
+	{ .compatible = "silergy,sq24860" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sq24860_of_match);
+
+static int sq24860_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct pmbus_driver_info *info;
+	u32 rimon = SQ24860_DEFAULT_RIMON;
+	u32 gimon = SQ24860_DEFAULT_GIMON;
+	int ret;
+
+	ret = device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon);
+	if (ret < 0 && ret != -EINVAL)
+		return dev_err_probe(dev, ret, "failed to get rimon\n");
+
+	ret = sq24860_write_iin_cal_gain(client, rimon, gimon);
+	if (ret < 0)
+		return dev_err_probe(&client->dev, ret,
+					     "Failed to set gain\n");
+	info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	return pmbus_do_probe(client, info);
+}
+
+static struct i2c_driver sq24860_driver = {
+	.driver = {
+		.name = "sq24860",
+		.of_match_table = sq24860_of_match,
+	},
+	.probe = sq24860_probe,
+	.id_table = sq24860_i2c_id,
+};
+module_i2c_driver(sq24860_driver);
+
+MODULE_AUTHOR("Ziming Zhu <ziming.zhu@silergycorp.com>");
+MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
-- 
2.25.1


^ permalink raw reply related

* [PATCH 1/3] dt-bindings: hwmon: pmbus: Add bindings for Silergy SQ24860
From: Ziming Zhu @ 2026-06-05  2:04 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon, devicetree, linux-kernel, linux-doc,
	Ziming Zhu
In-Reply-To: <20260605020450.89638-1-zmzhu0630@163.com>

From: Ziming Zhu <ziming.zhu@silergycorp.com>

Add devicetree binding documentation for the Silergy SQ24860 eFuse.

The device is a PMBus hardware monitoring device which reports voltage,
current, power, and temperature telemetry. The board-specific IMON
resistor value is described with silergy,rimon-micro-ohms.

Signed-off-by: Ziming Zhu <ziming.zhu@silergycorp.com>
---
 .../bindings/hwmon/pmbus/silergy,sq24860.yaml | 74 +++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml

diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml
new file mode 100644
index 000000000000..03ef82c11e1a
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/hwmon/pmbus/silergy,sq24860.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Silergy SQ24860 eFuse
+
+maintainers:
+  - Ziming Zhu <ziming.zhu@silergycorp.com>
+
+description:
+  The Silergy SQ24860 is an integrated, high-current circuit protection and
+  power management device with PMBus interface.
+
+properties:
+  compatible:
+    const: silergy,sq24860
+
+  reg:
+    maxItems: 1
+
+  silergy,rimon-micro-ohms:
+    description:
+      Micro-ohms value of the resistance installed between the IMON pin and
+      the ground reference.
+
+  interrupts:
+    description: PMBus SMBAlert interrupt.
+    maxItems: 1
+
+  regulators:
+    type: object
+    description:
+      List of regulators provided by this controller.
+
+    properties:
+      vout:
+        $ref: /schemas/regulator/regulator.yaml#
+        type: object
+        unevaluatedProperties: false
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - silergy,rimon-micro-ohms
+
+additionalProperties: false
+
+examples:
+  - |
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        hw-monitor@40 {
+            compatible = "silergy,sq24860";
+            reg = <0x40>;
+
+            interrupt-parent = <&gpio>;
+            interrupts = <42 8>;
+            silergy,rimon-micro-ohms = <1600000000>;
+
+            regulators {
+                cpu0_vout: vout {
+                    regulator-name = "main_cpu0";
+                };
+            };
+        };
+    };
-- 
2.25.1


^ permalink raw reply related

* [PATCH] Docs/damon: add TLB flush policy document
From: Kunwu Chan @ 2026-06-05  3:10 UTC (permalink / raw)
  To: sj, akpm, david, ljs, liam, vbabka, rppt, surenb, mhocko, corbet,
	skhan
  Cc: damon, linux-mm, linux-doc, linux-kernel, Kunwu Chan, Wang Lian

From: Kunwu Chan <kunwu.chan@gmail.com>

DAMON avoids TLB flushes after clearing PTE Accessed bits for sampling.
The overhead was measured and found significant [1].  Production
workloads with large working sets flush TLB buffers naturally, so
accuracy impact is negligible.

On systems with large TLB buffers and small test workloads, stale TLB
entries persist across sampling intervals and produce false negatives.
This comes up repeatedly on the mailing list and in private inquiries
[2][3].

Add a document on the design decision, trade-offs, test environment
problems, and recommendations.

Link: https://lore.kernel.org/20200403103059.12762-1-sjpark@amazon.com [1]
Link: https://lore.kernel.org/20260117020731.226785-3-sj@kernel.org [2]
Link: https://lore.kernel.org/all/20260526145034.91594-1-sj@kernel.org [3]

Co-developed-by: Wang Lian <lianux.mm@gmail.com>
Signed-off-by: Wang Lian <lianux.mm@gmail.com>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
 Documentation/mm/damon/index.rst     |   1 +
 Documentation/mm/damon/tlb_flush.rst | 131 +++++++++++++++++++++++++++
 2 files changed, 132 insertions(+)
 create mode 100644 Documentation/mm/damon/tlb_flush.rst

diff --git a/Documentation/mm/damon/index.rst b/Documentation/mm/damon/index.rst
index 318f6a7bfea4..5e239437dab3 100644
--- a/Documentation/mm/damon/index.rst
+++ b/Documentation/mm/damon/index.rst
@@ -19,6 +19,7 @@ DAMON is a Linux kernel subsystem for efficient :ref:`data access monitoring
 
    faq
    design
+   tlb_flush
    api
    maintainer-profile
 
diff --git a/Documentation/mm/damon/tlb_flush.rst b/Documentation/mm/damon/tlb_flush.rst
new file mode 100644
index 000000000000..394f7b86102a
--- /dev/null
+++ b/Documentation/mm/damon/tlb_flush.rst
@@ -0,0 +1,131 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==========================================
+DAMON TLB Flush Policy
+==========================================
+
+:Author: Kunwu Chan <kunwu.chan@gmail.com>
+:Author: Wang Lian <lianux.mm@gmail.com>
+
+Overview
+========
+
+DAMON monitors data access by sampling PTE (Page Table Entry) Accessed bits
+using ``ptep_test_and_clear_young()`` and ``pmdp_test_and_clear_young()``.
+These functions clear the Accessed bit but do **not** flush the TLB
+(Translation Lookaside Buffer).  This is an intentional design choice.
+
+Questions about this behavior come up repeatedly, both on the mailing list
+and in private inquiries.  This document describes the reasoning, the
+trade-offs, and recommendations for users and testers.
+
+Background
+==========
+
+DAMON's access check works as follows:
+
+1. Clear the PTE Accessed bit for a sampled page.
+2. Wait for one ``sampling interval``.
+3. Check if the Accessed bit has been set again by the hardware.
+
+If the bit was set again, the page was accessed during the sampling interval.
+
+On architectures with hardware-managed TLB (e.g., x86, arm64), the CPU may
+cache the Accessed bit state in the TLB.  After DAMON clears the Accessed bit
+in the page table, a stale TLB entry with the old Accessed bit remains in the
+TLB.  When the workload accesses the page, the access hits the stale TLB
+entry and does not trigger a page table walk, so the Accessed bit in the page
+table is not set again.  DAMON therefore fails to detect real accesses on its
+next check, reporting false negatives.
+
+Flushing the TLB after clearing the Accessed bit prevents stale TLB entries
+and eliminates this problem.  Functions such as ``ptep_clear_flush_young()`` and
+``pmdp_clear_flush_young()`` provide this behavior.  However, TLB flushes come
+at a performance cost.
+
+Why DAMON Does Not Flush TLB
+============================
+
+DAMON intentionally avoids TLB flushes to keep monitoring overhead low.
+The decision was made after measuring the performance impact of adding TLB
+flushes to the sampling path.  The measurement showed the overhead is
+significant enough to matter for production use [1]_.
+
+Production workloads typically have large working sets that flush TLB buffers
+anyway through normal memory access patterns.  Stale TLB entries that could
+cause monitoring inaccuracies are evicted by the workload's own memory activity
+before the next sampling interval.  The accuracy impact is therefore negligible in
+production.
+
+The following table summarizes the trade-off:
+
++---------------------+-----------------------------+---------------------------+
+|                     | Without TLB Flush (current) | With TLB Flush            |
++---------------------+-----------------------------+---------------------------+
+| Monitoring Overhead | Low                         | Higher (flush cost)       |
++---------------------+-----------------------------+---------------------------+
+| Accuracy (prod)     | Good                        | Good                      |
++---------------------+-----------------------------+---------------------------+
+| Accuracy (test)     | May degrade                 | Good                      |
++---------------------+-----------------------------+---------------------------+
+
+Impact on Testing and Small Workloads
+=====================================
+
+The lack of TLB flush becomes problematic when the working set is small enough
+to fit entirely within the TLB reach.  This is common in test environments
+and synthetic benchmarks.  In such cases, stale TLB entries persist across
+sampling intervals, so DAMON reports false accesses and monitoring results
+become incorrect.
+
+For example, on a machine with a large TLB buffer, a test workload of a few
+tens of megabytes may never experience TLB eviction.  DAMON's WSS (Working Set
+Size) estimation can report 100% error (all regions reported as accessed,
+or none reported as accessed depending on timing), and DAMOS schemes may never
+trigger correctly.
+
+This issue was observed in DAMON selftests and was addressed by increasing the
+test working set size to simulate production-like conditions, rather than
+changing DAMON's TLB flush behavior [2]_.  The selftest working set size was
+increased up to 160 MiB for this reason.
+
+Recommendations
+===============
+
+For Users
+---------
+
+If you observe unexpected ``nr_accesses`` values or inaccurate working
+set size estimates, the cause is likely stale TLB entries from DAMON's
+sampling without TLB flushes.  This happens when the working set fits
+within the TLB reach, which is uncommon for production workloads but can
+occur with small workloads.  See the For Testers section below for
+how to verify this.
+
+For Testers and Developers
+--------------------------
+
+When writing DAMON tests, ensure the test workload's working set is large
+enough to trigger natural TLB eviction on the target test machine.  The
+exact size depends on the CPU's TLB configuration.  The DAMON selftest for
+WSS estimation uses 160 MiB per region after finding smaller sizes
+unreliable on systems with large TLB buffers [2]_.
+
+For out-of-tree tests, gradually increase the working set size until DAMON
+reports stable and accurate results, then use that size as the baseline for
+subsequent tests on the same hardware.
+
+If DAMON reports unexpectedly high ``nr_accesses`` or empty
+``tried_regions``, the ``diagnose_empty_tried_regions.py`` script from
+DAMON selftests can help determine whether stale TLB entries are the cause.
+
+The existing DAMON selftests follow this approach [2]_.
+
+References
+==========
+
+.. [1] `DAMON TLB flush overhead measurement
+   <https://lore.kernel.org/20200403103059.12762-1-sjpark@amazon.com/>`_
+
+.. [2] `DAMON selftest: increase working set size for reliable results
+   <https://lore.kernel.org/20260117020731.226785-3-sj@kernel.org/>`_
-- 
2.43.0


^ permalink raw reply related

* [RFC v1 0/9] kho: granular compatibility and header decoupling
From: Pasha Tatashin @ 2026-06-05  3:32 UTC (permalink / raw)
  To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
	jasonmiu, linux-kernel, corbet, ran.xiaokai, pasha.tatashin,
	kexec, pratyush, graf

This series decouples the compatibility tracking and code organization
of individual KHO subsystems (radix tree, vmalloc, and block device).

The diff here is a bit larger than I'd like, but most of it is just
refactoring and moving code around to modularize the subsystems rather
than functional changes.

Specifically, this series separates KHO data structures from KHO
core functionality:
- KHO data structures are those that have ABI checks between kernel
  versions (e.g., vmalloc, radix trees, and block devices).
- KHO core is the functionality that involves passing the memory
  from one kernel to another.

KHO core depends on some of these data structures, but other users
of KHO also depend on them. The number of data structures keeps
growing: first we introduced vmalloc, then radix trees, then
linked blocks, and next we are planning to add an xarray-like
data structure. Keeping all of this within the same `kexec_handover.c`
file, and also under the same global version, is no longer sustainable.

To address this, this series:
1. Refactors and reorganizes the code by splitting out radix tree
   and vmalloc into separate files.
2. Moves and organizes internal and ABI headers into structured
   directories under include/linux/kho/ and include/linux/kho/abi/.
   Instead of cluttering include/linux/ with prefix-styled headers like
   kho_block.h or kho_radix_tree.h, we use the already existing
   include/linux/kho/ directory (e.g., kho/block.h and
   kho/radix_tree.h).
3. Introduces a standard set of compatibility helpers in
   kho/abi/compat.h.
4. Decouples the compatibility strings of individual KHO subsystems
   (radix tree, vmalloc, and block) from the global KHO version.
   This enables independent, granular compatibility versioning.
5. Adds a KUnit test suite to verify that the composite compatibility
   strings of different subsystems remain unique and sorted in
   alphabetical order, guaranteeing a consistent and predictable
   representation across configurations.

This series is to gather feedback on the overall design and layout
of the granular compatibility mechanism.

Pasha Tatashin (9):
  kho: split out radix tree tracker into kho_radix.c
  kho: split radix tree headers out of kexec_handover.h
  kho: split out vmalloc preservation into kho_vmalloc.c
  kho: split vmalloc headers out of kexec_handover.h
  kho: move kho_block.h to kho/block.h
  kho: introduce compatibility helpers and decouple block version
  kho: decouple radix tree compatibility from global KHO version
  kho: decouple vmalloc compatibility from global KHO version and update
    memfd
  liveupdate: add KUnit test to verify alphabetical order of
    compatibility strings

 Documentation/core-api/kho/abi.rst            |  11 +-
 Documentation/core-api/kho/index.rst          |  10 +-
 MAINTAINERS                                   |   1 -
 include/linux/kexec_handover.h                |  18 -
 include/linux/kho/abi/block.h                 |   4 +-
 include/linux/kho/abi/compat.h                |  33 ++
 include/linux/kho/abi/kexec_handover.h        | 203 +------
 include/linux/kho/abi/luo.h                   |   8 +-
 include/linux/kho/abi/memfd.h                 |  12 +-
 include/linux/kho/abi/radix_tree.h            | 133 +++++
 include/linux/kho/abi/vmalloc.h               | 101 ++++
 include/linux/{kho_block.h => kho/block.h}    |   2 +-
 .../{kho_radix_tree.h => kho/radix_tree.h}    |   5 +-
 include/linux/kho/vmalloc.h                   |  34 ++
 kernel/liveupdate/Kconfig                     |  15 +
 kernel/liveupdate/Makefile                    |   9 +-
 kernel/liveupdate/kexec_handover.c            | 531 +-----------------
 kernel/liveupdate/kho_block.c                 |   2 +-
 kernel/liveupdate/kho_radix.c                 | 290 ++++++++++
 kernel/liveupdate/kho_vmalloc.c               | 274 +++++++++
 kernel/liveupdate/liveupdate_test.c           |  56 ++
 kernel/liveupdate/luo_internal.h              |   2 +-
 kernel/liveupdate/luo_session.c               |   2 +-
 lib/test_kho.c                                |   1 +
 mm/memfd_luo.c                                |   1 +
 tools/testing/selftests/liveupdate/config     |   1 +
 26 files changed, 997 insertions(+), 762 deletions(-)
 create mode 100644 include/linux/kho/abi/compat.h
 create mode 100644 include/linux/kho/abi/radix_tree.h
 create mode 100644 include/linux/kho/abi/vmalloc.h
 rename include/linux/{kho_block.h => kho/block.h} (100%)
 rename include/linux/{kho_radix_tree.h => kho/radix_tree.h} (96%)
 create mode 100644 include/linux/kho/vmalloc.h
 create mode 100644 kernel/liveupdate/kho_radix.c
 create mode 100644 kernel/liveupdate/kho_vmalloc.c
 create mode 100644 kernel/liveupdate/liveupdate_test.c


base-commit: 5fb813ae0009d97fc414f08ad73286f562e9a123
-- 
2.53.0


^ permalink raw reply

* [RFC v1 1/9] kho: split out radix tree tracker into kho_radix.c
From: Pasha Tatashin @ 2026-06-05  3:32 UTC (permalink / raw)
  To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
	jasonmiu, linux-kernel, corbet, ran.xiaokai, pasha.tatashin,
	kexec, pratyush, graf
In-Reply-To: <20260605033235.717351-1-pasha.tatashin@soleen.com>

Move the radix tree tracker implementation from the core KHO code
into its own dedicated file (kho_radix.c).

This is a pure code movement patch; no logic or functional changes are
introduced.

Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
 Documentation/core-api/kho/index.rst |   3 +
 kernel/liveupdate/Makefile           |   6 +-
 kernel/liveupdate/kexec_handover.c   | 273 -------------------------
 kernel/liveupdate/kho_radix.c        | 290 +++++++++++++++++++++++++++
 4 files changed, 298 insertions(+), 274 deletions(-)
 create mode 100644 kernel/liveupdate/kho_radix.c

diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst
index 320914a42178..a9892c671ec3 100644
--- a/Documentation/core-api/kho/index.rst
+++ b/Documentation/core-api/kho/index.rst
@@ -83,6 +83,9 @@ Public API
 .. kernel-doc:: kernel/liveupdate/kexec_handover.c
   :export:
 
+.. kernel-doc:: kernel/liveupdate/kho_radix.c
+  :export:
+
 KHO Serialization Blocks API
 ============================
 
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index eec9d3ae07eb..a3ee8a5c27a2 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -7,7 +7,11 @@ luo-y :=								\
 		luo_flb.o						\
 		luo_session.o
 
-obj-$(CONFIG_KEXEC_HANDOVER)		+= kexec_handover.o
+kho-y :=								\
+		kexec_handover.o					\
+		kho_radix.o
+
+obj-$(CONFIG_KEXEC_HANDOVER)		+= kho.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)	+= kexec_handover_debug.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)	+= kexec_handover_debugfs.o
 
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4834a809985a..041efff7ca11 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -5,7 +5,6 @@
  * Copyright (C) 2025 Microsoft Corporation, Mike Rapoport <rppt@kernel.org>
  * Copyright (C) 2025 Google LLC, Changyuan Lyu <changyuanl@google.com>
  * Copyright (C) 2025 Pasha Tatashin <pasha.tatashin@soleen.com>
- * Copyright (C) 2026 Google LLC, Jason Miu <jasonmiu@google.com>
  */
 
 #define pr_fmt(fmt) "KHO: " fmt
@@ -84,278 +83,6 @@ static struct kho_out kho_out = {
 	},
 };
 
-/**
- * kho_radix_encode_key - Encodes a physical address and order into a radix key.
- * @phys: The physical address of the page.
- * @order: The order of the page.
- *
- * This function combines a page's physical address and its order into a
- * single unsigned long, which is used as a key for all radix tree
- * operations.
- *
- * Return: The encoded unsigned long radix key.
- */
-static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
-{
-	/* Order bits part */
-	unsigned long h = 1UL << (KHO_ORDER_0_LOG2 - order);
-	/* Shifted physical address part */
-	unsigned long l = phys >> (PAGE_SHIFT + order);
-
-	return h | l;
-}
-
-/**
- * kho_radix_decode_key - Decodes a radix key back into a physical address and order.
- * @key: The unsigned long key to decode.
- * @order: An output parameter, a pointer to an unsigned int where the decoded
- *         page order will be stored.
- *
- * This function reverses the encoding performed by kho_radix_encode_key(),
- * extracting the original physical address and page order from a given key.
- *
- * Return: The decoded physical address.
- */
-static phys_addr_t kho_radix_decode_key(unsigned long key, unsigned int *order)
-{
-	unsigned int order_bit = fls64(key);
-	phys_addr_t phys;
-
-	/* order_bit is numbered starting at 1 from fls64 */
-	*order = KHO_ORDER_0_LOG2 - order_bit + 1;
-	/* The order is discarded by the shift */
-	phys = key << (PAGE_SHIFT + *order);
-
-	return phys;
-}
-
-static unsigned long kho_radix_get_bitmap_index(unsigned long key)
-{
-	return key % (1 << KHO_BITMAP_SIZE_LOG2);
-}
-
-static unsigned long kho_radix_get_table_index(unsigned long key,
-					       unsigned int level)
-{
-	int s;
-
-	s = ((level - 1) * KHO_TABLE_SIZE_LOG2) + KHO_BITMAP_SIZE_LOG2;
-	return (key >> s) % (1 << KHO_TABLE_SIZE_LOG2);
-}
-
-/**
- * kho_radix_add_page - Marks a page as preserved in the radix tree.
- * @tree: The KHO radix tree.
- * @pfn: The page frame number of the page to preserve.
- * @order: The order of the page.
- *
- * This function traverses the radix tree based on the key derived from @pfn
- * and @order. It sets the corresponding bit in the leaf bitmap to mark the
- * page for preservation. If intermediate nodes do not exist along the path,
- * they are allocated and added to the tree.
- *
- * Return: 0 on success, or a negative error code on failure.
- */
-int kho_radix_add_page(struct kho_radix_tree *tree,
-		       unsigned long pfn, unsigned int order)
-{
-	/* Newly allocated nodes for error cleanup */
-	struct kho_radix_node *intermediate_nodes[KHO_TREE_MAX_DEPTH] = { 0 };
-	unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
-	struct kho_radix_node *anchor_node = NULL;
-	struct kho_radix_node *node = tree->root;
-	struct kho_radix_node *new_node;
-	unsigned int i, idx, anchor_idx;
-	struct kho_radix_leaf *leaf;
-	int err = 0;
-
-	if (WARN_ON_ONCE(!tree->root))
-		return -EINVAL;
-
-	might_sleep();
-
-	guard(mutex)(&tree->lock);
-
-	/* Go from high levels to low levels */
-	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
-		idx = kho_radix_get_table_index(key, i);
-
-		if (node->table[idx]) {
-			node = phys_to_virt(node->table[idx]);
-			continue;
-		}
-
-		/* Next node is empty, create a new node for it */
-		new_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
-		if (!new_node) {
-			err = -ENOMEM;
-			goto err_free_nodes;
-		}
-
-		node->table[idx] = virt_to_phys(new_node);
-
-		/*
-		 * Capture the node where the new branch starts for cleanup
-		 * if allocation fails.
-		 */
-		if (!anchor_node) {
-			anchor_node = node;
-			anchor_idx = idx;
-		}
-		intermediate_nodes[i] = new_node;
-
-		node = new_node;
-	}
-
-	/* Handle the leaf level bitmap (level 0) */
-	idx = kho_radix_get_bitmap_index(key);
-	leaf = (struct kho_radix_leaf *)node;
-	__set_bit(idx, leaf->bitmap);
-
-	return 0;
-
-err_free_nodes:
-	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
-		if (intermediate_nodes[i])
-			free_page((unsigned long)intermediate_nodes[i]);
-	}
-	if (anchor_node)
-		anchor_node->table[anchor_idx] = 0;
-
-	return err;
-}
-EXPORT_SYMBOL_GPL(kho_radix_add_page);
-
-/**
- * kho_radix_del_page - Removes a page's preservation status from the radix tree.
- * @tree: The KHO radix tree.
- * @pfn: The page frame number of the page to unpreserve.
- * @order: The order of the page.
- *
- * This function traverses the radix tree and clears the bit corresponding to
- * the page, effectively removing its "preserved" status. It does not free
- * the tree's intermediate nodes, even if they become empty.
- */
-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
-			unsigned int order)
-{
-	unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
-	struct kho_radix_node *node = tree->root;
-	struct kho_radix_leaf *leaf;
-	unsigned int i, idx;
-
-	if (WARN_ON_ONCE(!tree->root))
-		return;
-
-	might_sleep();
-
-	guard(mutex)(&tree->lock);
-
-	/* Go from high levels to low levels */
-	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
-		idx = kho_radix_get_table_index(key, i);
-
-		/*
-		 * Attempting to delete a page that has not been preserved,
-		 * return with a warning.
-		 */
-		if (WARN_ON(!node->table[idx]))
-			return;
-
-		node = phys_to_virt(node->table[idx]);
-	}
-
-	/* Handle the leaf level bitmap (level 0) */
-	leaf = (struct kho_radix_leaf *)node;
-	idx = kho_radix_get_bitmap_index(key);
-	__clear_bit(idx, leaf->bitmap);
-}
-EXPORT_SYMBOL_GPL(kho_radix_del_page);
-
-static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
-			       unsigned long key,
-			       kho_radix_tree_walk_callback_t cb)
-{
-	unsigned long *bitmap = (unsigned long *)leaf;
-	unsigned int order;
-	phys_addr_t phys;
-	unsigned int i;
-	int err;
-
-	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
-		phys = kho_radix_decode_key(key | i, &order);
-		err = cb(phys, order);
-		if (err)
-			return err;
-	}
-
-	return 0;
-}
-
-static int __kho_radix_walk_tree(struct kho_radix_node *root,
-				 unsigned int level, unsigned long start,
-				 kho_radix_tree_walk_callback_t cb)
-{
-	struct kho_radix_node *node;
-	struct kho_radix_leaf *leaf;
-	unsigned long key, i;
-	unsigned int shift;
-	int err;
-
-	for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
-		if (!root->table[i])
-			continue;
-
-		shift = ((level - 1) * KHO_TABLE_SIZE_LOG2) +
-			KHO_BITMAP_SIZE_LOG2;
-		key = start | (i << shift);
-
-		node = phys_to_virt(root->table[i]);
-
-		if (level == 1) {
-			/*
-			 * we are at level 1,
-			 * node is pointing to the level 0 bitmap.
-			 */
-			leaf = (struct kho_radix_leaf *)node;
-			err = kho_radix_walk_leaf(leaf, key, cb);
-		} else {
-			err  = __kho_radix_walk_tree(node, level - 1,
-						     key, cb);
-		}
-
-		if (err)
-			return err;
-	}
-
-	return 0;
-}
-
-/**
- * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.
- * @tree: A pointer to the KHO radix tree to walk.
- * @cb: A callback function of type kho_radix_tree_walk_callback_t that will be
- *      invoked for each preserved page found in the tree. The callback receives
- *      the physical address and order of the preserved page.
- *
- * This function walks the radix tree, searching from the specified top level
- * down to the lowest level (level 0). For each preserved page found, it invokes
- * the provided callback, passing the page's physical address and order.
- *
- * Return: 0 if the walk completed the specified tree, or the non-zero return
- *         value from the callback that stopped the walk.
- */
-int kho_radix_walk_tree(struct kho_radix_tree *tree,
-			kho_radix_tree_walk_callback_t cb)
-{
-	if (WARN_ON_ONCE(!tree->root))
-		return -EINVAL;
-
-	guard(mutex)(&tree->lock);
-
-	return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb);
-}
-EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
 
 /* For physically contiguous 0-order pages. */
 static void kho_init_pages(struct page *page, unsigned long nr_pages)
diff --git a/kernel/liveupdate/kho_radix.c b/kernel/liveupdate/kho_radix.c
new file mode 100644
index 000000000000..c836783a1376
--- /dev/null
+++ b/kernel/liveupdate/kho_radix.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * kho_radix.c - KHO radix tree tracker for preserved memory pages
+ * Copyright (C) 2025 Microsoft Corporation, Mike Rapoport <rppt@kernel.org>
+ * Copyright (C) 2025 Pasha Tatashin <pasha.tatashin@soleen.com>
+ * Copyright (C) 2026 Google LLC, Jason Miu <jasonmiu@google.com>
+ */
+
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho_radix_tree.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+/**
+ * kho_radix_encode_key - Encodes a physical address and order into a radix key.
+ * @phys: The physical address of the page.
+ * @order: The order of the page.
+ *
+ * This function combines a page's physical address and its order into a
+ * single unsigned long, which is used as a key for all radix tree
+ * operations.
+ *
+ * Return: The encoded unsigned long radix key.
+ */
+static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
+{
+	/* Order bits part */
+	unsigned long h = 1UL << (KHO_ORDER_0_LOG2 - order);
+	/* Shifted physical address part */
+	unsigned long l = phys >> (PAGE_SHIFT + order);
+
+	return h | l;
+}
+
+/**
+ * kho_radix_decode_key - Decodes a radix key back into a physical address and order.
+ * @key: The unsigned long key to decode.
+ * @order: An output parameter, a pointer to an unsigned int where the decoded
+ *         page order will be stored.
+ *
+ * This function reverses the encoding performed by kho_radix_encode_key(),
+ * extracting the original physical address and page order from a given key.
+ *
+ * Return: The decoded physical address.
+ */
+static phys_addr_t kho_radix_decode_key(unsigned long key, unsigned int *order)
+{
+	unsigned int order_bit = fls64(key);
+	phys_addr_t phys;
+
+	/* order_bit is numbered starting at 1 from fls64 */
+	*order = KHO_ORDER_0_LOG2 - order_bit + 1;
+	/* The order is discarded by the shift */
+	phys = key << (PAGE_SHIFT + *order);
+
+	return phys;
+}
+
+static unsigned long kho_radix_get_bitmap_index(unsigned long key)
+{
+	return key % (1 << KHO_BITMAP_SIZE_LOG2);
+}
+
+static unsigned long kho_radix_get_table_index(unsigned long key,
+					       unsigned int level)
+{
+	int s;
+
+	s = ((level - 1) * KHO_TABLE_SIZE_LOG2) + KHO_BITMAP_SIZE_LOG2;
+	return (key >> s) % (1 << KHO_TABLE_SIZE_LOG2);
+}
+
+/**
+ * kho_radix_add_page - Marks a page as preserved in the radix tree.
+ * @tree: The KHO radix tree.
+ * @pfn: The page frame number of the page to preserve.
+ * @order: The order of the page.
+ *
+ * This function traverses the radix tree based on the key derived from @pfn
+ * and @order. It sets the corresponding bit in the leaf bitmap to mark the
+ * page for preservation. If intermediate nodes do not exist along the path,
+ * they are allocated and added to the tree.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int kho_radix_add_page(struct kho_radix_tree *tree,
+		       unsigned long pfn, unsigned int order)
+{
+	/* Newly allocated nodes for error cleanup */
+	struct kho_radix_node *intermediate_nodes[KHO_TREE_MAX_DEPTH] = { 0 };
+	unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
+	struct kho_radix_node *anchor_node = NULL;
+	struct kho_radix_node *node = tree->root;
+	struct kho_radix_node *new_node;
+	unsigned int i, idx, anchor_idx;
+	struct kho_radix_leaf *leaf;
+	int err = 0;
+
+	if (WARN_ON_ONCE(!tree->root))
+		return -EINVAL;
+
+	might_sleep();
+
+	guard(mutex)(&tree->lock);
+
+	/* Go from high levels to low levels */
+	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
+		idx = kho_radix_get_table_index(key, i);
+
+		if (node->table[idx]) {
+			node = phys_to_virt(node->table[idx]);
+			continue;
+		}
+
+		/* Next node is empty, create a new node for it */
+		new_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
+		if (!new_node) {
+			err = -ENOMEM;
+			goto err_free_nodes;
+		}
+
+		node->table[idx] = virt_to_phys(new_node);
+
+		/*
+		 * Capture the node where the new branch starts for cleanup
+		 * if allocation fails.
+		 */
+		if (!anchor_node) {
+			anchor_node = node;
+			anchor_idx = idx;
+		}
+		intermediate_nodes[i] = new_node;
+
+		node = new_node;
+	}
+
+	/* Handle the leaf level bitmap (level 0) */
+	idx = kho_radix_get_bitmap_index(key);
+	leaf = (struct kho_radix_leaf *)node;
+	__set_bit(idx, leaf->bitmap);
+
+	return 0;
+
+err_free_nodes:
+	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
+		if (intermediate_nodes[i])
+			free_page((unsigned long)intermediate_nodes[i]);
+	}
+	if (anchor_node)
+		anchor_node->table[anchor_idx] = 0;
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(kho_radix_add_page);
+
+/**
+ * kho_radix_del_page - Removes a page's preservation status from the radix tree.
+ * @tree: The KHO radix tree.
+ * @pfn: The page frame number of the page to unpreserve.
+ * @order: The order of the page.
+ *
+ * This function traverses the radix tree and clears the bit corresponding to
+ * the page, effectively removing its "preserved" status. It does not free
+ * the tree's intermediate nodes, even if they become empty.
+ */
+void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
+			unsigned int order)
+{
+	unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
+	struct kho_radix_node *node = tree->root;
+	struct kho_radix_leaf *leaf;
+	unsigned int i, idx;
+
+	if (WARN_ON_ONCE(!tree->root))
+		return;
+
+	might_sleep();
+
+	guard(mutex)(&tree->lock);
+
+	/* Go from high levels to low levels */
+	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
+		idx = kho_radix_get_table_index(key, i);
+
+		/*
+		 * Attempting to delete a page that has not been preserved,
+		 * return with a warning.
+		 */
+		if (WARN_ON(!node->table[idx]))
+			return;
+
+		node = phys_to_virt(node->table[idx]);
+	}
+
+	/* Handle the leaf level bitmap (level 0) */
+	leaf = (struct kho_radix_leaf *)node;
+	idx = kho_radix_get_bitmap_index(key);
+	__clear_bit(idx, leaf->bitmap);
+}
+EXPORT_SYMBOL_GPL(kho_radix_del_page);
+
+static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
+			       unsigned long key,
+			       kho_radix_tree_walk_callback_t cb)
+{
+	unsigned long *bitmap = (unsigned long *)leaf;
+	unsigned int order;
+	phys_addr_t phys;
+	unsigned int i;
+	int err;
+
+	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
+		phys = kho_radix_decode_key(key | i, &order);
+		err = cb(phys, order);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int __kho_radix_walk_tree(struct kho_radix_node *root,
+				 unsigned int level, unsigned long start,
+				 kho_radix_tree_walk_callback_t cb)
+{
+	struct kho_radix_node *node;
+	struct kho_radix_leaf *leaf;
+	unsigned long key, i;
+	unsigned int shift;
+	int err;
+
+	for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
+		if (!root->table[i])
+			continue;
+
+		shift = ((level - 1) * KHO_TABLE_SIZE_LOG2) +
+			KHO_BITMAP_SIZE_LOG2;
+		key = start | (i << shift);
+
+		node = phys_to_virt(root->table[i]);
+
+		if (level == 1) {
+			/*
+			 * we are at level 1,
+			 * node is pointing to the level 0 bitmap.
+			 */
+			leaf = (struct kho_radix_leaf *)node;
+			err = kho_radix_walk_leaf(leaf, key, cb);
+		} else {
+			err  = __kho_radix_walk_tree(node, level - 1,
+						     key, cb);
+		}
+
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+/**
+ * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.
+ * @tree: A pointer to the KHO radix tree to walk.
+ * @cb: A callback function of type kho_radix_tree_walk_callback_t that will be
+ *      invoked for each preserved page found in the tree. The callback receives
+ *      the physical address and order of the preserved page.
+ *
+ * This function walks the radix tree, searching from the specified top level
+ * down to the lowest level (level 0). For each preserved page found, it invokes
+ * the provided callback, passing the page's physical address and order.
+ *
+ * Return: 0 if the walk completed the specified tree, or the non-zero return
+ *         value from the callback that stopped the walk.
+ */
+int kho_radix_walk_tree(struct kho_radix_tree *tree,
+			kho_radix_tree_walk_callback_t cb)
+{
+	if (WARN_ON_ONCE(!tree->root))
+		return -EINVAL;
+
+	guard(mutex)(&tree->lock);
+
+	return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb);
+}
+EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
-- 
2.53.0


^ permalink raw reply related

* [RFC v1 2/9] kho: split radix tree headers out of kexec_handover.h
From: Pasha Tatashin @ 2026-06-05  3:32 UTC (permalink / raw)
  To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
	jasonmiu, linux-kernel, corbet, ran.xiaokai, pasha.tatashin,
	kexec, pratyush, graf
In-Reply-To: <20260605033235.717351-1-pasha.tatashin@soleen.com>

Split the radix tree tracker-related ABI definitions and header
declarations out of the monolithic kexec_handover.h header into a
dedicated header file (radix_tree.h).

Additionally, rename kho_radix_tree.h to kho/radix_tree.h, organizing it
within the existing kho directory structure as more KHO data structures
are introduced.

This is a pure code movement patch; no logic or functional changes are
introduced.

Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
 Documentation/core-api/kho/abi.rst            |   3 +-
 Documentation/core-api/kho/index.rst          |   2 +-
 include/linux/kho/abi/kexec_handover.h        | 114 ---------------
 include/linux/kho/abi/radix_tree.h            | 131 ++++++++++++++++++
 .../{kho_radix_tree.h => kho/radix_tree.h}    |   5 +-
 kernel/liveupdate/kexec_handover.c            |   2 +-
 kernel/liveupdate/kho_radix.c                 |   2 +-
 7 files changed, 137 insertions(+), 122 deletions(-)
 create mode 100644 include/linux/kho/abi/radix_tree.h
 rename include/linux/{kho_radix_tree.h => kho/radix_tree.h} (96%)

diff --git a/Documentation/core-api/kho/abi.rst b/Documentation/core-api/kho/abi.rst
index edeb5b311963..da5c6636bb17 100644
--- a/Documentation/core-api/kho/abi.rst
+++ b/Documentation/core-api/kho/abi.rst
@@ -25,8 +25,7 @@ memblock preservation ABI
 KHO persistent memory tracker ABI
 =================================
 
-.. kernel-doc:: include/linux/kho/abi/kexec_handover.h
-  :doc: KHO persistent memory tracker
+.. kernel-doc:: include/linux/kho/abi/radix_tree.h
 
 KHO serialization block ABI
 ===========================
diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst
index a9892c671ec3..f69367d217cf 100644
--- a/Documentation/core-api/kho/index.rst
+++ b/Documentation/core-api/kho/index.rst
@@ -74,7 +74,7 @@ the next KHO, because kexec can overwrite even the original kernel.
 Kexec Handover Radix Tree
 =========================
 
-.. kernel-doc:: include/linux/kho_radix_tree.h
+.. kernel-doc:: include/linux/kho/radix_tree.h
   :doc: Kexec Handover Radix Tree
 
 Public API
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 5e2eb8519bda..99e4a53d4e35 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -4,15 +4,10 @@
  * Copyright (C) 2023 Alexander Graf <graf@amazon.com>
  * Copyright (C) 2025 Microsoft Corporation, Mike Rapoport <rppt@kernel.org>
  * Copyright (C) 2025 Google LLC, Changyuan Lyu <changyuanl@google.com>
- * Copyright (C) 2025 Google LLC, Jason Miu <jasonmiu@google.com>
  */
 
 #ifndef _LINUX_KHO_ABI_KEXEC_HANDOVER_H
 #define _LINUX_KHO_ABI_KEXEC_HANDOVER_H
-
-#include <linux/bits.h>
-#include <linux/log2.h>
-#include <linux/math.h>
 #include <linux/types.h>
 
 #include <asm/page.h>
@@ -177,113 +172,4 @@ struct kho_vmalloc {
 	unsigned short order;
 };
 
-/**
- * DOC: KHO persistent memory tracker
- *
- * KHO tracks preserved memory using a radix tree data structure. Each node of
- * the tree is exactly a single page. The leaf nodes are bitmaps where each set
- * bit is a preserved page of any order. The intermediate nodes are tables of
- * physical addresses that point to a lower level node.
- *
- * The tree hierarchy is shown below::
- *
- *   root
- *   +-------------------+
- *   |     Level 5       | (struct kho_radix_node)
- *   +-------------------+
- *     |
- *     v
- *   +-------------------+
- *   |     Level 4       | (struct kho_radix_node)
- *   +-------------------+
- *     |
- *     | ... (intermediate levels)
- *     |
- *     v
- *   +-------------------+
- *   |      Level 0      | (struct kho_radix_leaf)
- *   +-------------------+
- *
- * The tree is traversed using a key that encodes the page's physical address
- * (pa) and its order into a single unsigned long value. The encoded key value
- * is composed of two parts: the 'order bit' in the upper part and the
- * 'shifted physical address' in the lower part.::
- *
- *   +------------+-----------------------------+--------------------------+
- *   | Page Order | Order Bit                   | Shifted Physical Address |
- *   +------------+-----------------------------+--------------------------+
- *   | 0          | ...000100 ... (at bit 52)   | pa >> (PAGE_SHIFT + 0)   |
- *   | 1          | ...000010 ... (at bit 51)   | pa >> (PAGE_SHIFT + 1)   |
- *   | 2          | ...000001 ... (at bit 50)   | pa >> (PAGE_SHIFT + 2)   |
- *   | ...        | ...                         | ...                      |
- *   +------------+-----------------------------+--------------------------+
- *
- * Shifted Physical Address:
- * The 'shifted physical address' is the physical address normalized for its
- * order. It effectively represents the PFN shifted right by the order.
- *
- * Order Bit:
- * The 'order bit' encodes the page order by setting a single bit at a
- * specific position. The position of this bit itself represents the order.
- *
- * For instance, on a 64-bit system with 4KB pages (PAGE_SHIFT = 12), the
- * maximum range for the shifted physical address (for order 0) is 52 bits
- * (64 - 12). This address occupies bits [0-51]. For order 0, the order bit is
- * set at position 52.
- *
- * The following diagram illustrates how the encoded key value is split into
- * indices for the tree levels, with PAGE_SIZE of 4KB::
- *
- *        63:60   59:51    50:42    41:33    32:24    23:15         14:0
- *   +---------+--------+--------+--------+--------+--------+-----------------+
- *   |    0    |  Lv 5  |  Lv 4  |  Lv 3  |  Lv 2  |  Lv 1  |  Lv 0 (bitmap)  |
- *   +---------+--------+--------+--------+--------+--------+-----------------+
- *
- * The radix tree stores pages of all orders in a single 6-level hierarchy. It
- * efficiently shares higher tree levels, especially due to common zero top
- * address bits, allowing a single, efficient algorithm to manage all
- * pages. This bitmap approach also offers memory efficiency; for example, a
- * 512KB bitmap can cover a 16GB memory range for 0-order pages with PAGE_SIZE =
- * 4KB.
- *
- * The data structures defined here are part of the KHO ABI. Any modification
- * to these structures that breaks backward compatibility must be accompanied by
- * an update to the "compatible" string. This ensures that a newer kernel can
- * correctly interpret the data passed by an older kernel.
- */
-
-/*
- * Defines constants for the KHO radix tree structure, used to track preserved
- * memory. These constants govern the indexing, sizing, and depth of the tree.
- */
-enum kho_radix_consts {
-	/*
-	 * The bit position of the order bit (and also the length of the
-	 * shifted physical address) for an order-0 page.
-	 */
-	KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT,
-
-	/* Size of the table in kho_radix_node, in log2 */
-	KHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)),
-
-	/* Number of bits in the kho_radix_leaf bitmap, in log2 */
-	KHO_BITMAP_SIZE_LOG2 = PAGE_SHIFT + const_ilog2(BITS_PER_BYTE),
-
-	/*
-	 * The total tree depth is the number of intermediate levels
-	 * and 1 bitmap level.
-	 */
-	KHO_TREE_MAX_DEPTH =
-		DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
-			     KHO_TABLE_SIZE_LOG2) + 1,
-};
-
-struct kho_radix_node {
-	u64 table[1 << KHO_TABLE_SIZE_LOG2];
-};
-
-struct kho_radix_leaf {
-	DECLARE_BITMAP(bitmap, 1 << KHO_BITMAP_SIZE_LOG2);
-};
-
 #endif	/* _LINUX_KHO_ABI_KEXEC_HANDOVER_H */
diff --git a/include/linux/kho/abi/radix_tree.h b/include/linux/kho/abi/radix_tree.h
new file mode 100644
index 000000000000..f4cc5c02f37a
--- /dev/null
+++ b/include/linux/kho/abi/radix_tree.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Google LLC, Jason Miu <jasonmiu@google.com>
+ * Copyright (C) 2026 Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#ifndef _LINUX_KHO_ABI_RADIX_TREE_H
+#define _LINUX_KHO_ABI_RADIX_TREE_H
+
+#include <linux/types.h>
+#include <asm/page.h>
+
+/**
+ * DOC: KHO persistent memory tracker
+ *
+ * Subsystems using the KHO persistent memory tracker rely on the stable
+ * Application Binary Interface defined below to pass serialized state from a
+ * pre-update kernel to a post-update kernel.
+ *
+ * This interface is a contract. Any modification to the structure fields,
+ * compatible strings, or the layout of the serialization structures defined
+ * here constitutes a breaking change. Such changes require incrementing the
+ * version number in the `KHO_FDT_COMPATIBLE` string to prevent a new kernel
+ * from misinterpreting data from an old kernel.
+ *
+ * Changes are allowed provided the compatibility version is incremented;
+ * however, backward/forward compatibility is only guaranteed for kernels
+ * supporting the same ABI version.
+ *
+ * KHO tracks preserved memory using a radix tree data structure. Each node of
+ * the tree is exactly a single page. The leaf nodes are bitmaps where each set
+ * bit is a preserved page of any order. The intermediate nodes are tables of
+ * physical addresses that point to a lower level node.
+ *
+ * The tree hierarchy is shown below::
+ *
+ *   root
+ *   +-------------------+
+ *   |     Level 5       | (struct kho_radix_node)
+ *   +-------------------+
+ *     |
+ *     v
+ *   +-------------------+
+ *   |     Level 4       | (struct kho_radix_node)
+ *   +-------------------+
+ *     |
+ *     | ... (intermediate levels)
+ *     |
+ *     v
+ *   +-------------------+
+ *   |      Level 0      | (struct kho_radix_leaf)
+ *   +-------------------+
+ *
+ * The tree is traversed using a key that encodes the page's physical address
+ * (pa) and its order into a single unsigned long value. The encoded key value
+ * is composed of two parts: the 'order bit' in the upper part and the
+ * 'shifted physical address' in the lower part.::
+ *
+ *   +------------+-----------------------------+--------------------------+
+ *   | Page Order | Order Bit                   | Shifted Physical Address |
+ *   +------------+-----------------------------+--------------------------+
+ *   | 0          | ...000100 ... (at bit 52)   | pa >> (PAGE_SHIFT + 0)   |
+ *   | 1          | ...000010 ... (at bit 51)   | pa >> (PAGE_SHIFT + 1)   |
+ *   | 2          | ...000001 ... (at bit 50)   | pa >> (PAGE_SHIFT + 2)   |
+ *   | ...        | ...                         | ...                      |
+ *   +------------+-----------------------------+--------------------------+
+ *
+ * Shifted Physical Address:
+ * The 'shifted physical address' is the physical address normalized for its
+ * order. It effectively represents the PFN shifted right by the order.
+ *
+ * Order Bit:
+ * The 'order bit' encodes the page order by setting a single bit at a
+ * specific position. The position of this bit itself represents the order.
+ *
+ * For instance, on a 64-bit system with 4KB pages (PAGE_SHIFT = 12), the
+ * maximum range for the shifted physical address (for order 0) is 52 bits
+ * (64 - 12). This address occupies bits [0-51]. For order 0, the order bit is
+ * set at position 52.
+ *
+ * The following diagram illustrates how the encoded key value is split into
+ * indices for the tree levels, with PAGE_SIZE of 4KB::
+ *
+ *        63:60   59:51    50:42    41:33    32:24    23:15         14:0
+ *   +---------+--------+--------+--------+--------+--------+-----------------+
+ *   |    0    |  Lv 5  |  Lv 4  |  Lv 3  |  Lv 2  |  Lv 1  |  Lv 0 (bitmap)  |
+ *   +---------+--------+--------+--------+--------+--------+-----------------+
+ *
+ * The radix tree stores pages of all orders in a single 6-level hierarchy. It
+ * efficiently shares higher tree levels, especially due to common zero top
+ * address bits, allowing a single, efficient algorithm to manage all
+ * pages. This bitmap approach also offers memory efficiency; for example, a
+ * 512KB bitmap can cover a 16GB memory range for 0-order pages with PAGE_SIZE =
+ * 4KB.
+ */
+
+/*
+ * Defines constants for the KHO radix tree structure, used to track preserved
+ * memory. These constants govern the indexing, sizing, and depth of the tree.
+ */
+enum kho_radix_consts {
+	/*
+	 * The bit position of the order bit (and also the length of the
+	 * shifted physical address) for an order-0 page.
+	 */
+	KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT,
+
+	/* Size of the table in kho_radix_node, in log2 */
+	KHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)),
+
+	/* Number of bits in the kho_radix_leaf bitmap, in log2 */
+	KHO_BITMAP_SIZE_LOG2 = PAGE_SHIFT + const_ilog2(BITS_PER_BYTE),
+
+	/*
+	 * The total tree depth is the number of intermediate levels
+	 * and 1 bitmap level.
+	 */
+	KHO_TREE_MAX_DEPTH =
+		DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
+			     KHO_TABLE_SIZE_LOG2) + 1,
+};
+
+struct kho_radix_node {
+	u64 table[1 << KHO_TABLE_SIZE_LOG2];
+};
+
+struct kho_radix_leaf {
+	DECLARE_BITMAP(bitmap, 1 << KHO_BITMAP_SIZE_LOG2);
+};
+
+#endif /* _LINUX_KHO_ABI_RADIX_TREE_H */
diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho/radix_tree.h
similarity index 96%
rename from include/linux/kho_radix_tree.h
rename to include/linux/kho/radix_tree.h
index 84e918b96e53..1e337e73deba 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho/radix_tree.h
@@ -5,6 +5,7 @@
 
 #include <linux/err.h>
 #include <linux/errno.h>
+#include <linux/kho/abi/radix_tree.h>
 #include <linux/mutex_types.h>
 #include <linux/types.h>
 
@@ -24,11 +25,9 @@
  * Client code is responsible for allocating the root node of the tree,
  * initializing the mutex lock, and managing its lifecycle. It must use the
  * tree data structures defined in the KHO ABI,
- * `include/linux/kho/abi/kexec_handover.h`.
+ * `include/linux/kho/abi/radix_tree.h`.
  */
 
-struct kho_radix_node;
-
 struct kho_radix_tree {
 	struct kho_radix_node *root;
 	struct mutex lock; /* protects the tree's structure and root pointer */
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 041efff7ca11..4a3d6a54a17f 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -16,7 +16,7 @@
 #include <linux/kasan.h>
 #include <linux/kexec.h>
 #include <linux/kexec_handover.h>
-#include <linux/kho_radix_tree.h>
+#include <linux/kho/radix_tree.h>
 #include <linux/utsname.h>
 #include <linux/kho/abi/kexec_handover.h>
 #include <linux/kho/abi/kexec_metadata.h>
diff --git a/kernel/liveupdate/kho_radix.c b/kernel/liveupdate/kho_radix.c
index c836783a1376..f48088847264 100644
--- a/kernel/liveupdate/kho_radix.c
+++ b/kernel/liveupdate/kho_radix.c
@@ -11,7 +11,7 @@
 #include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/kho/abi/kexec_handover.h>
-#include <linux/kho_radix_tree.h>
+#include <linux/kho/radix_tree.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
 #include <linux/types.h>
-- 
2.53.0


^ permalink raw reply related

* [RFC v1 3/9] kho: split out vmalloc preservation into kho_vmalloc.c
From: Pasha Tatashin @ 2026-06-05  3:32 UTC (permalink / raw)
  To: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
	jasonmiu, linux-kernel, corbet, ran.xiaokai, pasha.tatashin,
	kexec, pratyush, graf
In-Reply-To: <20260605033235.717351-1-pasha.tatashin@soleen.com>

Move the vmalloc serialization and preservation implementation out of the
core KHO code into its own dedicated file (kho_vmalloc.c).

This is a pure code movement patch; no logic or functional changes are
introduced.

Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
 Documentation/core-api/kho/index.rst |   3 +
 kernel/liveupdate/Makefile           |   3 +-
 kernel/liveupdate/kexec_handover.c   | 258 +------------------------
 kernel/liveupdate/kho_vmalloc.c      | 274 +++++++++++++++++++++++++++
 lib/test_kho.c                       |   1 +
 mm/memfd_luo.c                       |   1 +
 6 files changed, 282 insertions(+), 258 deletions(-)
 create mode 100644 kernel/liveupdate/kho_vmalloc.c

diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst
index f69367d217cf..a10b10700fb9 100644
--- a/Documentation/core-api/kho/index.rst
+++ b/Documentation/core-api/kho/index.rst
@@ -86,6 +86,9 @@ Public API
 .. kernel-doc:: kernel/liveupdate/kho_radix.c
   :export:
 
+.. kernel-doc:: kernel/liveupdate/kho_vmalloc.c
+  :export:
+
 KHO Serialization Blocks API
 ============================
 
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index a3ee8a5c27a2..b481e21a311a 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -9,7 +9,8 @@ luo-y :=								\
 
 kho-y :=								\
 		kexec_handover.o					\
-		kho_radix.o
+		kho_radix.o						\
+		kho_vmalloc.o
 
 obj-$(CONFIG_KEXEC_HANDOVER)		+= kho.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)	+= kexec_handover_debug.o
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4a3d6a54a17f..6672bc168e57 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -13,7 +13,6 @@
 #include <linux/cma.h>
 #include <linux/kmemleak.h>
 #include <linux/count_zeros.h>
-#include <linux/kasan.h>
 #include <linux/kexec.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/radix_tree.h>
@@ -23,11 +22,7 @@
 #include <linux/libfdt.h>
 #include <linux/list.h>
 #include <linux/memblock.h>
-#include <linux/page-isolation.h>
 #include <linux/unaligned.h>
-#include <linux/vmalloc.h>
-
-#include <asm/early_ioremap.h>
 
 /*
  * KHO is tightly coupled with mm init and needs access to some of mm
@@ -84,6 +79,7 @@ static struct kho_out kho_out = {
 };
 
 
+
 /* For physically contiguous 0-order pages. */
 static void kho_init_pages(struct page *page, unsigned long nr_pages)
 {
@@ -702,259 +698,7 @@ void kho_unpreserve_pages(struct page *page, unsigned long nr_pages)
 }
 EXPORT_SYMBOL_GPL(kho_unpreserve_pages);
 
-/* vmalloc flags KHO supports */
-#define KHO_VMALLOC_SUPPORTED_FLAGS	(VM_ALLOC | VM_ALLOW_HUGE_VMAP)
-
-/* KHO internal flags for vmalloc preservations */
-#define KHO_VMALLOC_ALLOC	0x0001
-#define KHO_VMALLOC_HUGE_VMAP	0x0002
-
-static unsigned short vmalloc_flags_to_kho(unsigned int vm_flags)
-{
-	unsigned short kho_flags = 0;
-
-	if (vm_flags & VM_ALLOC)
-		kho_flags |= KHO_VMALLOC_ALLOC;
-	if (vm_flags & VM_ALLOW_HUGE_VMAP)
-		kho_flags |= KHO_VMALLOC_HUGE_VMAP;
-
-	return kho_flags;
-}
-
-static unsigned int kho_flags_to_vmalloc(unsigned short kho_flags)
-{
-	unsigned int vm_flags = 0;
-
-	if (kho_flags & KHO_VMALLOC_ALLOC)
-		vm_flags |= VM_ALLOC;
-	if (kho_flags & KHO_VMALLOC_HUGE_VMAP)
-		vm_flags |= VM_ALLOW_HUGE_VMAP;
-
-	return vm_flags;
-}
-
-static struct kho_vmalloc_chunk *new_vmalloc_chunk(struct kho_vmalloc_chunk *cur)
-{
-	struct kho_vmalloc_chunk *chunk;
-	int err;
-
-	chunk = (struct kho_vmalloc_chunk *)get_zeroed_page(GFP_KERNEL);
-	if (!chunk)
-		return NULL;
-
-	err = kho_preserve_pages(virt_to_page(chunk), 1);
-	if (err)
-		goto err_free;
-	if (cur)
-		KHOSER_STORE_PTR(cur->hdr.next, chunk);
-	return chunk;
-
-err_free:
-	free_page((unsigned long)chunk);
-	return NULL;
-}
-
-static void kho_vmalloc_unpreserve_chunk(struct kho_vmalloc_chunk *chunk,
-					 unsigned short order)
-{
-	struct kho_radix_tree *tree = &kho_out.radix_tree;
-	unsigned long pfn = PHYS_PFN(virt_to_phys(chunk));
-
-	__kho_unpreserve(tree, pfn, pfn + 1);
-
-	for (int i = 0; i < ARRAY_SIZE(chunk->phys) && chunk->phys[i]; i++) {
-		pfn = PHYS_PFN(chunk->phys[i]);
-		__kho_unpreserve(tree, pfn, pfn + (1 << order));
-	}
-}
-
-/**
- * kho_preserve_vmalloc - preserve memory allocated with vmalloc() across kexec
- * @ptr: pointer to the area in vmalloc address space
- * @preservation: placeholder for preservation metadata
- *
- * Instructs KHO to preserve the area in vmalloc address space at @ptr. The
- * physical pages mapped at @ptr will be preserved and on successful return
- * @preservation will hold the physical address of a structure that describes
- * the preservation.
- *
- * NOTE: The memory allocated with vmalloc_node() variants cannot be reliably
- * restored on the same node
- *
- * Return: 0 on success, error code on failure
- */
-int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
-{
-	struct kho_vmalloc_chunk *chunk;
-	struct vm_struct *vm = find_vm_area(ptr);
-	unsigned int order, flags, nr_contig_pages;
-	unsigned int idx = 0;
-	int err;
-
-	if (!vm)
-		return -EINVAL;
-
-	if (vm->flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
-		return -EOPNOTSUPP;
-
-	flags = vmalloc_flags_to_kho(vm->flags);
-	order = get_vm_area_page_order(vm);
-
-	chunk = new_vmalloc_chunk(NULL);
-	if (!chunk)
-		return -ENOMEM;
-	KHOSER_STORE_PTR(preservation->first, chunk);
-
-	nr_contig_pages = (1 << order);
-	for (int i = 0; i < vm->nr_pages; i += nr_contig_pages) {
-		phys_addr_t phys = page_to_phys(vm->pages[i]);
-
-		err = kho_preserve_pages(vm->pages[i], nr_contig_pages);
-		if (err)
-			goto err_free;
-
-		chunk->phys[idx++] = phys;
-		if (idx == ARRAY_SIZE(chunk->phys)) {
-			chunk = new_vmalloc_chunk(chunk);
-			if (!chunk) {
-				err = -ENOMEM;
-				goto err_free;
-			}
-			idx = 0;
-		}
-	}
-
-	preservation->total_pages = vm->nr_pages;
-	preservation->flags = flags;
-	preservation->order = order;
-
-	return 0;
-
-err_free:
-	kho_unpreserve_vmalloc(preservation);
-	return err;
-}
-EXPORT_SYMBOL_GPL(kho_preserve_vmalloc);
-
-/**
- * kho_unpreserve_vmalloc - unpreserve memory allocated with vmalloc()
- * @preservation: preservation metadata returned by kho_preserve_vmalloc()
- *
- * Instructs KHO to unpreserve the area in vmalloc address space that was
- * previously preserved with kho_preserve_vmalloc().
- */
-void kho_unpreserve_vmalloc(struct kho_vmalloc *preservation)
-{
-	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
-
-	while (chunk) {
-		struct kho_vmalloc_chunk *tmp = chunk;
-
-		kho_vmalloc_unpreserve_chunk(chunk, preservation->order);
-
-		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
-		free_page((unsigned long)tmp);
-	}
-}
-EXPORT_SYMBOL_GPL(kho_unpreserve_vmalloc);
-
-/**
- * kho_restore_vmalloc - recreates and populates an area in vmalloc address
- * space from the preserved memory.
- * @preservation: preservation metadata.
- *
- * Recreates an area in vmalloc address space and populates it with memory that
- * was preserved using kho_preserve_vmalloc().
- *
- * Return: pointer to the area in the vmalloc address space, NULL on failure.
- */
-void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
-{
-	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
-	kasan_vmalloc_flags_t kasan_flags = KASAN_VMALLOC_PROT_NORMAL;
-	unsigned int align, order, shift, vm_flags;
-	unsigned long total_pages, contig_pages;
-	unsigned long addr, size;
-	struct vm_struct *area;
-	struct page **pages;
-	unsigned int idx = 0;
-	int err;
-
-	vm_flags = kho_flags_to_vmalloc(preservation->flags);
-	if (vm_flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
-		return NULL;
-
-	total_pages = preservation->total_pages;
-	pages = kvmalloc_objs(*pages, total_pages);
-	if (!pages)
-		return NULL;
-	order = preservation->order;
-	contig_pages = (1 << order);
-	shift = PAGE_SHIFT + order;
-	align = 1 << shift;
-
-	while (chunk) {
-		struct page *page;
-
-		for (int i = 0; i < ARRAY_SIZE(chunk->phys) && chunk->phys[i]; i++) {
-			phys_addr_t phys = chunk->phys[i];
-
-			if (idx + contig_pages > total_pages)
-				goto err_free_pages_array;
-
-			page = kho_restore_pages(phys, contig_pages);
-			if (!page)
-				goto err_free_pages_array;
-
-			for (int j = 0; j < contig_pages; j++)
-				pages[idx++] = page + j;
-
-			phys += contig_pages * PAGE_SIZE;
-		}
-
-		page = kho_restore_pages(virt_to_phys(chunk), 1);
-		if (!page)
-			goto err_free_pages_array;
-		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
-		__free_page(page);
-	}
-
-	if (idx != total_pages)
-		goto err_free_pages_array;
-
-	area = __get_vm_area_node(total_pages * PAGE_SIZE, align, shift,
-				  vm_flags | VM_UNINITIALIZED,
-				  VMALLOC_START, VMALLOC_END,
-				  NUMA_NO_NODE, GFP_KERNEL,
-				  __builtin_return_address(0));
-	if (!area)
-		goto err_free_pages_array;
-
-	addr = (unsigned long)area->addr;
-	size = get_vm_area_size(area);
-	err = vmap_pages_range(addr, addr + size, PAGE_KERNEL, pages, shift);
-	if (err)
-		goto err_free_vm_area;
 
-	area->nr_pages = total_pages;
-	area->pages = pages;
-
-	if (vm_flags & VM_ALLOC)
-		kasan_flags |= KASAN_VMALLOC_VM_ALLOC;
-
-	area->addr = kasan_unpoison_vmalloc(area->addr, total_pages * PAGE_SIZE,
-					    kasan_flags);
-	clear_vm_uninitialized_flag(area);
-
-	return area->addr;
-
-err_free_vm_area:
-	free_vm_area(area);
-err_free_pages_array:
-	kvfree(pages);
-	return NULL;
-}
-EXPORT_SYMBOL_GPL(kho_restore_vmalloc);
 
 /**
  * kho_alloc_preserve - Allocate, zero, and preserve memory.
diff --git a/kernel/liveupdate/kho_vmalloc.c b/kernel/liveupdate/kho_vmalloc.c
new file mode 100644
index 000000000000..84c17b7a81ae
--- /dev/null
+++ b/kernel/liveupdate/kho_vmalloc.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * kho_vmalloc.c - KHO vmalloc space serialization/preservation
+ * Copyright (C) 2025 Microsoft Corporation, Mike Rapoport <rppt@kernel.org>
+ * Copyright (C) 2025 Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/io.h>
+#include <linux/kasan.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+
+#include "../../mm/internal.h"
+#include "kexec_handover_internal.h"
+
+/* vmalloc flags KHO supports */
+#define KHO_VMALLOC_SUPPORTED_FLAGS	(VM_ALLOC | VM_ALLOW_HUGE_VMAP)
+
+/* KHO internal flags for vmalloc preservations */
+#define KHO_VMALLOC_ALLOC	0x0001
+#define KHO_VMALLOC_HUGE_VMAP	0x0002
+
+static unsigned short vmalloc_flags_to_kho(unsigned int vm_flags)
+{
+	unsigned short kho_flags = 0;
+
+	if (vm_flags & VM_ALLOC)
+		kho_flags |= KHO_VMALLOC_ALLOC;
+	if (vm_flags & VM_ALLOW_HUGE_VMAP)
+		kho_flags |= KHO_VMALLOC_HUGE_VMAP;
+
+	return kho_flags;
+}
+
+static unsigned int kho_flags_to_vmalloc(unsigned short kho_flags)
+{
+	unsigned int vm_flags = 0;
+
+	if (kho_flags & KHO_VMALLOC_ALLOC)
+		vm_flags |= VM_ALLOC;
+	if (kho_flags & KHO_VMALLOC_HUGE_VMAP)
+		vm_flags |= VM_ALLOW_HUGE_VMAP;
+
+	return vm_flags;
+}
+
+static struct kho_vmalloc_chunk *new_vmalloc_chunk(struct kho_vmalloc_chunk *cur)
+{
+	struct kho_vmalloc_chunk *chunk;
+	int err;
+
+	chunk = (struct kho_vmalloc_chunk *)get_zeroed_page(GFP_KERNEL);
+	if (!chunk)
+		return NULL;
+
+	err = kho_preserve_pages(virt_to_page(chunk), 1);
+	if (err)
+		goto err_free;
+	if (cur)
+		KHOSER_STORE_PTR(cur->hdr.next, chunk);
+	return chunk;
+
+err_free:
+	free_page((unsigned long)chunk);
+	return NULL;
+}
+
+static void kho_vmalloc_unpreserve_chunk(struct kho_vmalloc_chunk *chunk,
+					 unsigned short order)
+{
+	unsigned long pfn = PHYS_PFN(virt_to_phys(chunk));
+
+	kho_unpreserve_pages(pfn_to_page(pfn), 1);
+
+	for (int i = 0; i < ARRAY_SIZE(chunk->phys) && chunk->phys[i]; i++) {
+		pfn = PHYS_PFN(chunk->phys[i]);
+		kho_unpreserve_pages(pfn_to_page(pfn), 1 << order);
+	}
+}
+
+/**
+ * kho_preserve_vmalloc - preserve memory allocated with vmalloc() across kexec
+ * @ptr: pointer to the area in vmalloc address space
+ * @preservation: placeholder for preservation metadata
+ *
+ * Instructs KHO to preserve the area in vmalloc address space at @ptr. The
+ * physical pages mapped at @ptr will be preserved and on successful return
+ * @preservation will hold the physical address of a structure that describes
+ * the preservation.
+ *
+ * NOTE: The memory allocated with vmalloc_node() variants cannot be reliably
+ * restored on the same node
+ *
+ * Return: 0 on success, error code on failure
+ */
+int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)
+{
+	struct kho_vmalloc_chunk *chunk;
+	struct vm_struct *vm = find_vm_area(ptr);
+	unsigned int order, flags, nr_contig_pages;
+	unsigned int idx = 0;
+	int err;
+
+	if (!vm)
+		return -EINVAL;
+
+	if (vm->flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+		return -EOPNOTSUPP;
+
+	flags = vmalloc_flags_to_kho(vm->flags);
+	order = get_vm_area_page_order(vm);
+
+	chunk = new_vmalloc_chunk(NULL);
+	if (!chunk)
+		return -ENOMEM;
+	KHOSER_STORE_PTR(preservation->first, chunk);
+
+	nr_contig_pages = (1 << order);
+	for (int i = 0; i < vm->nr_pages; i += nr_contig_pages) {
+		phys_addr_t phys = page_to_phys(vm->pages[i]);
+
+		err = kho_preserve_pages(vm->pages[i], nr_contig_pages);
+		if (err)
+			goto err_free;
+
+		chunk->phys[idx++] = phys;
+		if (idx == ARRAY_SIZE(chunk->phys)) {
+			chunk = new_vmalloc_chunk(chunk);
+			if (!chunk) {
+				err = -ENOMEM;
+				goto err_free;
+			}
+			idx = 0;
+		}
+	}
+
+	preservation->total_pages = vm->nr_pages;
+	preservation->flags = flags;
+	preservation->order = order;
+
+	return 0;
+
+err_free:
+	kho_unpreserve_vmalloc(preservation);
+	return err;
+}
+EXPORT_SYMBOL_GPL(kho_preserve_vmalloc);
+
+/**
+ * kho_unpreserve_vmalloc - unpreserve memory allocated with vmalloc()
+ * @preservation: preservation metadata returned by kho_preserve_vmalloc()
+ *
+ * Instructs KHO to unpreserve the area in vmalloc address space that was
+ * previously preserved with kho_preserve_vmalloc().
+ */
+void kho_unpreserve_vmalloc(struct kho_vmalloc *preservation)
+{
+	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
+
+	while (chunk) {
+		struct kho_vmalloc_chunk *tmp = chunk;
+
+		kho_vmalloc_unpreserve_chunk(chunk, preservation->order);
+
+		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+		free_page((unsigned long)tmp);
+	}
+}
+EXPORT_SYMBOL_GPL(kho_unpreserve_vmalloc);
+
+/**
+ * kho_restore_vmalloc - recreates and populates an area in vmalloc address
+ * space from the preserved memory.
+ * @preservation: preservation metadata.
+ *
+ * Recreates an area in vmalloc address space and populates it with memory that
+ * was preserved using kho_preserve_vmalloc().
+ *
+ * Return: pointer to the area in the vmalloc address space, NULL on failure.
+ */
+void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
+{
+	struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
+	kasan_vmalloc_flags_t kasan_flags = KASAN_VMALLOC_PROT_NORMAL;
+	unsigned int align, order, shift, vm_flags;
+	unsigned long total_pages, contig_pages;
+	unsigned long addr, size;
+	struct vm_struct *area;
+	struct page **pages;
+	unsigned int idx = 0;
+	int err;
+
+	vm_flags = kho_flags_to_vmalloc(preservation->flags);
+	if (vm_flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+		return NULL;
+
+	total_pages = preservation->total_pages;
+	pages = kvmalloc_objs(*pages, total_pages);
+	if (!pages)
+		return NULL;
+	order = preservation->order;
+	contig_pages = (1 << order);
+	shift = PAGE_SHIFT + order;
+	align = 1 << shift;
+
+	while (chunk) {
+		struct page *page;
+
+		for (int i = 0; i < ARRAY_SIZE(chunk->phys) && chunk->phys[i]; i++) {
+			phys_addr_t phys = chunk->phys[i];
+
+			if (idx + contig_pages > total_pages)
+				goto err_free_pages_array;
+
+			page = kho_restore_pages(phys, contig_pages);
+			if (!page)
+				goto err_free_pages_array;
+
+			for (int j = 0; j < contig_pages; j++)
+				pages[idx++] = page + j;
+
+			phys += contig_pages * PAGE_SIZE;
+		}
+
+		page = kho_restore_pages(virt_to_phys(chunk), 1);
+		if (!page)
+			goto err_free_pages_array;
+		chunk = KHOSER_LOAD_PTR(chunk->hdr.next);
+		__free_page(page);
+	}
+
+	if (idx != total_pages)
+		goto err_free_pages_array;
+
+	area = __get_vm_area_node(total_pages * PAGE_SIZE, align, shift,
+				  vm_flags | VM_UNINITIALIZED,
+				  VMALLOC_START, VMALLOC_END,
+				  NUMA_NO_NODE, GFP_KERNEL,
+				  __builtin_return_address(0));
+	if (!area)
+		goto err_free_pages_array;
+
+	addr = (unsigned long)area->addr;
+	size = get_vm_area_size(area);
+	err = vmap_pages_range(addr, addr + size, PAGE_KERNEL, pages, shift);
+	if (err)
+		goto err_free_vm_area;
+
+	area->nr_pages = total_pages;
+	area->pages = pages;
+
+	if (vm_flags & VM_ALLOC)
+		kasan_flags |= KASAN_VMALLOC_VM_ALLOC;
+
+	area->addr = kasan_unpoison_vmalloc(area->addr, total_pages * PAGE_SIZE,
+					    kasan_flags);
+	clear_vm_uninitialized_flag(area);
+
+	return area->addr;
+
+err_free_vm_area:
+	free_vm_area(area);
+err_free_pages_array:
+	kvfree(pages);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(kho_restore_vmalloc);
diff --git a/lib/test_kho.c b/lib/test_kho.c
index aa6a0956bb8b..6907e09688dd 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -20,6 +20,7 @@
 #include <linux/vmalloc.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/vmalloc.h>
 
 #include <net/checksum.h>
 
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 59de210bee5f..ade2aa24c7b8 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -76,6 +76,7 @@
 #include <linux/io.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/memfd.h>
+#include <linux/kho/vmalloc.h>
 #include <linux/liveupdate.h>
 #include <linux/shmem_fs.h>
 #include <linux/vmalloc.h>
-- 
2.53.0


^ 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